cloud_fvModel.C
Go to the documentation of this file.
1 /*---------------------------------------------------------------------------*\
2  ========= |
3  \\ / F ield | OpenFOAM: The Open Source CFD Toolbox
4  \\ / O peration | Website: https://openfoam.org
5  \\ / A nd | Copyright (C) 2025-2026 OpenFOAM Foundation
6  \\/ M anipulation |
7 -------------------------------------------------------------------------------
8 License
9  This file is part of OpenFOAM.
10 
11  OpenFOAM is free software: you can redistribute it and/or modify it
12  under the terms of the GNU General Public License as published by
13  the Free Software Foundation, either version 3 of the License, or
14  (at your option) any later version.
15 
16  OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
17  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
18  FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
19  for more details.
20 
21  You should have received a copy of the GNU General Public License
22  along with OpenFOAM. If not, see <http://www.gnu.org/licenses/>.
23 
24 \*---------------------------------------------------------------------------*/
25 
26 #include "cloud_fvModel.H"
28 #include "fvmSup.H"
29 #include "pimpleNoLoopControl.H"
31 
32 // * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
33 
34 namespace Foam
35 {
36 namespace fv
37 {
39 }
40 }
41 
42 
43 // * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * //
44 
45 template<class Type>
46 void Foam::fv::cloud::fail(const fvMatrix<Type>& eqn) const
47 {
49  << "Could not add a source for conservation of "
50  << "1"
51  << " in the Lagrangian cloud " << cloud_.name()
52  << " to the finite-volume equation for " << eqn.psi().name()
53  << exit(FatalError);
54 }
55 
56 
57 template<class Type, class ... AlphaRhoFieldTypes>
58 void Foam::fv::cloud::fail
59 (
60  const fvMatrix<Type>& eqn,
61  const AlphaRhoFieldTypes& ... alphaRhoFields
62 ) const
63 {
65  << "Could not add a source for conservation of "
66  << LagrangianModel::fieldsName(alphaRhoFields ...)
67  << " in the Lagrangian cloud " << cloud_.name()
68  << " to the finite-volume equation for " << eqn.psi().name()
69  << exit(FatalError);
70 }
71 
72 
74 (
75  const word& phaseName,
76  const dimensionSet& dims
77 ) const
78 {
79  typedef HashTable<const CarrierEqn<scalar>*> carrierEqnTable;
80 
81  const bool isPhase = phaseName != word::null;
82  const bool isMultiphase = carriedCloud_.carrierPhaseName() != word::null;
83 
84  const clouds::coupledToConstantDensityFluid& ctcdfCloud =
85  refCastNull<const clouds::coupledToConstantDensityFluid>(cloud_);
86 
87  // Volume source
88  if (!isPhase && !isMultiphase && dims == dimless && notNull(ctcdfCloud))
89  {
90  return
91  ctcdfCloud.rhoByRhoc
92  *coupledCloud_.carrierEqn<scalar>("1").residual(dimless/dimTime);
93  }
94  if (!isPhase && isMultiphase && dims == dimless && notNull(ctcdfCloud))
95  {
96  tmp<volScalarField::Internal> tS =
98  (
99  "S",
100  mesh(),
101  dimensionedScalar(dimless/dimTime, scalar(0))
102  );
103  volScalarField::Internal& S = tS.ref();
104 
105  const carrierEqnTable carrierEqns =
106  coupledCloud_.carrierEqns<scalar>("1");
107 
108  forAllConstIter(carrierEqnTable, carrierEqns, iter)
109  {
110  const uniformDimensionedScalarField& rhoPhase =
111  mesh().lookupObject<uniformDimensionedScalarField>
112  (
113  IOobject::groupName("rho", iter.key())
114  );
115 
116  S +=
117  ctcdfCloud.rho()/rhoPhase
118  *iter()->residual(dimless/dimTime);
119  }
120 
121  return tS;
122  }
123 
124  // Phase volume source
125  if (isPhase && dims == dimless && notNull(ctcdfCloud))
126  {
127  const uniformDimensionedScalarField& rhoPhase =
128  mesh().lookupObject<uniformDimensionedScalarField>
129  (
130  IOobject::groupName("rho", phaseName)
131  );
132 
133  return
134  ctcdfCloud.rho()/rhoPhase
135  *coupledCloud_
136  .carrierEqn<scalar>(IOobject::groupName("1", phaseName))
137  .residual(dimless/dimTime);
138  }
139 
140  // Mass source
141  if (!isPhase && dims == dimDensity)
142  {
143  tmp<volScalarField::Internal> tS =
145  (
146  "S",
147  mesh(),
149  );
150  volScalarField::Internal& S = tS.ref();
151 
152  const carrierEqnTable carrierEqns =
153  coupledCloud_.carrierEqns<scalar>("rho");
154 
155  forAllConstIter(carrierEqnTable, carrierEqns, iter)
156  {
157  S += iter()->residual(dimDensity/dimTime);
158  }
159 
160  return tS;
161  }
162 
163  // Phase mass source
164  if (isPhase && dims == dimDensity)
165  {
166  return
167  coupledCloud_
168  .carrierEqn<scalar>(IOobject::groupName("rho", phaseName))
169  .residual(dimDensity/dimTime);
170  }
171 
172  FatalError
173  << "Could not create material source "
174  << (isPhase ? "for phase " + phaseName : "").c_str()
175  << " with dimensions " << dims << exit(FatalError);
176 
177  return tmp<volScalarField::Internal>(nullptr);
178 }
179 
180 
181 template<class Type>
182 Foam::tmp<Foam::fvMatrix<Type>> Foam::fv::cloud::Sfield
183 (
184  const VolField<Type>& field,
185  const dimensionSet& dims
186 ) const
187 {
188  const volScalarField::Internal S(this->S(field.group(), dims));
189 
191  field.sources()[name()].sourceCoeff(*this, S);
193  field.sources()[name()].internalCoeff(*this, S);
194 
195  return S*sourceCoeff + fvm::Sp(S*internalCoeff, field);
196 }
197 
198 
199 template<class Type>
200 void Foam::fv::cloud::addSupType
201 (
202  const VolField<Type>& field,
203  fvMatrix<Type>& eqn
204 ) const
205 {
206  if (!coupledCloud_.hasCarrierEqns()) return;
207 
208  const word phaseName = field.group();
209 
210  const bool isPhase = phaseName != word::null;
211  const bool isMultiphase = carriedCloud_.carrierPhaseName() != word::null;
212 
213  const bool hasCarrierEqn = coupledCloud_.hasCarrierEqn(field);
214 
215  const clouds::coupledToConstantDensityFluid& ctcdfCloud =
216  refCastNull<const clouds::coupledToConstantDensityFluid>(cloud_);
217 
219  << "field=" << field.name()
220  << ", eqnField=" << eqn.psi().name()
221  << ", hasCarrierEqn(" << field.name() << ")=" << hasCarrierEqn << endl;
222 
223  // Volume-weighted cloud source into a volume-weighted single-phase
224  // property equation
225  if (!isPhase && !isMultiphase && hasCarrierEqn && notNull(ctcdfCloud))
226  {
227  eqn += ctcdfCloud.rhoByRhoc*coupledCloud_.carrierEqn(field);
228  }
229  // Volume-weighted cloud source into a volume-weighted multiphase mixture
230  // property equation
231  else if (!isPhase && isMultiphase && hasCarrierEqn && notNull(ctcdfCloud))
232  {
233  // This is not possible. The parts of the source for the mixture
234  // property that relate to different phases have already been combined.
235  // To do this correctly would require separating them again and then
236  // scaling them by their individual cloud-to-phase density ratios.
237  fail(eqn, field);
238  }
239  // Generic material source into a volume-weighted property equation
240  else if (!isPhase && !hasCarrierEqn)
241  {
242  eqn += Sfield(field, dimless);
243  }
244  // Not recognised
245  else
246  {
247  fail(eqn, field);
248  }
249 }
250 
251 
252 void Foam::fv::cloud::addSupType
253 (
254  const volScalarField& alphaRhoOrField,
255  fvMatrix<scalar>& eqn
256 ) const
257 {
258  if (!coupledCloud_.hasCarrierEqns()) return;
259 
260  const word phaseName = alphaRhoOrField.group();
261 
262  const bool isPhase = phaseName != word::null;
263  const bool isAlpha =
264  alphaRhoOrField.member() == "alpha"
265  && alphaRhoOrField.dimensions() == dimless;
266  const bool isRho =
267  alphaRhoOrField.member() == "rho"
268  && alphaRhoOrField.dimensions() == dimDensity;
269 
270  const word oneName = IOobject::groupName("1", phaseName);
271  const word rhoName = IOobject::groupName("rho", phaseName);
272 
273  const clouds::coupledToConstantDensityFluid& ctcdfCloud =
274  refCastNull<const clouds::coupledToConstantDensityFluid>(cloud_);
275 
277  << "alphaRhoOrField=" << alphaRhoOrField.name()
278  << ", eqnField=" << eqn.psi().name()
279  << ", hasCarrierEqn(" << oneName << ")="
280  << coupledCloud_.hasCarrierEqn<scalar>(oneName) << endl;
281 
282  // Mass continuity equation
283  if (!isPhase && isRho)
284  {
285  eqn += coupledCloud_.carrierEqn<scalar>(rhoName);
286  }
287  // Phase volume continuity equation
288  else if (isPhase && isAlpha && notNull(ctcdfCloud))
289  {
290  eqn +=
291  ctcdfCloud.rho()
292  /mesh().lookupObject<uniformDimensionedScalarField>(rhoName)
293  *coupledCloud_.carrierEqn<scalar>(oneName);
294  }
295  // Try property equations
296  else
297  {
298  addSupType<scalar>(alphaRhoOrField, eqn);
299  }
300 }
301 
302 
303 template<class Type>
304 void Foam::fv::cloud::addSupType
305 (
306  const volScalarField& alphaOrRho,
307  const VolField<Type>& field,
308  fvMatrix<Type>& eqn
309 ) const
310 {
311  if (!coupledCloud_.hasCarrierEqns()) return;
312 
313  const word phaseName = alphaOrRho.group();
314 
315  const bool isPhase = phaseName != word::null;
316  const bool isAlpha =
317  alphaOrRho.member() == "alpha"
318  && alphaOrRho.dimensions() == dimless;
319  const bool isRho =
320  alphaOrRho.member() == "rho"
321  && alphaOrRho.dimensions() == dimDensity;
322 
323  const bool hasCarrierEqn = coupledCloud_.hasCarrierEqn(field);
324 
325  const word oneName = IOobject::groupName("1", phaseName);
326  const word rhoName = IOobject::groupName("rho", phaseName);
327 
328  const clouds::coupledToConstantDensityFluid& ctcdfCloud =
329  refCastNull<const clouds::coupledToConstantDensityFluid>(cloud_);
330 
332  << "alphaOrRho=" << alphaOrRho.name()
333  << ", field=" << field.name()
334  << ", eqnField=" << eqn.psi().name()
335  << ", hasCarrierEqn(" << field.name() << ")=" << hasCarrierEqn << endl;
336 
337  // Mass-weighted cloud source into a mass-weighted single-phase property
338  // equation
339  if (!isPhase && isRho && hasCarrierEqn && isNull(ctcdfCloud))
340  {
341  eqn += coupledCloud_.carrierEqn(field);
342  }
343  // Volume-weighted cloud source into a mass-weighted multiphase mixture
344  // property equation
345  else if (!isPhase && isRho && hasCarrierEqn && notNull(ctcdfCloud))
346  {
347  eqn += ctcdfCloud.rho()*coupledCloud_.carrierEqn(field);
348  }
349  // Generic material source into a mass-weighted property equation
350  else if (!isPhase && isRho && !hasCarrierEqn)
351  {
352  eqn += Sfield(field, dimDensity);
353  }
354  // Volume-weighted cloud source into a volume-weighted phase property
355  // equation
356  else if (isPhase && isAlpha && hasCarrierEqn && notNull(ctcdfCloud))
357  {
358  eqn +=
359  ctcdfCloud.rho()
360  /mesh().lookupObject<uniformDimensionedScalarField>(rhoName)
361  *coupledCloud_.carrierEqn(field);
362  }
363  // Generic material source into a volume-weighted phase property equation
364  else if (isPhase && isAlpha && !hasCarrierEqn && notNull(ctcdfCloud))
365  {
366  eqn += Sfield(field, dimless);
367  }
368  // Not recognised
369  else
370  {
371  fail(eqn, alphaOrRho, field);
372  }
373 }
374 
375 
376 void Foam::fv::cloud::addSupType
377 (
378  const volScalarField& alphaOrRho,
379  const volScalarField& rhoOrField,
380  fvMatrix<scalar>& eqn
381 ) const
382 {
383  if (!coupledCloud_.hasCarrierEqns()) return;
384 
385  const word phaseName = alphaOrRho.group();
386 
387  const bool isPhase = phaseName != word::null;
388  const bool isAlpha =
389  alphaOrRho.member() == "alpha"
390  && alphaOrRho.dimensions() == dimless;
391  const bool isRhoField =
392  rhoOrField.member() == "rho"
393  && rhoOrField.dimensions() == dimDensity;
394 
396  << "alphaOrRho=" << alphaOrRho.name()
397  << ", rhoOrField=" << rhoOrField.name()
398  << ", eqnField=" << eqn.psi().name()
399  << ", hasCarrierEqn(" << rhoOrField.name() << ")="
400  << coupledCloud_.hasCarrierEqn(rhoOrField) << endl;
401 
402  // Phase mass continuity equation
403  if (isPhase && isAlpha && isRhoField)
404  {
405  eqn += coupledCloud_.carrierEqn(rhoOrField);
406  }
407  // Try property equations
408  else
409  {
410  addSupType<scalar>(alphaOrRho, rhoOrField, eqn);
411  }
412 }
413 
414 
415 template<class Type>
416 void Foam::fv::cloud::addSupType
417 (
418  const volScalarField& alpha,
419  const volScalarField& rho,
420  const VolField<Type>& field,
421  fvMatrix<Type>& eqn
422 ) const
423 {
424  if (!coupledCloud_.hasCarrierEqns()) return;
425 
426  const bool hasCarrierEqn = coupledCloud_.hasCarrierEqn(field);
427 
429  << "alpha=" << alpha.name()
430  << ", rho=" << rho.name()
431  << ", field=" << field.name()
432  << ", eqnField=" << eqn.psi().name()
433  << ", hasCarrierEqn(" << field.name() << ")=" << hasCarrierEqn << endl;
434 
435  // Mass-weighted cloud source into a mass-weighted phase property equation
436  if (hasCarrierEqn)
437  {
438  eqn += coupledCloud_.carrierEqn(field);
439  }
440  // Generic material source into a mass-weighted phase property equation
441  else
442  {
443  eqn += Sfield(field, dimDensity);
444  }
445 }
446 
447 
448 // * * * * * * * * * * * * * * * * Destructor * * * * * * * * * * * * * * * //
449 
451 {}
452 
453 
454 // * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * * //
455 
456 bool Foam::fv::cloud::addsSupToField(const word& fieldName) const
457 {
458  const LagrangianModels& models = cloud_.LagrangianModels();
459 
460  const word phaseName = IOobject::group(fieldName);
461 
462  #define hasCarrierEqnType(Type, nullArg) \
463  || coupledCloud_.hasCarrierEqn<Type>(fieldName)
464 
465  return
466  models.addsSupToField
467  (
468  word::null,
469  clouds::carried::nameToCarrierName("1", phaseName)
470  )
471  || models.addsSupToField
472  (
473  word::null,
474  clouds::carried::nameToCarrierName("rho", phaseName)
475  )
477 }
478 
479 
481 {
482  if (!coupledCloud_.hasCarrierEqns()) return;
483 
484  const word oneName = "1";
485 
486  const clouds::coupledToConstantDensityFluid& ctcdfCloud =
487  refCastNull<const clouds::coupledToConstantDensityFluid>(cloud_);
488 
490  << ", eqnField=" << eqn.psi().name()
491  << ", hasCarrierEqn(" << oneName << ")="
492  << coupledCloud_.hasCarrierEqn<scalar>(oneName) << endl;
493 
494  // Volume continuity equation
495  if (notNull(ctcdfCloud))
496  {
497  eqn += ctcdfCloud.rhoByRhoc*coupledCloud_.carrierEqn<scalar>(oneName);
498  }
499  // Not recognised
500  else
501  {
502  fail(eqn);
503  }
504 }
505 
506 
508 
509 
511 
512 
514 
515 
516 void Foam::fv::cloud::correct()
517 {
518  if (mesh().foundObject<pimpleNoLoopControl>(solutionControl::typeName))
519  {
520  const pimpleNoLoopControl& pimple =
522 
523  const bool outerCorrectors =
524  cloud_.mesh().solution().lookup<bool>("outerCorrectors");
525 
526  if (pimple.firstIter() || outerCorrectors)
527  {
528  cloudPtr_->solve(pimple.firstIter(), pimple.finalIter());
529  }
530  }
531  else
532  {
533  cloudPtr_->solve(true, true);
534  }
535 }
536 
537 
539 {
540  cloudPtr_->storePosition();
541 }
542 
543 
545 {
546  return true;
547 }
548 
549 
551 {
552  cloudPtr_->topoChange(map);
553 }
554 
555 
557 {
558  cloudPtr_->mapMesh(map);
559 }
560 
561 
563 {
564  cloudPtr_->distribute(map);
565 }
566 
567 
568 // ************************************************************************* //
#define forAllConstIter(Container, container, iter)
Iterate across all elements in the container object of type.
Definition: UList.H:492
Field with dimensions and associated with geometry type GeoMesh which is used to size the field and a...
static tmp< DimensionedField< Type, GeoMesh, PrimitiveField > > New(const word &name, const GeoMesh &mesh, const dimensionSet &, const PrimitiveField< Type > &)
Return a temporary field constructed from name, mesh,.
Generic GeometricField class.
const Sources & sources() const
Return const-reference to the sources.
DimensionedField< Type, GeoMesh, PrimitiveField > Internal
Type of the internal field from which this GeometricField is derived.
static word group(const word &name)
Return group (extension part of name)
Definition: IOobject.C:131
word group() const
Return group (extension part of name)
Definition: IOobject.C:321
const word & name() const
Return name.
Definition: IOobject.H:307
static word groupName(Name name, const word &group)
static word fieldsName(const AlphaRhoFieldType &alphaRhoField, const AlphaRhoFieldTypes &... alphaRhoFields)
Return the name of the product of the fields associated with a.
List of Lagrangian models, constructed as a (Lagrangian) mesh object. Provides similar functions to t...
bool addsSupToField(const word &fieldName) const
Return true if the LagrangianModels adds a source term to the.
LagrangianModels(const LagrangianMesh &mesh)
Construct for a mesh.
static word nameToCarrierName(const word &name)
Convert a name to its disambiguated carrier equivalent name. I.e.,.
Definition: carried.C:273
Base class for clouds which are coupled to a constant density fluid.
const dimensionedScalar rhoByRhoc
Cloud/carrier density ratio.
ITstream & lookup(const word &, bool recursive=false, bool patternMatch=true) const
Find and return an entry data stream.
Definition: dictionary.C:669
Dimension set for the base types.
Definition: dimensionSet.H:125
A special matrix type and solver, designed for finite volume solutions of scalar equations....
Definition: fvMatrix.H:118
VolField< Type > & psi()
Definition: fvMatrix.H:289
const fvSolution & solution() const
Return the fvSolution.
Definition: fvMesh.C:1803
Finite volume model that solves for the evolution of a cloud. Provides two-way coupling with a finite...
Definition: cloud_fvModel.H:56
virtual bool movePoints()
Update for mesh motion.
virtual void topoChange(const polyTopoChangeMap &)
Update topology using the given map.
virtual void distribute(const polyDistributionMap &)
Redistribute or update using the given distribution map.
virtual void preUpdateMesh()
Prepare for mesh update.
virtual void mapMesh(const polyMeshMap &)
Update from another mesh using the given map.
virtual ~cloud()
Destructor.
virtual void addSup(fvMatrix< scalar > &eqn) const
Add a source term to a field-less proxy equation.
virtual bool addsSupToField(const word &fieldName) const
Return true if the fvModel adds a source term to the given.
const Type & lookupObject(const word &name) const
Lookup and return the object of the given Type and name.
Pimple no-loop control class. Implements various option flags, but leaves loop controls to the deriva...
Class containing mesh-to-mesh mapping information after a mesh distribution where we send parts of me...
Class containing mesh-to-mesh mapping information.
Definition: polyMeshMap.H:51
Class containing mesh-to-mesh mapping information after a change in polyMesh topology.
const fvMesh & mesh() const
Return the mesh.
A class for managing temporary objects.
Definition: tmp.H:55
A class for handling words, derived from string.
Definition: word.H:63
static const word null
An empty word.
Definition: word.H:78
#define hasCarrierEqnType(Type, nullArg)
pimpleControl pimple(mesh)
Foam::fvMesh mesh(Foam::IOobject(regionName, runTime.name(), runTime, Foam::IOobject::MUST_READ), false)
#define FatalErrorInFunction
Report an error message using Foam::FatalError.
Definition: error.H:334
#define IMPLEMENT_FV_MODEL_ADD_FIELD_SUP(Type, modelType)
Definition: fvModelM.H:33
#define IMPLEMENT_FV_MODEL_ADD_ALPHA_RHO_FIELD_SUP(Type, modelType)
Definition: fvModelM.H:71
#define IMPLEMENT_FV_MODEL_ADD_RHO_FIELD_SUP(Type, modelType)
Definition: fvModelM.H:51
Calculate the matrix for implicit and explicit sources.
rho
Definition: pEqn.H:1
volScalarField alpha(IOobject("alpha", runTime.name(), mesh, IOobject::READ_IF_PRESENT, IOobject::AUTO_WRITE), lambda *max(Ua &U, zeroSensitivity))
#define DebugInFunction
Report an information message using Foam::Info.
void correct(const RdeltaTType &rDeltaT, const RhoType &rho, volScalarField &psi, const surfaceScalarField &phiCorr, const SpType &Sp)
defineTypeNameAndDebug(bound, 0)
tmp< fvMatrix< Type > > S(const Pair< tmp< volScalarField::Internal >> &, const VolField< Type > &)
tmp< fvMatrix< Type > > Sp(const volScalarField::Internal &, const VolField< Type > &)
Namespace for OpenFOAM.
errorManipArg< error, int > exit(error &err, const int errNo=1)
Definition: errorManip.H:124
const dimensionSet & dimless
Definition: dimensions.C:138
Ostream & endl(Ostream &os)
Add newline and flush stream.
Definition: Ostream.H:288
String typeName(const std::type_info &info)
Return the un-mangled name given the standard type info.
FOR_ALL_FIELD_TYPES(makeDimensionedPointFieldFunctions)
bool notNull(const T &t)
Return true if t is not a reference to the nullObject of type T.
Definition: nullObjectI.H:64
const dimensionSet & dimTime
Definition: dimensions.C:142
VolField< scalar > volScalarField
Definition: volFieldsFwd.H:62
const dimensionSet & dimDensity
Definition: dimensions.C:158
word name(const LagrangianState state)
Return a string representation of a Lagrangian state enumeration.
bool isNull(const T &t)
Return true if t is a reference to the nullObject of type T.
Definition: nullObjectI.H:58
error FatalError
dimensioned< scalar > dimensionedScalar
Dimensioned scalar obtained from generic dimensioned type.
labelList fv(nPoints)
Typedefs for UniformDimensionedField.