/* Lattice Boltzmann sample, written in C++, using the OpenLB
* library
*
* Copyright (C) 2006-2014 Jonas Latt, Mathias J. Krause,
* Vojtech Cvrcek, Peter Weisbrod
* 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.
*/
/* cylinder2d.cpp:
* This example examines a steady flow past a cylinder placed in a channel.
* The cylinder is offset somewhat from the center of the flow to make the
* steady-state symmetrical flow unstable. At the inlet, a Poiseuille profile is
* imposed on the velocity, whereas the outlet implements a Dirichlet pressure
* condition set by p = 0.
* Inspired by "Benchmark Computations of Laminar Flow Around
* a Cylinder" by M.Schäfer and S.Turek. For high resolution, low
* latticeU, and enough time to converge, the results for pressure drop, drag
* and lift lie within the estimated intervals for the exact results.
* An unsteady flow with Karman vortex street can be created by changing the
* Reynolds number to Re=100.
*/
#include "olb2D.h"
#ifndef OLB_PRECOMPILED // Unless precompiled version is used,
#include "olb2D.hh" // include full template code
#endif
#include
#include
#include
#include
using namespace olb;
using namespace olb::descriptors;
using namespace olb::graphics;
using namespace olb::util;
using namespace std;
typedef double T;
#define DESCRIPTOR D2Q9<>
// Parameters for the simulation setup
const int N = 10; // resolution of the model
const T Re = 20.; // Reynolds number
const T maxPhysT = 16.; // max. simulation time in s, SI unit
const T L = 0.1/N; // latticeL
const T lengthX = 2.2;
const T lengthY = .41+L;
const T centerCylinderX = 0.2;
const T centerCylinderY = 0.2+L/2.;
const T radiusCylinder = 0.05;
// Stores geometry information in form of material numbers
void prepareGeometry( UnitConverter const& converter,
SuperGeometry2D& superGeometry )
{
OstreamManager clout( std::cout,"prepareGeometry" );
clout << "Prepare Geometry ..." << std::endl;
Vector extend( lengthX,lengthY );
Vector center( centerCylinderX,centerCylinderY );
Vector origin;
IndicatorCircle2D circle( center, radiusCylinder );
superGeometry.rename( 0,2 );
superGeometry.rename( 2,1,1,1 );
// Set material number for inflow
extend[0] = 2.*L;
origin[0] = -L;
IndicatorCuboid2D inflow( extend, origin );
superGeometry.rename( 2,3,1,inflow );
// Set material number for outflow
origin[0] = lengthX-L;
IndicatorCuboid2D outflow( extend, origin );
superGeometry.rename( 2,4,1,outflow );
// Set material number for cylinder
superGeometry.rename( 1,5,circle );
// Removes all not needed boundary voxels outside the surface
superGeometry.clean();
superGeometry.checkForErrors();
superGeometry.print();
clout << "Prepare Geometry ... OK" << std::endl;
}
// Set up the geometry of the simulation
void prepareLattice( SuperLattice2D& sLattice,
UnitConverter const& converter,
Dynamics& bulkDynamics,
sOnLatticeBoundaryCondition2D& sBoundaryCondition,
sOffLatticeBoundaryCondition2D& offBc,
SuperGeometry2D& 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
// Material=3 -->bulk dynamics (inflow)
// Material=4 -->bulk dynamics (outflow)
auto bulkIndicator = superGeometry.getMaterialIndicator({1, 3, 4});
sLattice.defineDynamics( bulkIndicator, &bulkDynamics );
// Material=2 -->bounce back
sLattice.defineDynamics( superGeometry, 2, &instances::getBounceBack() );
// Setting of the boundary conditions
sBoundaryCondition.addVelocityBoundary( superGeometry, 3, omega );
sBoundaryCondition.addPressureBoundary( superGeometry, 4, omega );
// Material=5 -->bounce back
//sLattice.defineDynamics(superGeometry, 5, &instances::getBounceBack());
// Material=5 -->bouzidi
Vector center( centerCylinderX,centerCylinderY );
IndicatorCircle2D circle( center, radiusCylinder );
sLattice.defineDynamics( superGeometry, 5, &instances::getNoDynamics() );
offBc.addZeroVelocityBoundary( superGeometry, 5, circle );
// Initial conditions
AnalyticalConst2D rhoF( 1 );
std::vector velocity( 2,T( 0 ) );
AnalyticalConst2D uF( velocity );
// Initialize all values of distribution functions to their local equilibrium
sLattice.defineRhoU( bulkIndicator, rhoF, uF );
sLattice.iniEquilibrium( bulkIndicator, rhoF, uF );
// Make the lattice ready for simulation
sLattice.initialize();
clout << "Prepare Lattice ... OK" << std::endl;
}
// Generates a slowly increasing inflow for the first iTMaxStart timesteps
void setBoundaryValues( SuperLattice2D& sLattice,
UnitConverter const& converter, int iT,
SuperGeometry2D& superGeometry )
{
OstreamManager clout( std::cout,"setBoundaryValues" );
// No of time steps for smooth start-up
int iTmaxStart = converter.getLatticeTime( maxPhysT*0.4 );
int iTupdate = 5;
if ( iT%iTupdate==0 && iT<= iTmaxStart ) {
// Smooth start curve, sinus
// SinusStartScale StartScale(iTmaxStart, T(1));
// Smooth start curve, polynomial
PolynomialStartScale StartScale( iTmaxStart, T( 1 ) );
// Creates and sets the Poiseuille inflow profile using functors
T iTvec[1] = {T( iT )};
T frac[1] = {};
StartScale( frac,iTvec );
T maxVelocity = converter.getCharLatticeVelocity()*3./2.*frac[0];
T distance2Wall = L/2.;
Poiseuille2D poiseuilleU( superGeometry, 3, maxVelocity, distance2Wall );
sLattice.defineU( superGeometry, 3, poiseuilleU );
}
}
// Computes the pressure drop between the voxels before and after the cylinder
void getResults( SuperLattice2D& sLattice,
UnitConverter const& converter, int iT,
SuperGeometry2D& superGeometry, Timer& timer,
CircularBuffer& buffer )
{
OstreamManager clout( std::cout,"getResults" );
SuperVTMwriter2D vtmWriter( "cylinder2d" );
SuperLatticePhysVelocity2D velocity( sLattice, converter );
SuperLatticePhysPressure2D pressure( sLattice, converter );
vtmWriter.addFunctor( velocity );
vtmWriter.addFunctor( pressure );
const int vtkIter = converter.getLatticeTime( .3 );
const int statIter = converter.getLatticeTime( .1 );
T point[2] = {};
point[0] = centerCylinderX + 3*radiusCylinder;
point[1] = centerCylinderY;
AnalyticalFfromSuperF2D intpolateP( pressure, true );
T p;
intpolateP( &p,point );
buffer.insert(p);
if ( iT == 0 ) {
// Writes the geometry, cuboid no. and rank no. as vti file for visualization
SuperLatticeGeometry2D geometry( sLattice, superGeometry );
SuperLatticeCuboid2D cuboid( sLattice );
SuperLatticeRank2D rank( sLattice );
vtmWriter.write( geometry );
vtmWriter.write( cuboid );
vtmWriter.write( rank );
vtmWriter.createMasterFile();
}
// Writes the vtk files
if ( iT%vtkIter == 0 && iT > 0 ) {
vtmWriter.write( iT );
SuperEuklidNorm2D normVel( velocity );
BlockReduction2D2D planeReduction( normVel, 600, BlockDataSyncMode::ReduceOnly );
// write output as JPEG
heatmap::write(planeReduction, iT);
}
// Gnuplot constructor (must be static!)
// for real-time plotting: gplot("name", true) // experimental!
static Gnuplot gplot( "drag" );
// write pdf at last time step
if ( iT == converter.getLatticeTime( maxPhysT )-1 ) {
// writes pdf
gplot.writePDF();
}
// Writes output on the console
if ( iT%statIter == 0 ) {
// Timer console output
timer.update( iT );
timer.printStep();
clout << "Circular buffer test: moving average pointwise value=" << buffer.average() << std::endl;
// Lattice statistics console output
sLattice.getStatistics().print( iT,converter.getPhysTime( iT ) );
// Drag, lift, pressure drop
AnalyticalFfromSuperF2D intpolatePressure( pressure, true );
SuperLatticePhysDrag2D drag( sLattice, superGeometry, 5, converter );
T point1[2] = {};
T point2[2] = {};
point1[0] = centerCylinderX - radiusCylinder;
point1[1] = centerCylinderY;
point2[0] = centerCylinderX + radiusCylinder;
point2[1] = centerCylinderY;
T p1, p2;
intpolatePressure( &p1,point1 );
intpolatePressure( &p2,point2 );
clout << "pressure1=" << p1;
clout << "; pressure2=" << p2;
T pressureDrop = p1-p2;
clout << "; pressureDrop=" << pressureDrop;
int input[3] = {};
T _drag[drag.getTargetDim()];
drag( _drag,input );
clout << "; drag=" << _drag[0] << "; lift=" << _drag[1] << endl;
// set data for gnuplot: input={xValue, yValue(s), names (optional), position of key (optional)}
gplot.setData( converter.getPhysTime( iT ), {_drag[0], 5.58}, {"drag(openLB)", "drag(schaeferTurek)"}, "bottom right", {'l','l'} );
// writes a png in one file for every timestep, if the file is open it can be used as a "liveplot"
gplot.writePNG();
// every (iT%vtkIter) write an png of the plot
if ( iT%( vtkIter ) == 0 ) {
// writes pngs: input={name of the files (optional), x range for the plot (optional)}
gplot.writePNG( iT, maxPhysT );
}
}
}
int main( int argc, char* argv[] )
{
// === 1st Step: Initialization ===
olbInit( &argc, &argv );
singleton::directories().setOutputDir( "./tmp/" );
OstreamManager clout( std::cout,"main" );
// display messages from every single mpi process
//clout.setMultiOutput(true);
UnitConverterFromResolutionAndRelaxationTime const converter(
int {N}, // resolution: number of voxels per charPhysL
(T) 0.56, // latticeRelaxationTime: relaxation time, have to be greater than 0.5!
(T) 2.0*radiusCylinder, // charPhysLength: reference length of simulation geometry
(T) 0.2, // charPhysVelocity: maximal/highest expected velocity during simulation in __m / s__
(T) 0.2*2.*radiusCylinder/Re, // physViscosity: physical kinematic viscosity in __m^2 / s__
(T) 1.0 // 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("cylinder2d");
// === 2rd Step: Prepare Geometry ===
Vector extend( lengthX,lengthY );
Vector origin;
IndicatorCuboid2D cuboid( extend, origin );
// Instantiation of a cuboidGeometry with weights
#ifdef PARALLEL_MODE_MPI
const int noOfCuboids = singleton::mpi().getSize();
#else
const int noOfCuboids = 7;
#endif
CuboidGeometry2D cuboidGeometry( cuboid, L, noOfCuboids );
// Instantiation of a loadBalancer
HeuristicLoadBalancer loadBalancer( cuboidGeometry );
// Instantiation of a superGeometry
SuperGeometry2D superGeometry( cuboidGeometry, loadBalancer, 2 );
prepareGeometry( converter, superGeometry );
// === 3rd Step: Prepare Lattice ===
SuperLattice2D sLattice( superGeometry );
BGKdynamics bulkDynamics( converter.getLatticeRelaxationFrequency(), instances::getBulkMomenta() );
// choose between local and non-local boundary condition
sOnLatticeBoundaryCondition2D sBoundaryCondition( sLattice );
// createInterpBoundaryCondition2D(sBoundaryCondition);
createLocalBoundaryCondition2D( sBoundaryCondition );
sOffLatticeBoundaryCondition2D sOffBoundaryCondition( sLattice );
createBouzidiBoundaryCondition2D ( sOffBoundaryCondition );
prepareLattice( sLattice, converter, bulkDynamics, sBoundaryCondition, sOffBoundaryCondition, superGeometry );
// === 4th Step: Main Loop with Timer ===
CircularBuffer buffer(converter.getLatticeTime(.2));
clout << "starting simulation..." << endl;
Timer timer( converter.getLatticeTime( maxPhysT ), superGeometry.getStatistics().getNvoxel() );
timer.start();
for ( int iT = 0; iT < converter.getLatticeTime( maxPhysT ); ++iT ) {
// === 5th Step: Definition of Initial and Boundary Conditions ===
setBoundaryValues( sLattice, converter, iT, superGeometry );
// === 6th Step: Collide and Stream Execution ===
sLattice.collideAndStream();
// === 7th Step: Computation and Output of the Results ===
getResults( sLattice, converter, iT, superGeometry, timer, buffer );
}
timer.stop();
timer.printSummary();
}