/* Lattice Boltzmann sample, written in C++, using the OpenLB
* library
*
* Copyright (C) 2019 Sam Avis
* 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.
*/
/* microfluidics2d.cpp:
* This example shows a microfluidic channel creating droplets of
* two fluid components. Poiseuille velocity profiles are imposed
* at the various channel inlets, while a constant density outlet
* is imposed at the end of the channel to allow the droplets to
* exit the simulation.
*
* This example demonstrates the use of three fluid components
* with the free energy model. It also shows the use of open
* boundary conditions, specifically velocity inlet and density
* outlet boundaries.
*/
#include "olb2D.h"
#include "olb2D.hh" // use only generic version!
#include
#include
#include
using namespace olb;
using namespace olb::descriptors;
using namespace olb::graphics;
using namespace std;
typedef double T;
#define DESCRIPTOR D2Q9
// Parameters for the simulation setup
const int N = 100;
const T nx = 800.;
const T ny = 100.;
const T dx = ny / N;
const T inSize = 175.;
const T xl1 = inSize * 2./7.;
const T yl1 = ny / 4.;
const T xl2 = inSize / 7.;
const T yl2 = ny;
const T xl3 = inSize * 3./7.;
const T yl3 = ny / 4.;
const T xl4 = inSize / 7.;
const T yl4 = ny;
const T xl5 = nx - inSize;
const T yl5 = ny / 2.;
const T inlet1Velocity = 0.00056; // [lattice units]
const T inlet2Velocity = 0.00055; // [lattice units]
const T inlet3Velocity = 0.0014; // [lattice units]
const T outletDensity = 1.; // [lattice units]
const T alpha = 1.; // Interfacial width [lattice units]
const T kappa1 = 0.0132; // For surface tensions [lattice units]
const T kappa2 = 0.0012; // For surface tensions [lattice units]
const T kappa3 = 0.0013; // For surface tensions [lattice units]
const T gama = 1.; // For mobility of interfaces [lattice units]
const T h1 = 0.; // Contact angle 90 degrees [lattice units]
const T h2 = 0.; // Contact angle 90 degrees [lattice units]
const T h3 = 0.; // Contact angle 90 degrees [lattice units]
const int maxIter = 1000000;
const int vtkIter = 1000;
const int statIter = 2000;
void prepareGeometry( SuperGeometry2D& superGeometry ) {
OstreamManager clout( std::cout,"prepareGeometry" );
clout << "Prepare Geometry ..." << std::endl;
std::shared_ptr> section1 = std::make_shared>( xl1, yl1, std::vector{xl1/2., ny/2.} );
std::shared_ptr> section2 = std::make_shared>( xl2, yl2, std::vector{xl1 + xl2/2., ny/2.} );
std::shared_ptr> section3 = std::make_shared>( xl3, yl3, std::vector{xl1 + xl2 + xl3/2., ny/2.} );
std::shared_ptr> section4 = std::make_shared>( xl4, yl4, std::vector{xl1 + xl2 + xl3 + xl4/2., ny/2.} );
std::shared_ptr> section5 = std::make_shared>( xl5, yl5, std::vector{xl1 + xl2 + xl3 + xl4 + xl5/2., ny/2.} );
IndicatorIdentity2D channel( section1 + section2 + section3 + section4 + section5 );
superGeometry.rename( 0, 2, channel );
superGeometry.rename( 2,1,1,1 );
// Inlets and outlet
IndicatorCuboid2D inlet1 ( dx, yl1, {0., ny/2.} );
IndicatorCuboid2D inlet21( xl2 - dx, dx, {xl1 + xl2/2., 0.} );
IndicatorCuboid2D inlet22( xl2 - dx, dx, {xl1 + xl2/2., ny} );
IndicatorCuboid2D inlet31( xl4 - dx, dx, {xl1 + xl2 + xl3 + xl4/2., 0.} );
IndicatorCuboid2D inlet32( xl4 - dx, dx, {xl1 + xl2 + xl3 + xl4/2., ny} );
IndicatorCuboid2D outlet( dx, yl5, {nx, ny/2.} );
superGeometry.rename( 2, 3, 1, inlet1 );
superGeometry.rename( 2, 4, 1, inlet21 );
superGeometry.rename( 2, 5, 1, inlet22 );
superGeometry.rename( 2, 6, 1, inlet31 );
superGeometry.rename( 2, 7, 1, inlet32 );
superGeometry.rename( 2, 8, 1, outlet );
superGeometry.innerClean();
superGeometry.checkForErrors();
superGeometry.print();
clout << "Prepare Geometry ... OK" << std::endl;
}
void prepareLattice( SuperLattice2D& sLattice1,
SuperLattice2D& sLattice2,
SuperLattice2D& sLattice3,
Dynamics& bulkDynamics1,
Dynamics& bulkDynamics2,
Dynamics& bulkDynamics3,
sOnLatticeBoundaryCondition2D& sOnBC1,
sOnLatticeBoundaryCondition2D& sOnBC2,
sOnLatticeBoundaryCondition2D& sOnBC3,
UnitConverter& converter,
SuperGeometry2D& superGeometry ) {
OstreamManager clout( std::cout,"prepareLattice" );
clout << "Prepare Lattice ..." << std::endl;
// define lattice dynamics
sLattice1.defineDynamics( superGeometry, 0, &instances::getNoDynamics() );
sLattice2.defineDynamics( superGeometry, 0, &instances::getNoDynamics() );
sLattice3.defineDynamics( superGeometry, 0, &instances::getNoDynamics() );
sLattice1.defineDynamics( superGeometry, 1, &bulkDynamics1 );
sLattice2.defineDynamics( superGeometry, 1, &bulkDynamics2 );
sLattice3.defineDynamics( superGeometry, 1, &bulkDynamics3 );
sLattice1.defineDynamics( superGeometry, 2, &instances::getNoDynamics() );
sLattice2.defineDynamics( superGeometry, 2, &instances::getNoDynamics() );
sLattice3.defineDynamics( superGeometry, 2, &instances::getNoDynamics() );
sLattice1.defineDynamics( superGeometry, 3, &instances::getNoDynamics() );
sLattice2.defineDynamics( superGeometry, 3, &instances::getNoDynamics() );
sLattice3.defineDynamics( superGeometry, 3, &instances::getNoDynamics() );
sLattice1.defineDynamics( superGeometry, 4, &instances::getNoDynamics() );
sLattice2.defineDynamics( superGeometry, 4, &instances::getNoDynamics() );
sLattice3.defineDynamics( superGeometry, 4, &instances::getNoDynamics() );
sLattice1.defineDynamics( superGeometry, 5, &instances::getNoDynamics() );
sLattice2.defineDynamics( superGeometry, 5, &instances::getNoDynamics() );
sLattice3.defineDynamics( superGeometry, 5, &instances::getNoDynamics() );
sLattice1.defineDynamics( superGeometry, 6, &instances::getNoDynamics() );
sLattice2.defineDynamics( superGeometry, 6, &instances::getNoDynamics() );
sLattice3.defineDynamics( superGeometry, 6, &instances::getNoDynamics() );
sLattice1.defineDynamics( superGeometry, 7, &instances::getNoDynamics() );
sLattice2.defineDynamics( superGeometry, 7, &instances::getNoDynamics() );
sLattice3.defineDynamics( superGeometry, 7, &instances::getNoDynamics() );
sLattice1.defineDynamics( superGeometry, 8, &instances::getNoDynamics() );
sLattice2.defineDynamics( superGeometry, 8, &instances::getNoDynamics() );
sLattice3.defineDynamics( superGeometry, 8, &instances::getNoDynamics() );
// add wall boundary
sOnBC1.addFreeEnergyWallBoundary( superGeometry, 2, alpha, kappa1, kappa2, kappa3, h1, h2, h3, 1 );
sOnBC2.addFreeEnergyWallBoundary( superGeometry, 2, alpha, kappa1, kappa2, kappa3, h1, h2, h3, 2 );
sOnBC3.addFreeEnergyWallBoundary( superGeometry, 2, alpha, kappa1, kappa2, kappa3, h1, h2, h3, 3 );
// add inlet boundaries
T omega = converter.getLatticeRelaxationFrequency();
auto inlet1Indicator = superGeometry.getMaterialIndicator(3);
sOnBC1.addFreeEnergyInletBoundary( inlet1Indicator, omega, "velocity", 1 );
sOnBC2.addFreeEnergyInletBoundary( inlet1Indicator, omega, "velocity", 2 );
sOnBC3.addFreeEnergyInletBoundary( inlet1Indicator, omega, "velocity", 3 );
auto inlet2Indicator = superGeometry.getMaterialIndicator({4, 5});
sOnBC1.addFreeEnergyInletBoundary( inlet2Indicator, omega, "velocity", 1 );
sOnBC2.addFreeEnergyInletBoundary( inlet2Indicator, omega, "velocity", 2 );
sOnBC3.addFreeEnergyInletBoundary( inlet2Indicator, omega, "velocity", 3 );
auto inlet3Indicator = superGeometry.getMaterialIndicator({6, 7});
sOnBC1.addFreeEnergyInletBoundary( inlet3Indicator, omega, "velocity", 1 );
sOnBC2.addFreeEnergyInletBoundary( inlet3Indicator, omega, "velocity", 2 );
sOnBC3.addFreeEnergyInletBoundary( inlet3Indicator, omega, "velocity", 3 );
// add outlet boundary
auto outletIndicator = superGeometry.getMaterialIndicator(8);
sOnBC1.addFreeEnergyOutletBoundary( outletIndicator, omega, "density", 1 );
sOnBC2.addFreeEnergyOutletBoundary( outletIndicator, omega, "density", 2 );
sOnBC3.addFreeEnergyOutletBoundary( outletIndicator, omega, "density", 3 );
// bulk initial conditions
std::vector v( 2,T() );
AnalyticalConst2D zeroVelocity( v );
AnalyticalConst2D zero ( 0. );
AnalyticalConst2D one ( 1. );
SmoothIndicatorCuboid2D section1( {xl1/2., ny/2.}, xl1+dx, ny, 0. );
SmoothIndicatorCuboid2D section2( {xl1 + (xl2 + xl3)/2., ny/2.}, xl2 + xl3, ny, 0. );
AnalyticalIdentity2D c1( section1 );
AnalyticalIdentity2D c2( section2 );
AnalyticalIdentity2D rho( one );
AnalyticalIdentity2D phi( c1 - c2 );
AnalyticalIdentity2D psi( rho - c1 - c2 );
auto allIndicator = superGeometry.getMaterialIndicator({1, 2, 3, 4, 5, 6});
sLattice1.iniEquilibrium( allIndicator, rho, zeroVelocity );
sLattice2.iniEquilibrium( allIndicator, phi, zeroVelocity );
sLattice3.iniEquilibrium( allIndicator, psi, zeroVelocity );
// inlet boundary conditions
Poiseuille2D inlet1U( superGeometry, 3, 1.5*inlet1Velocity, 0. );
sLattice1.defineU( inlet1Indicator, inlet1U );
sLattice2.defineRho( inlet1Indicator, phi );
sLattice3.defineRho( inlet1Indicator, psi );
Poiseuille2D inlet21U( superGeometry, 4, 1.5*inlet2Velocity, 0. );
Poiseuille2D inlet22U( superGeometry, 5, 1.5*inlet2Velocity, 0. );
sLattice1.defineU( superGeometry, 4, inlet21U );
sLattice1.defineU( superGeometry, 5, inlet22U );
sLattice2.defineRho( inlet2Indicator, phi );
sLattice3.defineRho( inlet2Indicator, psi );
Poiseuille2D inlet31U( superGeometry, 6, 1.5*inlet3Velocity, 0. );
Poiseuille2D inlet32U( superGeometry, 7, 1.5*inlet3Velocity, 0. );
sLattice1.defineU( superGeometry, 6, inlet31U );
sLattice1.defineU( superGeometry, 7, inlet32U );
sLattice2.defineRho( inlet3Indicator, phi );
sLattice3.defineRho( inlet3Indicator, psi );
// outlet initial / boundary conditions
AnalyticalConst2D rhoOutlet( outletDensity );
AnalyticalIdentity2D phiOutlet( zero );
AnalyticalIdentity2D psiOutlet( rhoOutlet );
sLattice1.defineRho( outletIndicator, rhoOutlet );
sLattice2.defineRho( outletIndicator, phiOutlet );
sLattice3.defineRho( outletIndicator, psiOutlet );
// initialise lattices
sLattice1.initialize();
sLattice2.initialize();
sLattice3.initialize();
sLattice1.communicate();
sLattice2.communicate();
sLattice3.communicate();
clout << "Prepare Lattice ... OK" << std::endl;
}
void prepareCoupling(SuperLattice2D& sLattice1,
SuperLattice2D& sLattice2,
SuperLattice2D& sLattice3,
SuperGeometry2D& superGeometry) {
OstreamManager clout( std::cout,"prepareCoupling" );
clout << "Add lattice coupling" << endl;
// Bulk couplings
FreeEnergyChemicalPotentialGenerator2D coupling2( alpha, kappa1, kappa2, kappa3 );
FreeEnergyForceGenerator2D coupling3;
// Inlet / outlet couplings
FreeEnergyDensityOutletGenerator2D coupling1( outletDensity );
FreeEnergyInletOutletGenerator2D coupling4;
// The DensityOutlet coupling must be added to the first lattice and come before the ChemicalPotential coupling
// The InletOutlet couplings must be added to the second lattice and come after the Force coupling.
sLattice1.addLatticeCoupling( superGeometry, 8, coupling1, {&sLattice2, &sLattice3} );
sLattice1.addLatticeCoupling( superGeometry, 1, coupling2, {&sLattice2, &sLattice3} );
sLattice2.addLatticeCoupling( superGeometry, 1, coupling3, {&sLattice1, &sLattice3} );
sLattice2.addLatticeCoupling( superGeometry, 3, coupling4, {&sLattice1, &sLattice3} );
sLattice2.addLatticeCoupling( superGeometry, 4, coupling4, {&sLattice1, &sLattice3} );
sLattice2.addLatticeCoupling( superGeometry, 5, coupling4, {&sLattice1, &sLattice3} );
sLattice2.addLatticeCoupling( superGeometry, 6, coupling4, {&sLattice1, &sLattice3} );
sLattice2.addLatticeCoupling( superGeometry, 7, coupling4, {&sLattice1, &sLattice3} );
sLattice2.addLatticeCoupling( superGeometry, 8, coupling4, {&sLattice1, &sLattice3} );
clout << "Add lattice coupling ... OK!" << endl;
}
void getResults( SuperLattice2D& sLattice1,
SuperLattice2D& sLattice2,
SuperLattice2D& sLattice3, int iT,
SuperGeometry2D& superGeometry, Timer& timer,
UnitConverter converter) {
OstreamManager clout( std::cout,"getResults" );
SuperVTMwriter2D vtmWriter( "microFluidics2d" );
if ( iT==0 ) {
// Writes the geometry, cuboid no. and rank no. as vti file for visualization
SuperLatticeGeometry2D geometry( sLattice1, superGeometry );
SuperLatticeCuboid2D cuboid( sLattice1 );
SuperLatticeRank2D rank( sLattice1 );
vtmWriter.write( geometry );
vtmWriter.write( cuboid );
vtmWriter.write( rank );
vtmWriter.createMasterFile();
}
// Get statistics
if ( iT%statIter==0 ) {
// Timer console output
timer.update( iT );
timer.printStep();
sLattice1.getStatistics().print( iT, converter.getPhysTime(iT) );
sLattice2.getStatistics().print( iT, converter.getPhysTime(iT) );
sLattice3.getStatistics().print( iT, converter.getPhysTime(iT) );
}
// Writes the VTK files
if ( iT%vtkIter==0 ) {
SuperLatticeVelocity2D velocity( sLattice1 );
SuperLatticeDensity2D density1( sLattice1 );
density1.getName() = "rho";
SuperLatticeDensity2D density2( sLattice2 );
density2.getName() = "phi";
SuperLatticeDensity2D density3( sLattice3 );
density3.getName() = "density-fluid-3";
AnalyticalConst2D half_( 0.5 );
SuperLatticeFfromAnalyticalF2D half(half_, sLattice1);
SuperIdentity2D c1 (half*(density1+density2-density3));
c1.getName() = "density-fluid-1";
SuperIdentity2D c2 (half*(density1-density2-density3));
c2.getName() = "density-fluid-2";
vtmWriter.addFunctor( velocity );
vtmWriter.addFunctor( density1 );
vtmWriter.addFunctor( density2 );
vtmWriter.addFunctor( density3 );
vtmWriter.addFunctor( c1 );
vtmWriter.addFunctor( c2 );
vtmWriter.write( iT );
}
}
int main( int argc, char *argv[] ) {
// === 1st Step: Initialization ===
olbInit( &argc, &argv );
singleton::directories().setOutputDir( "./tmp/" );
OstreamManager clout( std::cout,"main" );
UnitConverterFromResolutionAndRelaxationTime converter(
(T) N, // resolution
(T) 1., // lattice relaxation time (tau)
(T) ny, // charPhysLength: reference length of simulation geometry
(T) 1.e-6, // charPhysVelocity: maximal/highest expected velocity during simulation in __m / s__
(T) 0.1, // physViscosity: physical kinematic viscosity in __m^2 / s__
(T) 1. // physDensity: physical density in __kg / m^3__
);
// Prints the converter log as console output
converter.print();
// === 2nd Step: Prepare Geometry ===
std::vector extend = { nx, ny };
std::vector origin = { 0, 0 };
IndicatorCuboid2D cuboid(extend,origin);
#ifdef PARALLEL_MODE_MPI
CuboidGeometry2D cGeometry( cuboid, converter.getPhysDeltaX(), singleton::mpi().getSize() );
#else
CuboidGeometry2D cGeometry( cuboid, converter.getPhysDeltaX() );
#endif
// Instantiation of loadbalancer
HeuristicLoadBalancer loadBalancer( cGeometry );
loadBalancer.print();
// Instantiation of superGeometry
SuperGeometry2D superGeometry( cGeometry,loadBalancer );
prepareGeometry( superGeometry );
// === 3rd Step: Prepare Lattice ===
SuperLattice2D sLattice1( superGeometry );
SuperLattice2D sLattice2( superGeometry );
SuperLattice2D sLattice3( superGeometry );
ForcedBGKdynamics bulkDynamics1 (
converter.getLatticeRelaxationFrequency(),
instances::getBulkMomenta() );
FreeEnergyBGKdynamics bulkDynamics23 (
converter.getLatticeRelaxationFrequency(), gama,
instances::getBulkMomenta() );
sOnLatticeBoundaryCondition2D sOnBC1( sLattice1 );
sOnLatticeBoundaryCondition2D sOnBC2( sLattice2 );
sOnLatticeBoundaryCondition2D sOnBC3( sLattice3 );
createLocalBoundaryCondition2D (sOnBC1);
createLocalBoundaryCondition2D (sOnBC2);
createLocalBoundaryCondition2D (sOnBC3);
prepareLattice( sLattice1, sLattice2, sLattice3, bulkDynamics1, bulkDynamics23,
bulkDynamics23, sOnBC1, sOnBC2, sOnBC3, converter, superGeometry );
prepareCoupling( sLattice1, sLattice2, sLattice3, superGeometry );
SuperExternal2D sExternal1 (superGeometry, sLattice1, sLattice1.getOverlap() );
SuperExternal2D sExternal2 (superGeometry, sLattice2, sLattice2.getOverlap() );
SuperExternal2D sExternal3 (superGeometry, sLattice3, sLattice3.getOverlap() );
// === 4th Step: Main Loop with Timer ===
int iT = 0;
clout << "starting simulation..." << endl;
Timer timer( maxIter, superGeometry.getStatistics().getNvoxel() );
timer.start();
for ( iT=0; iT