/* Lattice Boltzmann sample, written in C++, using the OpenLB * library * * Copyright (C) 2011-2016 Thomas Henn, Mathias J. Krause, * Marie-Luise Maier * E-mail contact: info@openlb.net * The most recent release of OpenLB can be downloaded at * * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public * License along with this program; if not, write to the Free * Software Foundation, Inc., 51 Franklin Street, Fifth Floor, * Boston, MA 02110-1301, USA. */ /* bifurcation3d.cpp: * This example examines a steady particulate flow past a bifurcation. At the inlet, * an inflow condition with grad_n u = 0 and rho = 1 is implemented. * At both outlets, a Poiseuille profile is imposed on the velocity. * After a start time, particles are put into the bifurcation at the * inlet and experience a stokes drag force. * * A publication using the same geometry can be found here: * http://link.springer.com/chapter/10.1007/978-3-642-36961-2_5 * * */ #include "olb3D.h" #ifndef OLB_PRECOMPILED // Unless precompiled version is used, #include "olb3D.hh" // include full template code; #endif using namespace std; using namespace olb; using namespace olb::descriptors; using namespace olb::graphics; using namespace olb::util; typedef double T; #define DESCRIPTOR D3Q19<> #define PARTICLE Particle3D #ifndef M_PI #define M_PI 3.14159265358979323846 #endif const T Re = 50; // Reynolds number const int N = 19; // resolution of the model const T radius = 1.5e-4; // particles radius const T partRho = 998.2; //particles density const T fluidMaxPhysT = T( 5 ); // max. fluid simulation time in s, SI unit const T particleMaxPhysT = T( 10 ); // max. particle simulation time in s, SI unit const int noOfParticles = 1000; // total number of inserted particles // center of inflow and outflow regions [m] Vector inletCenter( T(), T(), 0.0786395 ); Vector outletCenter0( -0.0235929682287551, -0.000052820468762797, -0.021445708949909 ); Vector outletCenter1( 0.0233643529416147, 0.00000212439067050152, -0.0211994104877918 ); // radii of inflow and outflow regions [m] T inletRadius = 0.00999839; T outletRadius0 = 0.007927; T outletRadius1 = 0.00787134; // normals of inflow and outflow regions Vector inletNormal( T(), T(), T( -1 ) ); Vector outletNormal0( 0.505126, -0.04177, 0.862034 ); Vector outletNormal1( -0.483331, -0.0102764, 0.875377 ); void prepareGeometry( UnitConverter const& converter, IndicatorF3D& indicator, STLreader& stlReader, SuperGeometry3D& superGeometry ) { OstreamManager clout( std::cout, "prepareGeometry" ); clout << "Prepare Geometry ..." << std::endl; superGeometry.rename( 0, 2, indicator ); superGeometry.rename( 2, 1, stlReader ); superGeometry.clean(); // rename the material at the inlet IndicatorCircle3D inletCircle( inletCenter, inletNormal, inletRadius ); IndicatorCylinder3D inlet( inletCircle, 2 * converter.getConversionFactorLength() ); superGeometry.rename( 2, 3, 1, inlet ); // rename the material at the outlet0 IndicatorCircle3D outletCircle0( outletCenter0, outletNormal0, 0.95 * outletRadius0 ); IndicatorCylinder3D outlet0( outletCircle0, 4 * converter.getConversionFactorLength() ); superGeometry.rename( 2, 4, outlet0 ); // rename the material at the outlet1 IndicatorCircle3D outletCircle1( outletCenter1, outletNormal1, 0.95 * outletRadius1 ); IndicatorCylinder3D outlet1( outletCircle1, 4 * converter.getConversionFactorLength() ); superGeometry.rename( 2, 5, outlet1 ); superGeometry.clean(); superGeometry.innerClean( true ); superGeometry.checkForErrors(); superGeometry.print(); clout << "Prepare Geometry ... OK" << std::endl; return; } void prepareLattice( SuperLattice3D& sLattice, UnitConverter const& converter, Dynamics& bulkDynamics, sOnLatticeBoundaryCondition3D& bc, sOffLatticeBoundaryCondition3D& offBc, STLreader& stlReader, SuperGeometry3D& superGeometry ) { OstreamManager clout( std::cout, "prepareLattice" ); clout << "Prepare Lattice ..." << std::endl; const T omega = converter.getLatticeRelaxationFrequency(); // Material=0 -->do nothing sLattice.defineDynamics( superGeometry, 0, &instances::getNoDynamics() ); // Material=1 -->bulk dynamics sLattice.defineDynamics( superGeometry, 1, &bulkDynamics ); // Material=2 -->bounce back sLattice.defineDynamics( superGeometry, 2, &instances::getBounceBack() ); // Material=3 -->bulk dynamics (inflow) sLattice.defineDynamics( superGeometry, 3, &bulkDynamics ); // Material=4 -->bulk dynamics (outflow) sLattice.defineDynamics( superGeometry, 4, &bulkDynamics ); sLattice.defineDynamics( superGeometry, 5, &bulkDynamics ); // Setting of the boundary conditions bc.addPressureBoundary( superGeometry, 3, omega ); bc.addVelocityBoundary( superGeometry, 4, omega ); bc.addVelocityBoundary( superGeometry, 5, omega ); clout << "Prepare Lattice ... OK" << std::endl; return; } // Generates a slowly increasing sinuidal inflow for the first iTMax timesteps void setBoundaryValues( SuperLattice3D& sLattice, UnitConverter const& converter, int iT, T maxPhysT, SuperGeometry3D& superGeometry ) { OstreamManager clout( std::cout, "setBoundaryValues" ); // No of time steps for smooth start-up int iTmaxStart = converter.getLatticeTime( 0.8*maxPhysT ); int iTperiod = 100; // amount of timesteps when new boundary conditions are reset if ( iT == 0 ) { AnalyticalConst3D rhoF( 1 ); std::vector velocity( 3, T() ); AnalyticalConst3D uF( velocity ); sLattice.iniEquilibrium( superGeometry, 1, rhoF, uF ); sLattice.iniEquilibrium( superGeometry, 2, rhoF, uF ); sLattice.iniEquilibrium( superGeometry, 3, rhoF, uF ); sLattice.iniEquilibrium( superGeometry, 4, rhoF, uF ); sLattice.iniEquilibrium( superGeometry, 5, rhoF, uF ); sLattice.defineRhoU( superGeometry, 1, rhoF, uF ); sLattice.defineRhoU( superGeometry, 2, rhoF, uF ); sLattice.defineRhoU( superGeometry, 3, rhoF, uF ); sLattice.defineRhoU( superGeometry, 4, rhoF, uF ); sLattice.defineRhoU( superGeometry, 5, rhoF, uF ); // Make the lattice ready for simulation sLattice.initialize(); } else if ( iT <= iTmaxStart && iT % iTperiod == 0 ) { SinusStartScale startScale( iTmaxStart, T( 1 ) ); int iTvec[1] = { iT }; T frac[1] = { T( 0 ) }; startScale( frac, iTvec ); T maxVelocity = frac[0] * converter.getCharLatticeVelocity() * 3. / 4. * std::pow( inletRadius, 2 ) / std::pow( outletRadius0, 2 ); CirclePoiseuille3D poiseuilleU4( outletCenter0[0], outletCenter0[1], outletCenter0[2], outletNormal0[0], outletNormal0[1], outletNormal0[2], outletRadius0 * 0.95, -maxVelocity ); CirclePoiseuille3D poiseuilleU5( outletCenter1[0], outletCenter1[1], outletCenter1[2], outletNormal1[0], outletNormal1[1], outletNormal1[2], outletRadius1 * 0.95, -maxVelocity ); sLattice.defineU( superGeometry, 4, poiseuilleU4 ); sLattice.defineU( superGeometry, 5, poiseuilleU5 ); } } // Computes the pressure drop between voxels before and after the cylinder bool getResults( SuperLattice3D& sLattice, UnitConverter const& converter, int iT, int iTperiod, SuperGeometry3D& superGeometry, Timer& fluidTimer, STLreader& stlReader, SuperParticleSystem3D& supParticleSystem, T radii, T partRho, Timer& particleTimer, SuperParticleSysVtuWriter& supParticleWriter, bool fluidExists) { OstreamManager clout( std::cout, "getResults" ); SuperVTMwriter3D vtmWriter( "bifurcation3d" ); SuperVTMwriter3D vtmWriterStartTime( "startingTimeBifurcation3d" ); SuperLatticePhysVelocity3D velocity( sLattice, converter ); SuperLatticePhysPressure3D pressure( sLattice, converter ); vtmWriter.addFunctor( velocity ); vtmWriter.addFunctor( pressure ); vtmWriterStartTime.addFunctor( velocity ); vtmWriterStartTime.addFunctor( pressure ); int fluidMaxT = converter.getLatticeTime( fluidMaxPhysT ); if ( iT == 0 ) { SuperLatticeGeometry3D geometry( sLattice, superGeometry ); SuperLatticeCuboid3D cuboid( sLattice ); SuperLatticeRank3D rank( sLattice ); vtmWriter.write( geometry ); vtmWriter.write( cuboid ); vtmWriter.write( rank ); vtmWriter.createMasterFile(); vtmWriterStartTime.createMasterFile(); // Print some output of the chosen simulation setup clout << "N=" << N <<"; maxTimeSteps(fluid)=" << converter.getLatticeTime( fluidMaxPhysT ) << "; noOfCuboid=" << superGeometry.getCuboidGeometry().getNc() << "; Re=" << Re << "; noOfParticles=" << noOfParticles << "; maxTimeSteps(particle)=" << converter.getLatticeTime( particleMaxPhysT ) << "; St=" << ( 2.*partRho*radius*radius*converter.getCharPhysVelocity() ) / ( 9.*converter.getPhysViscosity()*converter.getPhysDensity()*converter.getCharPhysLength() ) << std::endl; } // Writes the vtk and gif files if ( iT % iTperiod == 0 ) { if ( !fluidExists && iT <= fluidMaxT ) { vtmWriterStartTime.write(iT); SuperEuklidNorm3D normVel( velocity ); BlockReduction3D2D planeReduction( normVel, {0, -1, 0}, 600, BlockDataSyncMode::ReduceOnly ); // write output as JPEG heatmap::write(planeReduction, iT); } if (iT > fluidMaxT) { // only write vtk-files after the fluid calculation is finished vtmWriter.write(iT - fluidMaxT); } } // Writes output on the console for the fluid phase if (iT < converter.getLatticeTime( fluidMaxPhysT ) && iT%iTperiod == 0 ) { // Timer statics fluidTimer.update( iT ); fluidTimer.printStep(); // Lattice statistics sLattice.getStatistics().print( iT, converter.getPhysTime( iT ) ); // Flux at the inlet and outlet regions const std::vector materials = { 1, 3, 4, 5 }; IndicatorCircle3D inlet( inletCenter + 2. * converter.getConversionFactorLength() * inletNormal, inletNormal, inletRadius + 2. * converter.getConversionFactorLength() ); SuperPlaneIntegralFluxVelocity3D vFluxInflow( sLattice, converter, superGeometry, inlet, materials ); vFluxInflow.print( "inflow", "ml/s" ); SuperPlaneIntegralFluxPressure3D pFluxInflow( sLattice, converter, superGeometry, inlet, materials ); pFluxInflow.print( "inflow", "N", "Pa" ); IndicatorCircle3D outlet0( outletCenter0 + 2. * converter.getConversionFactorLength() * outletNormal0, outletNormal0, outletRadius0 + 2. * converter.getConversionFactorLength() ); SuperPlaneIntegralFluxVelocity3D vFluxOutflow0( sLattice, converter, superGeometry, outlet0, materials ); vFluxOutflow0.print( "outflow0", "ml/s" ); SuperPlaneIntegralFluxPressure3D pFluxOutflow0( sLattice, converter, superGeometry, outlet0, materials ); pFluxOutflow0.print( "outflow0", "N", "Pa" ); IndicatorCircle3D outlet1( outletCenter1 + 2. * converter.getConversionFactorLength() * outletNormal1, outletNormal1, outletRadius1 + 2. * converter.getConversionFactorLength() ); SuperPlaneIntegralFluxVelocity3D vFluxOutflow1( sLattice, converter, superGeometry, outlet1, materials ); vFluxOutflow1.print( "outflow1", "ml/s" ); SuperPlaneIntegralFluxPressure3D pFluxOutflow1( sLattice, converter, superGeometry, outlet1, materials ); pFluxOutflow1.print( "outflow1", "N", "Pa" ); } // Writes output on the console for the fluid phase if ( iT >= converter.getLatticeTime( fluidMaxPhysT ) && (iT%iTperiod == 0 || iT == converter.getLatticeTime( fluidMaxPhysT )) ) { particleTimer.print( iT - fluidMaxT ); // console output number of particles at different material numbers mat supParticleSystem.print( {1,2,3,4,5} ); // console output of escape (E), capture (C) rate for material numbers mat supParticleSystem.captureEscapeRate( {4,5} ); // only write vtk-files after the fluid calculation is finished supParticleWriter.write( iT - fluidMaxT ); // true as long as certain amount of active particles if ( supParticleSystem.globalNumOfActiveParticles() < 0.001 * noOfParticles && iT > 0.9*converter.getLatticeTime( fluidMaxPhysT + particleMaxPhysT ) ) { return false; } } return true; } int main( int argc, char* argv[] ) { // === 1st Step: Initialization === olbInit( &argc, &argv ); singleton::directories().setOutputDir( "./tmp/" ); OstreamManager clout( std::cout, "main" ); UnitConverterFromResolutionAndRelaxationTime const converter( int {N}, // resolution: number of voxels per charPhysL (T) 0.557646, // latticeRelaxationTime: relaxation time, have to be greater than 0.5! (T) inletRadius*2., // charPhysLength: reference length of simulation geometry (T) Re*1.5e-5/( inletRadius*2 ), // charPhysVelocity: maximal/highest expected velocity during simulation in __m / s__ (T) 1.5e-5, // physViscosity: physical kinematic viscosity in __m^2 / s__ (T) 1.225 // physDensity: physical density in __kg / m^3__ ); // Prints the converter log as console output converter.print(); // Writes the converter log in a file converter.write("bifurcation3d"); // === 2nd Step: Prepare Geometry === STLreader stlReader( "../bifurcation3d.stl", converter.getConversionFactorLength() ); IndicatorLayer3D extendedDomain( stlReader, converter.getConversionFactorLength() ); // Instantiation of an empty cuboidGeometry int noOfCuboids = std::max( 16, 4 * singleton::mpi().getSize() ); CuboidGeometry3D cuboidGeometry( extendedDomain, converter.getConversionFactorLength(), noOfCuboids ); // Instantiation of an empty loadBalancer HeuristicLoadBalancer loadBalancer( cuboidGeometry ); // Default instantiation of superGeometry SuperGeometry3D superGeometry( cuboidGeometry, loadBalancer, 2 ); prepareGeometry( converter, extendedDomain, stlReader, superGeometry ); // === 3rd Step: Prepare Lattice === SuperLattice3D sLattice( superGeometry ); BGKdynamics bulkDynamics( converter.getLatticeRelaxationFrequency(), instances::getBulkMomenta() ); sOnLatticeBoundaryCondition3D sBoundaryCondition( sLattice ); createInterpBoundaryCondition3D( sBoundaryCondition ); sOffLatticeBoundaryCondition3D sOffBoundaryCondition( sLattice ); createBouzidiBoundaryCondition3D( sOffBoundaryCondition ); prepareLattice( sLattice, converter, bulkDynamics, sBoundaryCondition, sOffBoundaryCondition, stlReader, superGeometry ); // === 3.1 Step: Particles === clout << "Prepare Particles ..." << std::endl; // SuperParticleSystems3D SuperParticleSystem3D supParticleSystem( superGeometry ); // define which properties are to be written in output data SuperParticleSysVtuWriter supParticleWriter( supParticleSystem, "particles", SuperParticleSysVtuWriter::particleProperties:: velocity | SuperParticleSysVtuWriter::particleProperties::mass | SuperParticleSysVtuWriter::particleProperties::radius | SuperParticleSysVtuWriter::particleProperties::active ); SuperLatticeInterpPhysVelocity3D getVel( sLattice, converter ); auto stokesDragForce = make_shared < StokesDragForce3D > ( getVel, converter ); // material numbers where particles should be reflected std::set boundMaterial = { 2, 4, 5}; auto materialBoundary = make_shared < MaterialBoundary3D > ( superGeometry, boundMaterial ); supParticleSystem.addForce( stokesDragForce ); supParticleSystem.addBoundary( materialBoundary ); supParticleSystem.setOverlap( 2. * converter.getConversionFactorLength() ); // particles generation at inlet3 Vector c( inletCenter ); c[2] = 0.074; IndicatorCircle3D inflowCircle( c, inletNormal, inletRadius - converter.getConversionFactorLength() * 2.5 ); IndicatorCylinder3D inletCylinder( inflowCircle, 0.01 * converter.getConversionFactorLength() ); supParticleSystem.addParticle( inletCylinder, 4. / 3. * M_PI * std::pow( radius, 3 ) * partRho, radius, noOfParticles ); clout << "Prepare Particles ... OK" << std::endl; // === 4th Step: Main Loop with Timer === Timer fluidTimer( converter.getLatticeTime( fluidMaxPhysT ), superGeometry.getStatistics().getNvoxel() ); Timer particleTimer( converter.getLatticeTime( particleMaxPhysT ), noOfParticles ); fluidTimer.start(); int iT = 0; // amount of timesteps when getResults rewrites data int iTperiod = converter.getLatticeTime( .2 ); bool fluidExists = true; // checks whether there is already data of the fluid from an earlier calculation if ( !( sLattice.load( "fluidSolution" ) ) ) { fluidExists = false; // if there is no data available, it is generated for ( ; iT <= converter.getLatticeTime( fluidMaxPhysT ); ++iT ) { // during run up time boundary values are set, collide and stream step, // results of fluid, afterwards only particles are simulated setBoundaryValues( sLattice, converter, iT, fluidMaxPhysT, superGeometry ); sLattice.collideAndStream(); getResults( sLattice, converter, iT, iTperiod, superGeometry, fluidTimer, stlReader, supParticleSystem, radius, partRho, particleTimer, supParticleWriter, fluidExists ); } fluidTimer.stop(); fluidTimer.printSummary(); sLattice.communicate(); // calculated results are written in a file sLattice.save( "fluidSolution" ); } // if there exists already data of the fluid from an earlier calculation, this is used else { iT = converter.getLatticeTime( fluidMaxPhysT ); getResults( sLattice, converter, iT, iTperiod, superGeometry, fluidTimer, stlReader, supParticleSystem, radius, partRho, particleTimer, supParticleWriter, fluidExists ); } // after the fluid calculation, particle simulation starts supParticleSystem.setVelToFluidVel( getVel ); particleTimer.start(); for ( ; iT <= converter.getLatticeTime( fluidMaxPhysT + particleMaxPhysT ); ++iT ) { // particles simulation starts after run up time is over supParticleSystem.simulate( converter.getConversionFactorTime() ); if ( !getResults( sLattice, converter, iT, iTperiod, superGeometry, fluidTimer, stlReader, supParticleSystem, radius, partRho, particleTimer, supParticleWriter, fluidExists) ) { break; } } particleTimer.stop(); particleTimer.printSummary(); }