/* Lattice Boltzmann sample, written in C++, using the OpenLB
* library
*
* Copyright (C) 2018 Sascha Janz, 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.
*/
/* magneticParticles3d.cpp:
* High-gradient magnetic separation is a method
* to separate ferromagnetic particles from a suspension.
* The simulation shows the deposition of magnetic particles
* on a single magnetized wire and models
* the magnetic separation step of the complete process.
*/
#include "olb3D.h"
#ifndef OLB_PRECOMPILED // Unless precompiled version is used
#include "olb3D.hh" // Include full template code
#endif
using namespace olb;
using namespace olb::descriptors;
using namespace olb::graphics;
using namespace olb::util;
using namespace std;
#define DESCRIPTOR D3Q19<>
#define PARTICLE MagneticParticle3D
#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif
typedef double T;
int iT = 0;
// simulation geometry dimensions
T geometryLengthX = 3.e-3 ; // length x-dircetion [m]
T geometryLengthY = 2.e-3 ; // length y-dircetion [m]
T geometryLengthZ = 1.e-3 ; // length z-dircetion [m]
// magnetic particle paramters
T pRadius = 1.e-5 ; // particle radius [m]
T pDensity = 3250.; // particle density [kg / m^3]
T pVolume = 4. / 3. * M_PI * std::pow(pRadius, 3.); // particle volume [m^3]
T pMass = pVolume * pDensity; // particle mass [kg]
T pMag = 67. * pDensity * 0.06; // particle sat. magnetization [A / m]
T elastModulus = 2.e4; // elastic modulus [Pa]
T poissonRatio = 0.3; // poisson's ratio [-]
T shearModulus = elastModulus / (2. * (1. + poissonRatio)); // shear modulus [Pa]
// magnetic wire paramters
T wRadius = 5.e-4; // wire radius [m]
T wLength = geometryLengthZ; // wire length [m]
T wDensity = 7874.; // wire (iron) density [kg / m^3]
T wMag = 215. * wDensity * 8.5e-3; // wire sat. magnetization [A / m]
// mass-specific sat. magnetization iron = 215. [A m^2 / kg]
// Rayleigh time step
// can be used as an orientation value for particle time step sizes
T eta = 0.8766 + 0.1631 * poissonRatio; // [-]
T t_Rayleigh = (M_PI * pRadius / eta) * std::sqrt(pDensity / shearModulus); // [s]
void prepareGeometry(UnitConverter const& converter, IndicatorF3D& wire,
IndicatorF3D& indicator, IndicatorF3D& extendedDomain,
SuperGeometry3D& superGeometry)
{
OstreamManager clout(std::cout, "prepareGeometry");
clout << "Prepare Geometry ..." << std::endl;
superGeometry.rename(0, 2, extendedDomain);
superGeometry.rename(2, 1, indicator);
superGeometry.rename(1, 5, wire);
T minR = superGeometry.getStatistics().getMinPhysR(2)[0];
minR -= 0.5 * converter.getConversionFactorLength();
std::vector < T > center = superGeometry.getStatistics().getCenterPhysR(2);
std::vector physExtend = superGeometry.getStatistics().getPhysExtend(1);
Vector origin = { minR, center[1], center[2] };
Vector extend = { converter.getConversionFactorLength(), physExtend[1], physExtend[2] };
IndicatorCuboid3D inlet(extend[0], extend[1], extend[2], origin);
origin[0] += superGeometry.getStatistics().getPhysExtend(2)[0];
IndicatorCuboid3D outlet(extend[0], extend[1], extend[2], origin);
// rename the material at the inlet
superGeometry.rename(2, 3, inlet);
// rename the material at the outlet
superGeometry.rename(2, 4, outlet);
superGeometry.clean();
superGeometry.innerClean();
superGeometry.checkForErrors();
superGeometry.print();
clout << "Prepare Geometry ... OK" << std::endl;
}
/// Set up the geometry of the simulation
void prepareLattice(SuperLattice3D& sLattice,
UnitConverter const& converter, IndicatorF3D& wire,
Dynamics& bulkDynamics,
sOnLatticeBoundaryCondition3D& sOnBC,
sOffLatticeBoundaryCondition3D& offBc,
SuperGeometry3D& superGeometry)
{
OstreamManager clout(std::cout, "prepareLattice");
clout << "Prepare Lattice ..." << std::endl;
const T omega = (1. / converter.getLatticeRelaxationTime());
/// 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());
sLattice.defineDynamics(superGeometry, 3, &bulkDynamics);
sLattice.defineDynamics(superGeometry, 4, &bulkDynamics);
/// Material=5 -->do nothing
sLattice.defineDynamics(superGeometry, 5,
&instances::getNoDynamics());
offBc.addZeroVelocityBoundary(superGeometry, 5, wire);
// boundary conditions for fluid
// inlet
sOnBC.addVelocityBoundary(superGeometry, 3, omega);
// outlet
sOnBC.addPressureBoundary(superGeometry, 4, omega);
// initialisation
AnalyticalConst3D roh(1.);
AnalyticalConst3D u0(0., 0., 0.);
sLattice.defineRhoU(superGeometry, 1, roh, u0);
sLattice.iniEquilibrium(superGeometry, 1, roh, u0);
sLattice.defineRhoU(superGeometry, 3, roh, u0);
sLattice.iniEquilibrium(superGeometry, 3, roh, u0);
sLattice.defineRhoU(superGeometry, 4, roh, u0);
sLattice.iniEquilibrium(superGeometry, 4, roh, u0);
sLattice.initialize();
clout << "Prepare Lattice ... OK" << std::endl;
}
void setBoundaryValues(SuperLattice3D& sLattice,
UnitConverter const& converter, SuperParticleSystem3D& spSys,
int iT, int itStartScaleT, SuperGeometry3D& superGeometry, bool outNS, bool outLP)
{
OstreamManager clout(std::cout, "setBoundaryValues");
std::vector < T > maxVelocity(3, T());
T distanceToBoundary = converter.getConversionFactorLength() / 2.;
if (outNS && iT <= itStartScaleT) {
SinusStartScale startScale(itStartScaleT, T(1));
int help[1] = { iT };
T frac[3] = { T() };
startScale(frac, help);
maxVelocity[0] = converter.getCharPhysVelocity() * frac[0];
// outlet
RectanglePoiseuille3D u3(superGeometry, 3, maxVelocity,
distanceToBoundary, distanceToBoundary, distanceToBoundary);
sLattice.defineU(superGeometry, 3, u3);
}
// to reset and adopt boundaries in case of loaded fluid data
if (outLP && iT == 0) {
maxVelocity[0] = converter.getCharPhysVelocity();
// inlet
RectanglePoiseuille3D u3(superGeometry, 3, maxVelocity,
distanceToBoundary, distanceToBoundary, distanceToBoundary);
clout << "BC for loaded fluid data is reset on mat 3 " << std::endl;
sLattice.defineU(superGeometry, 3, u3);
}
}
void getResults(SuperGeometry3D& superGeometry,
SuperLattice3D& sLattice,
UnitConverter const& converter,
AnalyticalF3D& magForce, AnalyticalF3D& magField,
SuperParticleSystem3D& spSys,
SuperParticleSysVtuWriterMag& particleOut, Timer& timer, int& iT,
int& itConsoleOutputFluid, int& itVtkOutputFluid,
int& itConsoleOutputMagParticles, int& itVtkOutputMagParticles,
bool& outNS, bool& outLP)
{
OstreamManager clout(std::cout, "getResults");
// start vtm objects data until fluid reaches equilibrium
SuperVTMwriter3D vtmWriterFluidStart("fluidReachingEquilibrium");
SuperLatticePhysVelocity3D velocity(sLattice, converter);
SuperLatticeDensity3D density(sLattice);
SuperLatticeFfromAnalyticalF3D superMagPForceOne(magForce,
sLattice);
SuperLatticeFfromAnalyticalF3D superMagPFieldOne (magField, sLattice);
vtmWriterFluidStart.addFunctor(velocity);
vtmWriterFluidStart.addFunctor(density);
vtmWriterFluidStart.addFunctor(superMagPForceOne);
if (outNS && iT == 0) {
SuperLatticeGeometry3D geometry(sLattice, superGeometry);
SuperLatticeCuboid3D cuboid(sLattice);
SuperLatticeRank3D rank(sLattice);
vtmWriterFluidStart.write(geometry);
vtmWriterFluidStart.write(cuboid);
vtmWriterFluidStart.write(rank);
vtmWriterFluidStart.createMasterFile();
converter.write("magnetics 1. loop");
clout << "fluid computation" << std::endl;
}
if (outLP && iT == 0) {
converter.write("magnetics 2. loop");
clout << "magParticles computation" << std::endl;
}
// === fluid
// console output fluid
if (outNS && iT % itConsoleOutputFluid == 0) {
timer.update(iT);
timer.print( iT );
// output for latticeStatistics
sLattice.getStatistics().print(iT, converter.getPhysTime(iT));
}
// vtk output fluid
if (outNS && iT % itVtkOutputFluid == 0) {
vtmWriterFluidStart.write(iT);
}
// === particles
// Writes the console output files of the magnetic particles
if (outLP && iT % itConsoleOutputMagParticles == 0) {
/// Lattice statistics console output
timer.print( iT );
}
// Writes the pvd files of the magnetic particles
if (outLP && iT % itVtkOutputMagParticles == 0) {
particleOut.write(iT);
}
}
int main(int argc, char* argv[])
{
/// === 1st Step: Initialization ===
olbInit(&argc, &argv);
singleton::directories().setOutputDir("./tmp/");
OstreamManager clout(std::cout, "main");
string fName("magneticParticles3d.xml");
XMLreader config(fName);
// converter contains parameters of fluid simulation
UnitConverter* converter = createUnitConverter(config);
clout << "fluid converter: ..." << std::endl;
converter->print(); // gives overview of converter into console
// particles time step size
T physDeltaTParticles = t_Rayleigh * 0.4; // [s]
// particles relaxation time
T tau_particles = (physDeltaTParticles * 3 * converter->getPhysViscosity() /
std::pow(converter->getConversionFactorLength(), 2.) + 0.5);
// converter contains parameters of particle simulation
UnitConverterFromResolutionAndRelaxationTime const converterParticles(
int {converter->getResolution()}, // resolution: number of voxels per charPhysL
(T) tau_particles, // latticeRelaxationTime: relaxation time, has to be greater than 0.5!
(T) converter->getCharPhysLength(), // (hydraulic raius [m]) charPhysLength: reference length of simulation geometry
(T) converter->getCharPhysVelocity(), // charPhysVelocity: maximal/highest expected velocity during simulation in __m / s__
(T) converter->getPhysViscosity(), // physViscosity: physical kinematic viscosity in __m^2 / s__
(T) converter->getPhysDensity() // physDensity: physical density in __kg / m^3__
);
clout << "particle converter: ..." << std::endl;
converterParticles.print();
// name of particle data output file
string filename = "particleData.txt";
std::ofstream outputFile;
bool multiOutput = false;
config["Output"]["MultiOutput"].read(multiOutput);
clout.setMultiOutput(multiOutput);
std::string olbdir, outputdir;
config["Application"]["OlbDir"].read(olbdir);
config["Output"]["OutputDir"].read(outputdir);
singleton::directories().setOlbDir(olbdir);
singleton::directories().setOutputDir(outputdir);
/// Instantiation of a cuboidGeometry with weights
#ifdef PARALLEL_MODE_MPI
const int noOfCuboids = singleton::mpi().getSize();
#else
const int noOfCuboids = 7;
#endif
// read physical simulation times from xml-file
T physFluidNST; // time for fluid simulation
config["Application"]["SimulationTimes"]["PhysFluidNST"].read(physFluidNST);
T physStartScT; // factor * physFluidNST;
config["Application"]["SimulationTimes"]["PhysStartScT"].read(physStartScT);
T physParticleT;
config["Application"]["SimulationTimes"]["PhysParticleT"].read(physParticleT);
// convert physical times to lattice time steps
int itFluidNST = converter->getLatticeTime(physFluidNST);
int itStartScaleT = converter->getLatticeTime(physStartScT);
int itParticleT = converterParticles.getLatticeTime(physParticleT);
int itConsoleOutputFluid = itFluidNST / 10. , itVtkOutputFluid = itFluidNST / 4.;
int itConsoleOutputMagParticles = itParticleT / 75., itVtkOutputMagParticles = itParticleT / 249.;
clout << "Fluid: physFluidNST = " << physFluidNST << "s itFluidNST = "
<< itFluidNST << std::endl;
clout << "Particles: physParticleT = " << physParticleT << "s itParticleT = "
<< itParticleT << std::endl;
clout << "noOfCuboids = " << noOfCuboids << std::endl;
/// === 2nd Step: Prepare Geometry ===
// A: Cuboid as Channel
std::vector < T > center(3, T());
std::vector length(3, T());
length[0] = geometryLengthX;
length[1] = geometryLengthY;
length[2] = geometryLengthZ;
IndicatorCuboid3D cuboid(length, center);
IndicatorLayer3D extendedDomainCuboid(cuboid, converter->getConversionFactorLength());
std::vector centerW(3, T());
centerW[0] = geometryLengthX - 0.675e-3;
centerW[1] = geometryLengthY / 2;
centerW[2] = geometryLengthZ / 2.;
std::vector normalW(3, T());
normalW[0] = T(0);
normalW[1] = T(0);
normalW[2] = T(1);
wLength += 2 * converter->getConversionFactorLength();
IndicatorCylinder3D wire(centerW, normalW, wRadius, wLength);
CuboidGeometry3D cuboidGeometry(extendedDomainCuboid,
converter->getConversionFactorLength(), noOfCuboids);
/// Instantiation of a loadBalancer
HeuristicLoadBalancer loadBalancer(cuboidGeometry);
/// Instantiation of a superGeometry
SuperGeometry3D superGeometry(cuboidGeometry, loadBalancer, 2);
prepareGeometry(*converter, wire, cuboid, extendedDomainCuboid,
superGeometry);
/// === 3rd Step: Prepare Lattice ===
SuperLattice3D sLattice(superGeometry);
BGKdynamics bulkDynamics((1. / converter->getLatticeRelaxationTime()),
instances::getBulkMomenta());
// choose between local and non-local boundary condition
sOnLatticeBoundaryCondition3D sOnBoundaryCondition(sLattice);
createInterpBoundaryCondition3D(sOnBoundaryCondition);
// for the velocity field around the wire
sOffLatticeBoundaryCondition3D sOffBoundaryCondition(sLattice);
createBouzidiBoundaryCondition3D(sOffBoundaryCondition);
// gives dynamics to cells
prepareLattice(sLattice, *converter, wire, bulkDynamics, sOnBoundaryCondition,
sOffBoundaryCondition, superGeometry);
/// === 4th Step: Prepare Lagrange Particles
SuperParticleSystem3D spSys(cuboidGeometry, loadBalancer,
superGeometry);
// contact detection
NanoflannContact nanoflannContact(
*(spSys.getParticleSystems()[0]), 6. * pRadius);
spSys.setContactDetection(nanoflannContact);
// magnetic force field from wire
std::vector < T > origin = superGeometry.getStatistics().getCenterPhysR(1);
origin[0] = geometryLengthX - 0.675e-3;
origin[2] = superGeometry.getStatistics().getMinPhysR(1)[2];
std::vector orientation(3, T());
orientation[0] = 1.;
T degree = 0.;
// car2cyl helps to know which cylinder coordinates fit to the cartesion ones
CartesianToCylinder3D car2cyl(origin, degree, orientation);
/// Forces of Lagrange particles
// Object of magnetic force to know magnetic value at any place in geometry
// but needs to be called by operator function.
// inhomogeneous magnetic field round cylinder
MagneticForceFromCylinder3D magForceFromCylOnParticle(car2cyl, wLength,
wRadius, 4., wMag, pMag, pRadius);
MagneticFieldFromCylinder3D magFieldFromCylOnParticle(car2cyl, wLength,
wRadius, 4., wMag);
// external magnetic force from the wire
auto magneticPForceOne = make_shared
< MagneticForceForMagP3D
> (magForceFromCylOnParticle, magFieldFromCylOnParticle, 1.e0);
spSys.addForce(magneticPForceOne);
// interparticular magnetic force
auto interpMagF = make_shared
< InterpMagForceForMagP3D
> (1.e0, 1.e0);
spSys.addForce(interpMagF);
// dynamic viscosity
T dynVisc = converter->getPhysViscosity() * converter->getPhysDensity();
// damping force for the particle rotation
auto rotDampingF = make_shared
< LinearDampingForceForMagDipoleMoment3D
> (dynVisc, 1.e0);
spSys.addForce(rotDampingF);
// mechanic contact force
auto hertzMindlinContF = make_shared
< HertzMindlinDeresiewicz3D
> (shearModulus, shearModulus, poissonRatio, poissonRatio, 1.e0, 1.e0, false);
spSys.addForce(hertzMindlinContF);
// stokes drag force
SuperLatticeInterpPhysVelocity3D getVel( sLattice, *converter );
auto stokesDragF = make_shared
< StokesDragForce3D
> (getVel, converterParticles);
spSys.addForce(stokesDragF);
/// Boundarys
T dT = converter->getConversionFactorTime() ;
std::set wireBMaterial = { 5, 4 };
std::set reflBMat = { 2, 3 };
// particle boundary for deposited particles
auto wireBoundary = make_shared
< WireBoundaryForMagP3D
> (superGeometry, wireBMaterial);
spSys.addBoundary(wireBoundary);
// particle boundary
auto materialreflectBoundary = make_shared
< SimpleReflectBoundary3D
> (dT, superGeometry, reflBMat);
spSys.addBoundary(materialreflectBoundary);
// cuboid overlap
spSys.setOverlap(2.*converter->getConversionFactorLength());
// particles vtu output
std::string particleOutputName = "vtuOutMagP";
std::bitset < 9 > properties;
properties.set();
SuperParticleSysVtuWriterMag particleOut(spSys, particleOutputName, properties);
clout << "Number of Forces: " << spSys.numOfForces()[0] << std::endl;
/// Paramters for magnetic particle generation
std::vector pPos = { 0., 0., 0. }; // position
std::vector pVel = { 0., 0., 0. }; // velocity
std::vector pAVel = { 0., 0., 0. }; // angular velocity
std::vector pTrq = { 0., 0., 0. }; // torque
std::vector pDMoment = { -1., 0., 0. }; // orientation mag. dipole moment
if(!util::nearZero(util::norm(pDMoment))){util::normalize(pDMoment);}
else {clout << "Norm of pDMoment near zero!" << endl; exit(0);}
// magnetic particle magPartTemplate is used as copy template
PARTICLE magPartTemplate(pPos, pVel, pMass, pRadius, 0, pDMoment, pAVel, pTrq, pMag, 1);
// intialization geometry particles
std::vector particlesOrigin(3, T(0));
particlesOrigin = superGeometry.getStatistics().getMinPhysR(1);
std::vector particlesExtend(3, T(0));
particlesOrigin[0] = 1.e-4;
particlesOrigin[1] = (superGeometry.getStatistics().getMaxPhysR(1)[1]) * (1./2. - 1./6.);
particlesOrigin[2] = (superGeometry.getStatistics().getMaxPhysR(1)[2]) * (1./2. - 1./6.);
particlesExtend[0] = (superGeometry.getStatistics().getMaxPhysR(1)[0]) * 1./15. ;
particlesExtend[1] = (superGeometry.getStatistics().getMaxPhysR(1)[1]) * 2./6. ;
particlesExtend[2] = (superGeometry.getStatistics().getMaxPhysR(1)[2]) * 2./6. ;
IndicatorCuboid3D cuboidPart(particlesExtend, particlesOrigin);
clout << "starting simulation" << endl;
/// === 5th Step: Main Loop with Timer ===
// is set on true for the appropriate loop of fluid / particle simulation
bool outNS = false;
bool outLP = false;
// === Calculating Navier-Stokes Fluid
// if there is no computed fluid data in a external data files
// compute fluid data in data file, else load it and jump to next loop
if (!(sLattice.load("water"))) {
clout << std::endl;
clout << "Fluid data has to be computed " << std::endl;
clout << std::endl;
outNS = true;
Timer timerFluid(itFluidNST, superGeometry.getStatistics().getNvoxel());
timerFluid.start();
for (int iT = 0; iT < itFluidNST; ++iT) {
setBoundaryValues(sLattice, *converter, spSys, iT, itStartScaleT,
superGeometry, outNS, outLP);
sLattice.collideAndStream();
getResults(superGeometry, sLattice, *converter,
magForceFromCylOnParticle, magFieldFromCylOnParticle, spSys,
particleOut, timerFluid, iT, itConsoleOutputFluid, itVtkOutputFluid,
itConsoleOutputMagParticles, itVtkOutputMagParticles, outNS, outLP);
}
outNS = false;
// save computed fluid data in external data files
clout << "save fluid data " << std::endl;
sLattice.communicate();
sLattice.save("water");
timerFluid.stop();
timerFluid.printSummary();
} else {
sLattice.communicate();
sLattice.collideAndStream();
sLattice.getStatistics().print(iT, converter->getPhysTime(iT));
}
// === Calculating Particles
clout << std::endl;
clout << "particles computation" << std::endl;
clout << std::endl;
outLP = true;
// number of Particles in initialization geometry
int noOfPartX = 4 ;
int noOfPartY = 12 ;
int noOfPartZ = 6 ;
int noOfPart = noOfPartX * noOfPartY * noOfPartZ;
Timer timerParticles(itParticleT, noOfPart);
timerParticles.start();
setBoundaryValues(sLattice, converterParticles, spSys, iT, itStartScaleT,
superGeometry, outNS, outLP);
for (int iT = 0; iT < itParticleT; ++iT) {
// particles initialization
if (iT == 0) {
spSys.addParticleEquallyDistributed(cuboidPart, noOfPartX, noOfPartY, noOfPartZ, magPartTemplate);
spSys.setParticlesPosRandom(0.75 * pRadius, 0.75 * pRadius, 0.55 * pRadius);
}
getResults(superGeometry, sLattice, converterParticles,
magForceFromCylOnParticle, magFieldFromCylOnParticle, spSys,
particleOut, timerParticles, iT, itConsoleOutputFluid, itVtkOutputFluid,
itConsoleOutputMagParticles, itVtkOutputMagParticles,
outNS, outLP);
// particles simulation
spSys.simulate(converterParticles.getConversionFactorTime());
}
timerParticles.stop();
timerParticles.printSummary();
clout << "End of simulation" << std::endl;
}