patchInjection.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 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 "LagrangianSubMesh.H"
27 #include "patchInjection.H"
28 #include "LagrangianFields.H"
29 #include "cloud.H"
31 #include "tetIndices.H"
33 
34 // * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
35 
36 namespace Foam
37 {
38 namespace Lagrangian
39 {
42 }
43 }
44 
45 
46 // * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * //
47 
48 void Foam::Lagrangian::patchInjection::readCoeffs(const dictionary& modelDict)
49 {
50  patchName_ = modelDict.lookup<word>("patch");
51 
52  numberRate_.reset
53  (
55  (
56  "numberRate",
57  mesh().time().userUnits(),
58  dimRate,
59  modelDict
60  ).ptr()
61  );
62 
63  numberDeferred_ = 0;
64 }
65 
66 
67 void Foam::Lagrangian::patchInjection::calcSumAreas()
68 {
69  const polyPatch& patch = mesh().mesh().boundaryMesh()[patchName_];
70 
71  // Create a point field mid-way through the fraction. We distribute
72  // particles uniformly through the time-step, so using the mid-time-step
73  // geometry ensures that the particles get distributed evenly even in the
74  // presence of motion.
75  const tmp<pointField> tPoints
76  (
77  mesh().mesh().moving()
78  ? mesh().mesh().oldPoints()/2 + mesh().mesh().points()/2
79  : tmp<pointField>(mesh().mesh().points())
80  );
81  const pointField& points = tPoints();
82 
83  // Count the number of triangles in each face
84  labelList patchFaceNTris(patch.size(), label(0));
85  forAll(patch, patchFacei)
86  {
87  patchFaceNTris[patchFacei] = patch[patchFacei].nTriangles();
88  }
89 
90  // Construct cumulative sums of the areas in the patch faces and patch
91  // face-triangles
92  patchFaceSumArea_.resize(patch.size());
93  patchFaceSumArea_ = 0;
94  patchFaceTriSumArea_.resize(patchFaceNTris);
95  forAll(patch, patchFacei)
96  {
97  const label facei = patch.start() + patchFacei;
98  const face& f = patch[patchFacei];
99 
100  for (label fTrii = 0; fTrii < f.nTriangles(); ++ fTrii)
101  {
102  patchFaceSumArea_[patchFacei] +=
103  tetIndices(mesh().mesh().faceOwner()[facei], facei, fTrii + 1)
104  .faceTri(mesh().mesh(), points)
105  .mag();
106 
107  patchFaceTriSumArea_[patchFacei][fTrii] =
108  patchFaceSumArea_[patchFacei];
109  }
110  }
111  for (label patchFacei = 1; patchFacei < patch.size(); ++ patchFacei)
112  {
113  patchFaceSumArea_[patchFacei] += patchFaceSumArea_[patchFacei - 1];
114  }
115 
116  // Construct a cumulative sum of the areas across the processes
117  procSumArea_ = scalar(-vGreat);
118  procSumArea_[Pstream::myProcNo()] =
119  patch.size() ? patchFaceSumArea_.last() : scalar(0);
120  Pstream::listCombineGather(procSumArea_, maxEqOp<scalar>());
121  Pstream::listCombineScatter(procSumArea_);
122  for (label proci = 1; proci < Pstream::nProcs(); proci ++)
123  {
124  procSumArea_[proci] += procSumArea_[proci - 1];
125  }
126 }
127 
128 
129 // * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
130 
132 (
133  const word& name,
134  const LagrangianMesh& mesh,
135  const dictionary& modelDict,
136  const dictionary& stateDict
137 )
138 :
140  cloudLagrangianModel(static_cast<const LagrangianModel&>(*this)),
141  patchName_(word::null),
142  numberRate_(nullptr),
143  numberDeferred_(stateDict.lookupOrDefault<scalar>("numberDeferred", 0)),
144  globalRndGen_("globalRndGen", stateDict, name, true),
145  localRndGen_("localRndGen", stateDict, name, false),
146  timeIndex_(-1),
147  procSumArea_(Pstream::nProcs()),
148  patchFaceSumArea_(),
149  patchFaceTriSumArea_()
150 {
151  readCoeffs(modelDict);
152 
153  calcSumAreas();
154 }
155 
156 
157 // * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * * //
158 
160 (
162  const LagrangianSubMesh&
163 ) const
164 {
165  const scalar t1 = mesh.time().value();
166  const scalar t0 = t1 - mesh.time().deltaT().value();
167 
168  // Restart the generators if necessary and set the time index up to date
169  localRndGen_.start(timeIndex_ == db().time().timeIndex());
170  globalRndGen_.start(timeIndex_ == db().time().timeIndex());
171  timeIndex_ = db().time().timeIndex();
172 
173  // Calculate the number of particles to inject. Round down to get an
174  // integer number. Store the excess to apply at a later time.
175  const scalar number = numberRate_->integral(t0, t1) + numberDeferred_;
176  const label numberInt = floor(number);
177  numberDeferred_ = number - numberInt;
178 
179  // Inject at random times throughout the time-step
180  const scalarField fraction(globalRndGen_.scalar01(numberInt));
181 
182  // Binary search for a given volume within a supplied cumulative volume sum.
183  // Once the interval has been found, subtract the lower value from that
184  // volume so that a subsequent binary search can be done on a sub-set of
185  // volumes. Note that the supplied cumulative volume sum does not include
186  // the leading zero.
187  auto findVolume = []
188  (
189  const scalarUList& volumes,
190  scalar& volume
191  )
192  {
193  label i0 = -1, i1 = volumes.size() - 1;
194 
195  while (i0 + 1 != i1)
196  {
197  const label i = (i0 + i1)/2;
198 
199  (volume < volumes[i] ? i1 : i0) = i;
200  }
201 
202  if (i0 != -1)
203  {
204  volume -= volumes[i0];
205  }
206 
207  return i1;
208  };
209 
210  // Get the mesh index of the first face in the patch
211  const polyPatch& patch = mesh.mesh().boundaryMesh()[patchName_];
212  const label facei0 = patch.start();
213 
214  // Initialise storage for the injection geometry and topology. This is
215  // dynamic as we don't know how much will end up on each processor yet.
216  DynamicList<barycentric> injectCoordinates(numberInt/Pstream::nProcs());
217  DynamicList<label> injectCells(numberInt/Pstream::nProcs());
218  DynamicList<label> injectFaces(numberInt/Pstream::nProcs());
219  DynamicList<label> injectFaceTris(numberInt/Pstream::nProcs());
220  DynamicList<scalar> injectFraction(numberInt/Pstream::nProcs());
221 
222  // Create a (global) list of areas at which to inject. Each area is
223  // searched for in the cumulative lists to identify a triangle into
224  // which to inject.
225  scalarField area(globalRndGen_.scalar01(numberInt)*procSumArea_.last());
226  forAll(area, areai)
227  {
228  const label proci = findVolume(procSumArea_, area[areai]);
229 
230  if (Pstream::myProcNo() == proci)
231  {
232  const label patchFacei =
233  findVolume(patchFaceSumArea_, area[areai]);
234  const label facei = facei0 + patchFacei;
235  const label faceTrii =
236  findVolume(patchFaceTriSumArea_[patchFacei], area[areai]);
237 
238  const barycentric2D r = barycentric2D01(localRndGen_);
239 
240  injectCoordinates.append(barycentric(0, r.a(), r.b(), r.c()));
241  injectCells.append(mesh.mesh().faceOwner()[facei]);
242  injectFaces.append(facei);
243  injectFaceTris.append(faceTrii + 1);
244  injectFraction.append(fraction[areai]);
245  }
246  }
247 
248  // Create the particles at the identified locations
249  LagrangianSubMesh injectionMesh =
250  mesh.inject
251  (
252  *this,
253  barycentricField(injectCoordinates),
254  labelField(injectCells),
255  labelField(injectFaces),
256  labelField(injectFaceTris),
258  scalarField(injectFraction)
259  );
260 
261  // Execute functions for this sub mesh. This ensures that flux fields and
262  // similar include the amount injected through the patch faces.
264  (
266  (
267  injectionMesh.mesh(),
268  static_cast<LagrangianGroup>
269  (
270  static_cast<label>(LagrangianGroup::onPatchZero)
271  + patch.index()
272  ),
273  injectionMesh.size(),
274  injectionMesh.start()
275  ).sub
276  (
278  (
280  )
281  )
282  );
283 
284  return injectionMesh;
285 }
286 
287 
289 {
290  calcSumAreas();
291 }
292 
293 
295 {
296  calcSumAreas();
297 }
298 
299 
301 (
302  const polyDistributionMap& map
303 )
304 {
305  calcSumAreas();
306 }
307 
308 
310 {
311  if (mesh().mesh().moving())
312  {
313  calcSumAreas();
314  }
315 }
316 
317 
319 {
320  if (LagrangianInjection::read(modelDict))
321  {
322  readCoeffs(modelDict);
323  return true;
324  }
325  else
326  {
327  return false;
328  }
329 }
330 
331 
333 {
335 
336  writeEntry(os, "numberDeferred", numberDeferred_);
337  writeEntry(os, "globalRndGen", globalRndGen_);
338 }
339 
340 
342 {
344 
345  writeEntry(os, "localRndGen", localRndGen_);
346 }
347 
348 
349 // ************************************************************************* //
#define forAll(list, i)
Loop across all elements in list.
Definition: UList.H:433
Macros for easy insertion into run-time selection tables.
Templated 2D Barycentric derived from VectorSpace. Has 3 components, one of which is redundant.
Definition: Barycentric2D.H:53
const Cmpt & a() const
const Cmpt & b() const
const Cmpt & c() const
Field with dimensions and associated with geometry type GeoMesh which is used to size the field and a...
A 1D vector of objects of type <T> that resizes itself as necessary to accept the new objects.
Definition: DynamicList.H:78
DynamicList< T, SizeInc, SizeMult, SizeDiv > & append(const T &)
Append an element at the end of the list.
Definition: DynamicListI.H:296
static autoPtr< Function1< scalar > > New(const word &name, const Function1s::unitConversions &units, const dictionary &dict)
Select from dictionary.
Definition: Function1New.C:32
Base class for Lagrangian injections. Minimal wrapper over LagrangianSource. Implements some utility ...
Class containing Lagrangian geometry and topology.
static const word fractionName
Name of the tracked fraction field.
Base class for Lagrangian models.
virtual bool read(const dictionary &modelDict)
Read dictionary.
const LagrangianMesh & mesh() const
The mesh.
Mesh that relates to a sub-section of a Lagrangian mesh. This is used to construct fields that relate...
SubList< Type > sub(const List< Type > &list) const
Return a sub-list corresponding to this sub-mesh.
label size() const
Return size.
const LagrangianMesh & mesh() const
Return the mesh.
label start() const
Return start.
virtual void correct()
Correct the LagrangianModel.
patchInjection(const word &name, const LagrangianMesh &mesh, const dictionary &modelDict, const dictionary &stateDict)
Construct from components.
virtual void writeState(Ostream &os) const
Write state.
virtual void topoChange(const polyTopoChangeMap &)
Update topology using the given map.
LagrangianSubMesh modify(LagrangianMesh &mesh, const LagrangianSubMesh &) const
Create new elements in the Lagrangian mesh.
virtual void distribute(const polyDistributionMap &)
Redistribute or update using the given distribution map.
virtual void mapMesh(const polyMeshMap &)
Update from another mesh using the given map.
virtual void writeProcessorState(Ostream &os) const
Write state.
virtual bool read(const dictionary &modelDict)
Read dictionary.
An Ostream is an abstract base class for all output systems (streams, files, token lists,...
Definition: Ostream.H:57
Inter-processor communications stream.
Definition: Pstream.H:56
static void listCombineGather(const List< commsStruct > &comms, List< T > &Value, const CombineOp &cop, const int tag, const label comm)
static void listCombineScatter(const List< commsStruct > &comms, List< T > &Value, const int tag, const label comm)
Scatter data. Reverse of combineGather.
dimensionedScalar deltaT() const
Return time step.
Definition: TimeStateI.H:46
A 1D vector of objects of type <T>, where the size of the vector is known and can be used for subscri...
Definition: UList.H:74
label size() const
Return the number of elements in the UList.
Definition: UListI.H:311
static label nProcs(const label communicator=0)
Number of processes in parallel run.
Definition: UPstream.H:411
static int myProcNo(const label communicator=0)
Number of this process (starting from masterNo() = 0)
Definition: UPstream.H:429
List of references to the cloud function objects. Designed to be constructed temporarily for the scop...
virtual void postCrossFaces(const LagrangianSubScalarSubField &fraction)
Hook following face crossings of a specific sub-mesh.
Base class for Lagrangian models that refer to a cloud. Not a Lagrangian model in itself....
Base class for clouds. Provides a basic evolution algorithm, models, and a database for caching deriv...
Definition: cloud.H:63
A list of keywords followed by any number of values (e.g. words and numbers) or sub-dictionaries.
Definition: dictionary.H:162
const Type & value() const
Return const reference to value.
const Time & time() const
Return the top-level database.
Definition: fvMesh.H:420
const polyMesh & mesh() const
Return reference to polyMesh.
Definition: fvMesh.H:443
const Type & lookupObject(const word &name) const
Lookup and return the object of the given Type and name.
label index() const
Return the index of this patch in the boundaryMesh.
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
virtual const labelList & faceOwner() const
Return face owner.
Definition: polyMesh.C:1357
const polyBoundaryMesh & boundaryMesh() const
Return boundary mesh.
Definition: polyMesh.H:401
A patch is a list of labels that address the faces in the global face list.
Definition: polyPatch.H:70
label start() const
Return start label of this patch in the polyMesh face list.
Definition: polyPatch.H:280
Class containing mesh-to-mesh mapping information after a change in polyMesh topology.
bool writeState(const bool write) const
Write state.
Definition: stateModel.C:104
virtual void writeProcessorState(Ostream &os) const
Write processor state.
Definition: stateModel.C:132
A class for handling words, derived from string.
Definition: word.H:62
Foam::fvMesh mesh(Foam::IOobject(regionName, runTime.name(), runTime, Foam::IOobject::MUST_READ), false)
const pointField & points
addToRunTimeSelectionTable(LagrangianModel, constantCoefficientVirtualMass, dictionary)
defineTypeNameAndDebug(constantCoefficientVirtualMass, 0)
Namespace for OpenFOAM.
Barycentric< scalar > barycentric
A scalar version of the templated Barycentric.
Definition: barycentric.H:45
List< label > labelList
A List of labels.
Definition: labelList.H:56
Field< barycentric > barycentricField
Barycentric field.
intWM_LABEL_SIZE_t label
A label is an int32_t or int64_t as specified by the pre-processor macro WM_LABEL_SIZE.
Definition: label.H:59
const dimensionSet dimRate
vectorField pointField
pointField is a vectorField.
Definition: pointFieldFwd.H:42
Field< scalar > scalarField
Specialisation of Field<T> for scalar.
void writeEntry(Ostream &os, const HashTable< T, Key, Hash > &ht)
Definition: HashTableIO.C:96
Field< label > labelField
Specialisation of Field<T> for label.
Definition: labelField.H:49
word name(const LagrangianState state)
Return a string representation of a Lagrangian state enumeration.
LagrangianGroup
Lagrangian group enumeration.
barycentric2D barycentric2D01(randomGenerator &rndGen)
Generate a random barycentric coordinate within the unit triangle.
Definition: barycentric2D.C:31
label timeIndex
Definition: getTimeIndex.H:4
labelList f(nPoints)