/* Lattice Boltzmann sample, written in C++, using the OpenLB
* library
*
* Copyright (C) 2019 Davide Dapelo
* 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.
*/
/* Helper functionals for Lagrangian two-way coupling methods -- generic implementation.
*/
#ifndef LB_TWO_WAY_HELPER_FUNCTIONALS_HH
#define LB_TWO_WAY_HELPER_FUNCTIONALS_HH
namespace olb {
////////////////////// Class ParticleReynoldsNumberBase ////////////////////////
template class Particle>
ParticleReynoldsNumberBase::ParticleReynoldsNumberBase(UnitConverter& converter)
: _converter(converter)
{}
////////////////////// Class NewtonianParticleReynoldsNumber ////////////////////////
template class Particle>
NewtonianParticleReynoldsNumber::NewtonianParticleReynoldsNumber(UnitConverter& converter)
: ParticleReynoldsNumberBase(converter)
{}
template class Particle>
T NewtonianParticleReynoldsNumber::operator() ( Particle* p, T magU, int globicFull[])
{
T ReP = 2. * p->getRad() * magU / this->_converter.getPhysViscosity();
return ReP > this->_RePmin ? ReP : this->_RePmin;
}
////////////////////// Class PowerLawParticleReynoldsNumber ////////////////////////
template class Particle>
PowerLawParticleReynoldsNumber::PowerLawParticleReynoldsNumber (
UnitConverter& converter, SuperLattice3D& sLattice )
: ParticleReynoldsNumberBase(converter),
_sLattice(sLattice)
{}
template class Particle>
T PowerLawParticleReynoldsNumber::operator() ( Particle* p, T magU, int globicFull[])
{
// loc() indicates the local cuboid number locIC of the actual processing thread,
// for given global cuboid number iC
// this is to get appropriate particle system on locIC
int locIC = _sLattice.getLoadBalancer().loc(globicFull[0]);
T physPosP[3] = {T(), T(), T()}; // particle's physical position
physPosP[0] = (p->getPos()[0]);
physPosP[1] = (p->getPos()[1]);
physPosP[2] = (p->getPos()[2]);
// particle's dimensionless position, rounded at neighbouring voxel
int latticeRoundedPosP[3] = { globicFull[1], globicFull[2], globicFull[3] };
// getting the power-law relaxation frequency form the dynamics's external field
T omega = _sLattice.getBlockLattice(locIC).get (
latticeRoundedPosP[0],
latticeRoundedPosP[1],
latticeRoundedPosP[2] ).template getFieldPointer()[0];
// physical viscosity from relaxation time
T nu = this->_converter.getPhysViscosity (
(1./omega - 0.5) / descriptors::invCs2() );
T ReP = 2. * p->getRad() * magU / nu;
return ReP > this->_RePmin ? ReP : this->_RePmin;
}
////////////////////// Class TwoWayHelperFunctional ////////////////////////
template
TwoWayHelperFunctional::TwoWayHelperFunctional (
UnitConverter& converter,
SuperLattice3D& sLattice )
: _converter(converter),
_sLattice(sLattice)
{}
template
TwoWayHelperFunctional::~TwoWayHelperFunctional()
{
_interpLatticeDensity.reset();
_interpLatticeVelocity.reset();
}
////////////////////// Class NaiveMomentumExchange ////////////////////////
template
NaiveMomentumExchange::NaiveMomentumExchange (
UnitConverter& converter,
SuperLattice3D& sLattice,
std::shared_ptr > interpLatticeDensity )
: TwoWayHelperFunctional(converter, sLattice)
{
this->_interpLatticeDensity = interpLatticeDensity;
}
template
bool NaiveMomentumExchange::operator() ( T gF[], T latticeVelF[], T latticeVelP[],
T physPosP[], int latticeRoundedP[],
int globic )
{
T magU = sqrt( pow(latticeVelF[0] - latticeVelP[0],2) +
pow(latticeVelF[1] - latticeVelP[1],2) +
pow(latticeVelF[2] - latticeVelP[2],2) );
// Interpolated/exact pdf (depending on the functional)
T f[Lattice::q] = { T() };
this->_interpLatticeDensity->operator()(f, physPosP, globic);
// Mock cell with the same dynamics of the cell containing the particle
int locIC = this->_sLattice.getLoadBalancer().loc(globic);
Cell cell ( this->_sLattice.getBlockLattice(locIC).get (
latticeRoundedP[0],
latticeRoundedP[1],
latticeRoundedP[2] ).getDynamics() );
// Filling the mock cell with the interpolated pdf
for (unsigned iPop = 0; iPop < Lattice::q; ++iPop) {
cell[iPop] = f[iPop];
}
T rhoL = cell.computeRho();
gF[0] = rhoL * magU;
gF[1] = rhoL * magU;
gF[2] = rhoL * magU;
return true;
}
////////////////////// Class LaddMomentumExchange ////////////////////////
template
LaddMomentumExchange::LaddMomentumExchange (
UnitConverter& converter,
SuperLattice3D& sLattice,
std::shared_ptr > interpLatticeDensity,
std::shared_ptr > interpLatticeVelocity )
: TwoWayHelperFunctional(converter, sLattice)
{
this->_interpLatticeDensity = interpLatticeDensity;
this->_interpLatticeVelocity = interpLatticeVelocity;
}
template
bool LaddMomentumExchange::operator() ( T gF[], T latticeVelF[], T latticeVelP[],
T physPosP[], int latticeRoundedP[],
int globic )
{
T physLatticeL = this->_converter.getConversionFactorLength();
// force density gF
gF[0] = T();
gF[1] = T();
gF[2] = T();
T fiPop = T();
T sp = T(); // dot product for particle velocity
T faPos[3] = {T(), T(), T()}; // fAlphaPosition = particle position
T fbPos[3] = {T(), T(), T()}; // fBetaPosition = neighbor position to particle position in direction iPop
T fa[Lattice::q] = { T() }; // fAlpha = interpolated density to fAlphaPosition
T fb[Lattice::q] = { T() }; // fBeta = interpolated density to fBetaPosition
T lFU[3] = {T(), T(), T()};
// runs through all q discrete velocity directions
for (unsigned iPop = 0; iPop < Lattice::q; ++iPop) {
// physical position on neighbor to get pre-streaming collision part
faPos[0] = physPosP[0] + physLatticeL * descriptors::c(iPop,0);
faPos[1] = physPosP[1] + physLatticeL * descriptors::c(iPop,1);
faPos[2] = physPosP[2] + physLatticeL * descriptors::c(iPop,2);
// Lagrange interpolated polynomial to get density on particle position
this->_interpLatticeDensity->operator() (fa, faPos, globic);
// physical position on neighbor to get pre-streaming collision part
fbPos[0] = physPosP[0] - physLatticeL * descriptors::c(iPop,0);
fbPos[1] = physPosP[1] - physLatticeL * descriptors::c(iPop,1);
fbPos[2] = physPosP[2] - physLatticeL * descriptors::c(iPop,2);
// Lagrange interpolated polynomial to get density on particle position
this->_interpLatticeDensity->operator() (fb, fbPos, globic);
// fiPop = density on fBetaPosition in direction iPop
fiPop = fb[util::opposite(iPop)];
// Get f_l of the boundary cell
// add density fAlphaL of opposite direction to iPop
fiPop -= fa[iPop];
// physical velocity
lFU[0] = -descriptors::c(iPop,0) * fiPop;
lFU[1] = -descriptors::c(iPop,1) * fiPop;
lFU[2] = -descriptors::c(iPop,2) * fiPop;
// point product
sp = descriptors::c(iPop,0) * latticeVelP[0] + descriptors::c(iPop,1) * latticeVelP[1]
+ descriptors::c(iPop,2) * latticeVelP[2];
sp *= 2. * descriptors::invCs2()
* descriptors::t(iPop);
// external force density that acts on particles
gF[0] += (lFU[0] - descriptors::c(iPop,0) * (sp));
gF[1] += (lFU[1] - descriptors::c(iPop,1) * (sp));
gF[2] += (lFU[2] - descriptors::c(iPop,2) * (sp));
}
gF[0] = fabs(gF[0]);
gF[1] = fabs(gF[1]);
gF[2] = fabs(gF[2]);
return true;
}
////////////////////// Class SmoothingFunctional ////////////////////////
template
SmoothingFunctional::SmoothingFunctional (
T kernelLength, UnitConverter& converter, SuperLattice3D& sLattice )
: _kernelLength(kernelLength),
_converter(converter),
_sLattice(sLattice)
{}
template
int SmoothingFunctional::getSize()
{
return _latticePosAndWeight.size();
}
template
void SmoothingFunctional::getLatticePos(int latticePos[], int i)
{
latticePos[0] = _latticePosAndWeight[i].latticePos[0];
latticePos[1] = _latticePosAndWeight[i].latticePos[1];
latticePos[2] = _latticePosAndWeight[i].latticePos[2];
}
template
int SmoothingFunctional::getGlobic(int i)
{
return _latticePosAndWeight[i].globic;
}
template
T SmoothingFunctional::getWeight(int i)
{
return _latticePosAndWeight[i].weight;
}
template
bool SmoothingFunctional::update(T physPosP[], int globic)
{
// Bottom-left corner of a cube centered at the particle, with side 2*_kernelLength
T physPosMin[3] = {T(), T(), T()};
physPosMin[0] = physPosP[0] - _kernelLength;
physPosMin[1] = physPosP[1] - _kernelLength;
physPosMin[2] = physPosP[2] - _kernelLength;
int latticePosMin[3] = {0, 0, 0};
this->_sLattice.getCuboidGeometry().get(globic).getLatticeR (
latticePosMin, physPosMin );
// Top-right corner of a cube centered at the particle, with side 2*_kernelLength
T physPosMax[3] = {T(), T(), T()};
physPosMax[0] = physPosP[0] + _kernelLength;
physPosMax[1] = physPosP[1] + _kernelLength;
physPosMax[2] = physPosP[2] + _kernelLength;
int latticePosMax[3] = {0, 0, 0};
this->_sLattice.getCuboidGeometry().get(globic).getLatticeR (
latticePosMax, physPosMax );
// Clearing the _latticePosAndWeight list
_latticePosAndWeight.clear();
T normalizer = T();
int iLatticePos[3] = {0, 0, 0};
// Cycling all the cells on a cube containing a sphee centered in bubble's position and with kernel smoothing length as radius
for (iLatticePos[0]=latticePosMin[0]; iLatticePos[0]<=latticePosMax[0]; iLatticePos[0]++) {
for (iLatticePos[1]=latticePosMin[1]; iLatticePos[1]<=latticePosMax[1]; iLatticePos[1]++) {
for (iLatticePos[2]=latticePosMin[2]; iLatticePos[2]<=latticePosMax[2]; iLatticePos[2]++) {
T iPhysPos[3] = {T(), T(), T()};
this->_sLattice.getCuboidGeometry().get(globic).getPhysR (
iPhysPos, iLatticePos );
// Is the voxel within a smooting kernel length from the bubble's position?
if ( pow(physPosP[0] - iPhysPos[0], 2) +
pow(physPosP[1] - iPhysPos[1], 2) +
pow(physPosP[2] - iPhysPos[2], 2) < pow(_kernelLength, 2) ) {
// Adding the voxel's position (and relative weight) to the _latticePosAndWeight list
LatticePosAndWeight item;
item.latticePos[0] = iLatticePos[0];
item.latticePos[1] = iLatticePos[1];
item.latticePos[2] = iLatticePos[2];
item.weight = this->compute(physPosP, iPhysPos);
normalizer += item.weight;
_latticePosAndWeight.push_back(item);
}
}
}
}
// If normalizer is zero, then no voxels are within a kernel smoothing length from the bubble's location.
// And it is a problem.
if (normalizer == T()) {
std::cout << "ERROR: SmoothingFunctional::update(...):" << std::endl
<< "[smoothingFunctional] physPosP: "
<< physPosP[0] << " "
<< physPosP[1] << " "
<< physPosP[2] << std::endl
<< "[smoothingFunctional] physPosMin: "
<< physPosMin[0] << " "
<< physPosMin[1] << " "
<< physPosMin[2] << std::endl
<< "[smoothingFunctional] physPosMax: "
<< latticePosMax[0] << " "
<< latticePosMax[1] << " "
<< latticePosMax[2] << std::endl
<< "[smoothingFunctional] normalizer: "
<< normalizer << std::endl;
return false;
}
// Normalizing to one
for (int i=0; i
LinearAveragingSmoothingFunctional::LinearAveragingSmoothingFunctional (
T kernelLength, UnitConverter& converter, SuperLattice3D& sLattice )
: SmoothingFunctional(kernelLength, converter, sLattice)
{}
template
T LinearAveragingSmoothingFunctional::compute(T physPosP[], T physPosL[])
{
return this->smoothingFunction(physPosP[0] - physPosL[0])
* this->smoothingFunction(physPosP[1] - physPosL[1])
* this->smoothingFunction(physPosP[2] - physPosL[2]);
}
////////////////////// Class VolumeAveragingSmoothingFunctional ////////////////////////
template
VolumeAveragingSmoothingFunctional::VolumeAveragingSmoothingFunctional (
T kernelLength, UnitConverter& converter, SuperLattice3D& sLattice )
: SmoothingFunctional(kernelLength, converter, sLattice)
{}
template
T VolumeAveragingSmoothingFunctional::compute(T physPosP[], T physPosL[])
{
return this->smoothingFunction ( sqrt (
pow(physPosP[0] - physPosL[0], 2) +
pow(physPosP[1] - physPosL[1], 2) +
pow(physPosP[2] - physPosL[2], 2) ) );
}
////////////////////// Class DeenSmoothingFunctional ////////////////////////
template
DeenSmoothingFunctional::DeenSmoothingFunctional (
T kernelLength, UnitConverter& converter, SuperLattice3D& sLattice )
: LinearAveragingSmoothingFunctional(kernelLength, converter, sLattice)
{}
template
T DeenSmoothingFunctional::smoothingFunction(T delta)
{
return ( pow(delta, 4)/pow(this->_kernelLength, 5)
- 2.*pow(delta, 2)/pow(this->_kernelLength, 3)
+ 1./this->_kernelLength
);
}
////////////////////// Class StepSmoothingFunctional ////////////////////////
template
StepSmoothingFunctional::StepSmoothingFunctional (
T kernelLength, UnitConverter& converter, SuperLattice3D& sLattice )
: VolumeAveragingSmoothingFunctional(kernelLength, converter, sLattice)
{}
template
T StepSmoothingFunctional::smoothingFunction(T delta)
{
return 1.;
}
}
#endif