BlendedInterfacialModel.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) 2014-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 
28 #include "surfaceInterpolate.H"
29 #include "zeroDimensionalFvMesh.H"
30 #include "mathematicalConstants.H"
31 #include "writeFile.H"
32 #include "triFace.H"
33 #include "noSetWriter.H"
34 #include "noSurfaceWriter.H"
35 
36 // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
37 
38 namespace Foam
39 {
40 namespace blendedInterfacialModel
41 {
42 
43 template<class GeoField>
45 
46 template<>
48 {
49  return f;
50 }
51 
52 template<>
54 {
55  return fvc::interpolate(f);
56 }
57 
58 template<class ModelType>
59 inline bool valid(const PtrList<ModelType>& l)
60 {
61  forAll(l, i)
62  {
63  if (l.set(i))
64  {
65  return true;
66  }
67  }
68  return false;
69 }
70 
71 } // End namespace blendedInterfacialModel
72 } // End namespace Foam
73 
74 
75 // * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * //
76 
77 template<class ModelType>
79 {
80  // Only generate warnings once per timestep
81  const label timeIndex = interface_.mesh().time().timeIndex();
82  if (checkTimeIndex_ == timeIndex) return;
83  checkTimeIndex_ = timeIndex;
84 
85  const phaseModel& phase1 = interface_.phase1();
86  const phaseModel& phase2 = interface_.phase2();
87 
88  const bool can1In2 = blending_->canBeContinuous(1);
89  const bool can2In1 = blending_->canBeContinuous(0);
90  const bool canS = blending_->canSegregate();
91 
92  // Warnings associated with redundant model specification
93 
94  if
95  (
96  !can1In2
97  && (
98  model1DispersedIn2_.valid()
99  || blendedInterfacialModel::valid(models1DispersedIn2Displaced_)
100  )
101  )
102  {
104  << "A " << ModelType::typeName << " was provided for "
105  << dispersedPhaseInterface(phase1, phase2).name()
106  << " but the associated blending does not permit " << phase2.name()
107  << " to be continuous so this model will not be used" << endl;
108  }
109 
110  if
111  (
112  !can2In1
113  && (
114  model2DispersedIn1_.valid()
115  || blendedInterfacialModel::valid(models2DispersedIn1Displaced_)
116  )
117  )
118  {
120  << "A " << ModelType::typeName << " was provided for "
121  << dispersedPhaseInterface(phase2, phase1).name()
122  << " but the associated blending does not permit " << phase1.name()
123  << " to be continuous so this model will not be used" << endl;
124  }
125 
126  if
127  (
128  !canS
129  && (
130  model1SegregatedWith2_.valid()
131  || blendedInterfacialModel::valid(models1SegregatedWith2Displaced_)
132  )
133  )
134  {
136  << "A " << ModelType::typeName << " was provided for "
137  << segregatedPhaseInterface(phase1, phase2).name()
138  << " but the associated blending does not permit segregation"
139  << " so this model will not be used" << endl;
140  }
141 
142  if
143  (
144  modelGeneral_.valid()
145  && (can1In2 || can2In1 || canS)
146  && (!can1In2 || model1DispersedIn2_.valid())
147  && (!can2In1 || model2DispersedIn1_.valid())
148  && (!canS || model1SegregatedWith2_.valid())
149  )
150  {
152  << "A " << ModelType::typeName << " was provided for "
153  << phaseInterface(phase1, phase2).name()
154  << " but other displaced and/or segregated models apply"
155  << " across the entire phase fraction space so this model"
156  << " will not be used" << endl;
157  }
158 
159  forAll(interface_.fluid().phases(), phasei)
160  {
161  const phaseModel& phaseD = interface_.fluid().phases()[phasei];
162 
163  if
164  (
165  modelsGeneralDisplaced_.set(phasei)
166  && (can1In2 || can2In1 || canS)
167  && (!can1In2 || models1DispersedIn2Displaced_.set(phasei))
168  && (!can2In1 || models2DispersedIn1Displaced_.set(phasei))
169  && (!canS || models1SegregatedWith2Displaced_.set(phasei))
170  )
171  {
173  << "A " << ModelType::typeName << " was provided for "
174  << displacedPhaseInterface(phase1, phase2, phaseD).name()
175  << " but other displaced and/or segregated models apply"
176  << " across the entire phase fraction space so this model"
177  << " will not be used" << endl;
178  }
179  }
180 
181  // Warnings associated with gaps in the blending space
182 
183  if (can1In2 && !modelGeneral_.valid() && !model1DispersedIn2_.valid())
184  {
186  << "Blending for " << ModelType::typeName << "s permits "
187  << phase2.name() << " to become continuous, but no model was "
188  << "provided for " << dispersedPhaseInterface(phase1, phase2).name()
189  << ". Consider adding a model for this configuration (or for "
190  << phaseInterface(phase1, phase2).name() << "), or if no model is "
191  << "needed then add a \"none\" model to suppress this warning."
192  << endl;
193  }
194 
195  if (can2In1 && !modelGeneral_.valid() && !model2DispersedIn1_.valid())
196  {
198  << "Blending for " << ModelType::typeName << "s permits "
199  << phase1.name() << " to become continuous, but no model was "
200  << "provided for " << dispersedPhaseInterface(phase2, phase1).name()
201  << ". Consider adding a model for this configuration (or for "
202  << phaseInterface(phase1, phase2).name() << "), or if no model is "
203  << "needed then add a \"none\" model to suppress this warning."
204  << endl;
205  }
206 
207  if (canS && !modelGeneral_.valid() && !model1SegregatedWith2_.valid())
208  {
210  << "Blending for " << ModelType::typeName << "s permits "
211  << "segregation but no model was provided for "
212  << segregatedPhaseInterface(phase2, phase1).name()
213  << ". Consider adding a model for this configuration (or for "
214  << phaseInterface(phase1, phase2).name() << "), or if no model is "
215  << "needed then add a \"none\" model to suppress this warning."
216  << endl;
217  }
218 }
219 
220 
221 template<class ModelType>
222 template<class GeoMesh>
224 (
225  const UPtrList<const volScalarField>& alphas,
234 ) const
235 {
236  typedef GeometricField<scalar, GeoMesh> scalarGeoField;
237 
238  // Create a constant field
239  auto constant = [&](const scalar k)
240  {
241  return
243  (
244  Foam::name(k),
245  alphas.first().mesh(),
247  );
248  };
249 
250  // Get the dispersed blending functions
251  tmp<scalarGeoField> F1D2, F2D1;
252  if
253  (
254  model1DispersedIn2_.valid()
255  || model1SegregatedWith2_.valid()
256  || blendedInterfacialModel::valid(models1DispersedIn2Displaced_)
257  || blendedInterfacialModel::valid(models1SegregatedWith2Displaced_)
258  )
259  {
260  F1D2 =
261  blendedInterfacialModel::interpolate<scalarGeoField>
262  (
263  blending_->f1DispersedIn2(alphas)
264  );
265  }
266  if
267  (
268  model2DispersedIn1_.valid()
269  || model1SegregatedWith2_.valid()
270  || blendedInterfacialModel::valid(models2DispersedIn1Displaced_)
271  || blendedInterfacialModel::valid(models1SegregatedWith2Displaced_)
272  )
273  {
274  F2D1 =
275  blendedInterfacialModel::interpolate<scalarGeoField>
276  (
277  blending_->f2DispersedIn1(alphas)
278  );
279  }
280 
281  // Construct non-displaced coefficients
282  {
283  if (model1DispersedIn2_.valid())
284  {
285  f1D2 = F1D2().clone();
286  }
287 
288  if (model2DispersedIn1_.valid())
289  {
290  f2D1 = F2D1().clone();
291  }
292 
293  if (model1SegregatedWith2_.valid())
294  {
295  fS = constant(1);
296  if (f1D2.valid()) { fS.ref() -= f1D2(); }
297  if (f2D1.valid()) { fS.ref() -= f2D1(); }
298  }
299 
300  if (modelGeneral_.valid())
301  {
302  fG = constant(1);
303  if (f1D2.valid()) { fG.ref() -= f1D2(); }
304  if (f2D1.valid()) { fG.ref() -= f2D1(); }
305  if (fS.valid()) { fG.ref() -= fS(); }
306  }
307  }
308 
309  // Get the displaced blending function
310  tmp<scalarGeoField> FD;
311  if
312  (
313  blendedInterfacialModel::valid(modelsGeneralDisplaced_)
314  || blendedInterfacialModel::valid(models1DispersedIn2Displaced_)
315  || blendedInterfacialModel::valid(models2DispersedIn1Displaced_)
316  || blendedInterfacialModel::valid(models1SegregatedWith2Displaced_)
317  )
318  {
319  FD =
320  blendedInterfacialModel::interpolate<scalarGeoField>
321  (
322  blending_->fDisplaced(alphas)
323  );
324  }
325 
326  // Construct displaced coefficients
327  tmp<scalarGeoField> fDSum;
328  if
329  (
330  blendedInterfacialModel::valid(modelsGeneralDisplaced_)
331  || blendedInterfacialModel::valid(models1DispersedIn2Displaced_)
332  || blendedInterfacialModel::valid(models2DispersedIn1Displaced_)
333  || blendedInterfacialModel::valid(models1SegregatedWith2Displaced_)
334  )
335  {
336  fDSum = constant(0);
337 
338  forAll(alphas, phasei)
339  {
340  const phaseModel& phaseD = interface_.fluid().phases()[phasei];
341 
342  if (interface_.contains(phaseD)) continue;
343 
344  // Weight the contribution of a dispersed model for this phase
345  // according to the phase fraction in the subset of phases that are
346  // not part of this interface. This seems like a reasonable
347  // assumption if a stochastic viewpoint is taken. However, it is
348  // possible that other forms may be desired in which case this
349  // could be abstracted and controlled by the blending method.
350  tmp<scalarGeoField> alpha;
351  if
352  (
353  models1DispersedIn2Displaced_.set(phasei)
354  || models2DispersedIn1Displaced_.set(phasei)
355  || models1SegregatedWith2Displaced_.set(phasei)
356  || modelsGeneralDisplaced_.set(phasei)
357  )
358  {
359  alpha =
360  blendedInterfacialModel::interpolate<scalarGeoField>
361  (
362  alphas[phasei]
363  /max
364  (
365  1
366  - alphas[interface_.phase1().index()]
367  - alphas[interface_.phase2().index()],
368  phaseD.residualAlpha()
369  )
370  );
371  }
372 
373  if (models1DispersedIn2Displaced_.set(phasei))
374  {
375  f1D2D.set(phasei, alpha()*FD()*F1D2());
376  fDSum.ref() += f1D2D[phasei];
377  }
378 
379  if (models2DispersedIn1Displaced_.set(phasei))
380  {
381  f2D1D.set(phasei, alpha()*FD()*F2D1());
382  fDSum.ref() += f2D1D[phasei];
383  }
384 
385  if (models1SegregatedWith2Displaced_.set(phasei))
386  {
387  fSD.set(phasei, alpha()*FD());
388  if (f1D2D.set(phasei)) fSD[phasei] -= f1D2D[phasei];
389  if (f2D1D.set(phasei)) fSD[phasei] -= f2D1D[phasei];
390  fDSum.ref() += fSD[phasei];
391  }
392 
393  if (modelsGeneralDisplaced_.set(phasei))
394  {
395  fGD.set(phasei, alpha()*FD());
396  if (f1D2D.set(phasei)) fGD[phasei] -= f1D2D[phasei];
397  if (f2D1D.set(phasei)) fGD[phasei] -= f2D1D[phasei];
398  if (fSD.set(phasei)) fGD[phasei] -= fSD[phasei];
399  fDSum.ref() += fGD[phasei];
400  }
401  }
402  }
403 
404  // Remove the displaced part from the non-displaced coefficients
405  if (fDSum.valid())
406  {
407  if (f1D2.valid()) f1D2.ref() *= 1 - fDSum();
408  if (f2D1.valid()) f2D1.ref() *= 1 - fDSum();
409  if (fS.valid()) fS.ref() *= 1 - fDSum();
410  if (fG.valid()) fG.ref() *= 1 - fDSum();
411  }
412 }
413 
414 
415 template<class ModelType>
417 (
418  const word& format
419 ) const
420 {
421  using namespace constant::mathematical;
422 
423  const phaseSystem& fluid = interface_.fluid();
424  const label nPhases = fluid.phases().size();
425 
426  // Construct geometry and phase fraction values
428  faceList faces;
429  wordList fieldNames(nPhases);
430  PtrList<scalarField> fields(nPhases);
431  if (nPhases == 1)
432  {
433  // Single value
434  fieldNames[0] = fluid.phases()[0].volScalarField::name();
435  fields.set(0, new scalarField(1, 1));
436  }
437  else if (nPhases == 2)
438  {
439  // Single axis, using the first phase as the x-coordinate
440  static const label nDivisions = 128;
441  fieldNames[0] = fluid.phases()[0].volScalarField::name();
442  fieldNames[1] = fluid.phases()[1].volScalarField::name();
443  fields.set(0, new scalarField(linearSequence01(nDivisions)));
444  fields.set(1, new scalarField(1 - fields[0]));
445  }
446  else
447  {
448  // Polygon with as many vertices as there are phases. Each phase
449  // fraction equals one at a unique vertex, they vary linearly between
450  // vertices, and vary smoothly in the interior of the polygon. The sum
451  // of phase fractions is always one throughout the polygon (partition of
452  // unity). This is done using Wachspress coordinates. This does not
453  // cover the entire N-dimensional phase fraction space for N >= 4 (it is
454  // hard to imagine how that could be visualised) but it provides enough
455  // to give a good indication of what is going on.
456 
457  // Create the nodes of the blending space polygon
458  List<point> phaseNodes(nPhases);
459  forAll(phaseNodes, phasei)
460  {
461  const scalar theta = 2*pi*phasei/nPhases;
462  phaseNodes[phasei] = point(cos(theta), sin(theta), 0);
463  }
464 
465  // Create points within the polygon
466  static const label nDivisions = 32;
467  points.append(point::zero);
468  for (label divi = 0; divi < nDivisions; ++ divi)
469  {
470  const scalar s = scalar(divi + 1)/nDivisions;
471 
472  forAll(phaseNodes, phasei)
473  {
474  for (label i = 0; i < divi + 1; ++ i)
475  {
476  const scalar t = scalar(i)/(divi + 1);
477 
478  points.append
479  (
480  s*(1 - t)*phaseNodes[phasei]
481  + s*t*phaseNodes[(phasei + 1) % nPhases]
482  );
483  }
484  }
485  }
486 
487  // Create triangles within the polygon
488  forAll(phaseNodes, phasei)
489  {
490  faces.append(triFace(0, phasei + 1, (phasei + 1) % nPhases + 1));
491  }
492  for (label divi = 1; divi < nDivisions; ++ divi)
493  {
494  const label pointi0 = nPhases*(divi - 1)*divi/2 + 1;
495  const label pointi1 = nPhases*divi*(divi + 1)/2 + 1;
496 
497  forAll(phaseNodes, phasei)
498  {
499  for (label i = 0; i < divi + 1; ++ i)
500  {
501  const label pi00 =
502  pointi0
503  + ((phasei*divi + i) % (nPhases*divi));
504  const label pi01 =
505  pointi0
506  + ((phasei*divi + i + 1) % (nPhases*divi));
507  const label pi10 =
508  pointi1
509  + ((phasei*(divi + 1) + i) % (nPhases*(divi + 1)));
510  const label pi11 =
511  pointi1
512  + ((phasei*(divi + 1) + i + 1) % (nPhases*(divi + 1)));
513 
514  faces.append(triFace({pi00, pi10, pi11}));
515  if (i < divi) faces.append(triFace({pi00, pi11, pi01}));
516  }
517  }
518  }
519 
520  // Create phase fraction fields
521  forAll(fluid.phases(), phasei)
522  {
523  fieldNames[phasei] = fluid.phases()[phasei].volScalarField::name();
524  fields.set(phasei, new scalarField(points.size(), 0));
525 
526  const label phasei0 = (phasei + nPhases - 1) % nPhases;
527  const label phasei1 = (phasei + 1) % nPhases;
528 
529  const point& node0 = phaseNodes[phasei0];
530  const point& node = phaseNodes[phasei];
531  const point& node1 = phaseNodes[phasei1];
532 
533  forAll(points, i)
534  {
535  const scalar A = mag((node - node0) ^ (node1 - node0));
536  const scalar A0 = mag((node - node0) ^ (points[i] - node0));
537  const scalar A1 = mag((node1 - node) ^ (points[i] - node));
538 
539  if (A0 < rootSmall)
540  {
541  fields[phasei][i] =
542  great*mag(points[i] - node0)/mag(node - node0);
543  }
544  else if (A1 < rootSmall)
545  {
546  fields[phasei][i] =
547  great*mag(points[i] - node1)/mag(node - node1);
548  }
549  else
550  {
551  fields[phasei][i] = A/A0/A1;
552  }
553  }
554  }
555  forAll(points, i)
556  {
557  scalar s = 0;
558  forAll(fluid.phases(), phasei)
559  {
560  s += fields[phasei][i];
561  }
562  forAll(fluid.phases(), phasei)
563  {
564  fields[phasei][i] /= s;
565  }
566  }
567  }
568 
569  // Add the model coefficient fields
570  {
571  // Create alpha fields on a zero-dimensional one-cell mesh
572  const fvMesh mesh(zeroDimensionalFvMesh(fluid.mesh()));
573  PtrList<volScalarField> alphas(nPhases);
574  forAll(fluid.phases(), phasei)
575  {
576  alphas.set
577  (
578  phasei,
579  new volScalarField
580  (
581  IOobject
582  (
583  fluid.phases()[phasei].volScalarField::name(),
584  mesh.time().name(),
585  mesh
586  ),
587  mesh,
588  dimensionedScalar(dimless, fields[phasei][0])
589  )
590  );
591  }
592 
593  // Construct blending coefficient fields
594  {
595  tmp<volScalarField> fG, f1D2, f2D1, fS;
596  PtrList<volScalarField> fGD(nPhases);
597  PtrList<volScalarField> f1D2D(nPhases);
598  PtrList<volScalarField> f2D1D(nPhases);
599  PtrList<volScalarField> fSD(nPhases);
600  calculateBlendingCoeffs
601  (
602  alphas.convert<const volScalarField>(),
603  fG, f1D2, f2D1, fS,
604  fGD, f1D2D, f2D1D, fSD
605  );
606 
607  const phaseModel& phase1 = interface_.phase1();
608  const phaseModel& phase2 = interface_.phase2();
609 
610  auto addField = [&](const phaseInterface& interface)
611  {
612  fieldNames.append(interface.name());
613  fields.append(new scalarField(fields.first().size()));
614  };
615 
616  if (fG.valid())
617  {
618  addField(phaseInterface(phase1, phase2));
619  }
620  if (f1D2.valid())
621  {
622  addField(dispersedPhaseInterface(phase1, phase2));
623  }
624  if (f2D1.valid())
625  {
626  addField(dispersedPhaseInterface(phase2, phase1));
627  }
628  if (fS.valid())
629  {
630  addField(segregatedPhaseInterface(phase2, phase1));
631  }
632 
633  forAll(fluid.phases(), phasei)
634  {
635  const phaseModel& phaseD = fluid.phases()[phasei];
636 
637  if (fGD.set(phasei))
638  {
639  addField(displacedPhaseInterface(phase1, phase2, phaseD));
640  }
641  if (f1D2D.set(phasei))
642  {
643  addField
644  (
645  dispersedDisplacedPhaseInterface
646  (
647  phase1,
648  phase2,
649  phaseD
650  )
651  );
652  }
653  if (f2D1D.set(phasei))
654  {
655  addField
656  (
657  dispersedDisplacedPhaseInterface
658  (
659  phase2,
660  phase1,
661  phaseD
662  )
663  );
664  }
665  if (fSD.set(phasei))
666  {
667  addField
668  (
669  segregatedDisplacedPhaseInterface
670  (
671  phase1,
672  phase2,
673  phaseD
674  )
675  );
676  }
677  }
678  }
679 
680  // Populate blending coefficient fields
681  forAll(fields.first(), i)
682  {
683  forAll(fluid.phases(), phasei)
684  {
685  alphas[phasei] = fields[phasei][i];
686  }
687 
688  tmp<volScalarField> fG, f1D2, f2D1, fS;
689  PtrList<volScalarField> fGD(nPhases);
690  PtrList<volScalarField> f1D2D(nPhases);
691  PtrList<volScalarField> f2D1D(nPhases);
692  PtrList<volScalarField> fSD(nPhases);
693  calculateBlendingCoeffs
694  (
695  alphas.convert<const volScalarField>(),
696  fG, f1D2, f2D1, fS,
697  fGD, f1D2D, f2D1D, fSD
698  );
699 
700  label fieldi = nPhases;
701 
702  if (fG.valid())
703  {
704  fields[fieldi ++][i] = fG()[0];
705  }
706  if (f1D2.valid())
707  {
708  fields[fieldi ++][i] = f1D2()[0];
709  }
710  if (f2D1.valid())
711  {
712  fields[fieldi ++][i] = f2D1()[0];
713  }
714  if (fS.valid())
715  {
716  fields[fieldi ++][i] = fS()[0];
717  }
718 
719  forAll(fluid.phases(), phasei)
720  {
721  if (fGD.set(phasei))
722  {
723  fields[fieldi ++][i] = fGD[phasei][0];
724  }
725  if (f1D2D.set(phasei))
726  {
727  fields[fieldi ++][i] = f1D2D[phasei][0];
728  }
729  if (f2D1D.set(phasei))
730  {
731  fields[fieldi ++][i] = f2D1D[phasei][0];
732  }
733  if (fSD.set(phasei))
734  {
735  fields[fieldi ++][i] = fSD[phasei][0];
736  }
737  }
738  }
739  }
740 
741  // Write
742  const fileName path =
743  fluid.mesh().time().globalPath()
744  /functionObjects::writeFile::outputPrefix
746  Info<< "Writing blending coefficients to " << path/interface_.name()
747  << endl;
748  if (nPhases <= 2)
749  {
750  // Strip out the first field and shuffle everything else up
751  const autoPtr<scalarField> field0 = fields.set(0, nullptr);
752  const word field0Name = fieldNames[0];
753  for (label fieldi = 1; fieldi < fields.size(); ++ fieldi)
754  {
755  fields.set(fieldi - 1, fields.set(fieldi, nullptr).ptr());
756  fieldNames[fieldi - 1] = fieldNames[fieldi];
757  }
758  fields.resize(fields.size() - 1);
760 
762  (
763  format,
764  IOstream::ASCII,
765  IOstream::UNCOMPRESSED
766  )->write
767  (
768  path,
769  interface_.name(),
770  coordSet(true, field0Name, field0),
771  fieldNames,
772  fields
773  );
774  }
775  else
776  {
778  (
779  format,
780  IOstream::ASCII,
781  IOstream::UNCOMPRESSED
782  )->write
783  (
784  path,
785  interface_.name(),
786  points,
787  faces,
788  true,
789  fieldNames,
790  fields
791  );
792  }
793 }
794 
795 
796 // * * * * * * * * * * * * Protected Member Functions * * * * * * * * * * * //
797 
798 template<class ModelType>
799 template<class Type, class GeoMesh, class ... Args>
802 (
803  tmp<GeometricField<Type, GeoMesh>>(ModelType::*method)(Args ...) const,
804  const word& name,
805  const dimensionSet& dims,
806  Args ... args
807 ) const
808 {
809  check();
810 
811  typedef GeometricField<scalar, GeoMesh> scalarGeoField;
812  typedef GeometricField<Type, GeoMesh> typeGeoField;
813 
814  // Get the blending coefficients
815  const label nPhases = interface_.fluid().phases().size();
816  tmp<scalarGeoField> fG, f1D2, f2D1, fS;
817  PtrList<scalarGeoField> fGD(nPhases);
818  PtrList<scalarGeoField> f1D2D(nPhases);
819  PtrList<scalarGeoField> f2D1D(nPhases);
820  PtrList<scalarGeoField> fSD(nPhases);
821  calculateBlendingCoeffs
822  (
823  interface_.fluid().phases()
824  .PtrList<phaseModel>::convert<const volScalarField>(),
825  fG, f1D2, f2D1, fS,
826  fGD, f1D2D, f2D1D, fSD
827  );
828 
829  // Construct the result
832  (
833  ModelType::typeName + ":"
834  + IOobject::groupName(name, interface_.name()),
835  interface_.mesh(),
836  dimensioned<Type>(dims, Zero)
837  );
838 
839  // Add the model contributions to the result
840  if (modelGeneral_.valid())
841  {
842  x.ref() += fG*(modelGeneral_().*method)(args ...);
843  }
844  if (model1DispersedIn2_.valid())
845  {
846  x.ref() += f1D2*(model1DispersedIn2_().*method)(args ...);
847  }
848  if (model2DispersedIn1_.valid())
849  {
850  x.ref() += f2D1*(model2DispersedIn1_().*method)(args ...);
851  }
852  if (model1SegregatedWith2_.valid())
853  {
854  x.ref() += fS*(model1SegregatedWith2_().*method)(args ...);
855  }
856  forAll(interface_.fluid().phases(), phasei)
857  {
858  if (modelsGeneralDisplaced_.set(phasei))
859  {
860  x.ref() +=
861  fGD[phasei]
862  *(modelsGeneralDisplaced_[phasei].*method)(args ...);
863  }
864  if (models1DispersedIn2Displaced_.set(phasei))
865  {
866  x.ref() +=
867  f1D2D[phasei]
868  *(models1DispersedIn2Displaced_[phasei].*method)(args ...);
869  }
870  if (models2DispersedIn1Displaced_.set(phasei))
871  {
872  x.ref() +=
873  f2D1D[phasei]
874  *(models2DispersedIn1Displaced_[phasei].*method)(args ...);
875  }
876  if (models1SegregatedWith2Displaced_.set(phasei))
877  {
878  x.ref() +=
879  fSD[phasei]
880  *(models1SegregatedWith2Displaced_[phasei].*method)(args ...);
881  }
882  }
883 
884  return x;
885 }
886 
887 
888 template<class ModelType>
889 template<class Type, class GeoMesh, class ... Args>
892 (
894  (ModelType::*method)(Args ...) const,
895  const word& name,
896  const dimensionSet& dims,
897  Args ... args
898 ) const
899 {
900  check();
901 
902  typedef GeometricField<scalar, GeoMesh> scalarGeoField;
903  typedef GeometricField<Type, GeoMesh> typeGeoField;
904 
905  // Get the blending coefficients
906  const label nPhases = interface_.fluid().phases().size();
907  tmp<scalarGeoField> fG, f1D2, f2D1, fS;
908  PtrList<scalarGeoField> fGD(nPhases);
909  PtrList<scalarGeoField> f1D2D(nPhases);
910  PtrList<scalarGeoField> f2D1D(nPhases);
911  PtrList<scalarGeoField> fSD(nPhases);
912  calculateBlendingCoeffs
913  (
914  interface_.fluid().phases()
915  .PtrList<phaseModel>::convert<const volScalarField>(),
916  fG, f1D2, f2D1, fS,
917  fGD, f1D2D, f2D1D, fSD
918  );
919 
920  // Construct the result
922 
923  // Add the model contributions to the result
924  auto addToXs = [&]
925  (
926  const scalarGeoField& f,
927  const HashPtrTable<typeGeoField>& dxs
928  )
929  {
930  forAllConstIter(typename HashPtrTable<typeGeoField>, dxs, dxIter)
931  {
932  if (xs.found(dxIter.key()))
933  {
934  *xs[dxIter.key()] += f**dxIter();
935  }
936  else
937  {
938  xs.insert
939  (
940  dxIter.key(),
942  (
943  ModelType::typeName + ':'
944  + IOobject::groupName
945  (
946  IOobject::groupName(name, dxIter.key()),
947  interface_.name()
948  ),
949  f**dxIter()
950  ).ptr()
951  );
952  }
953  }
954  };
955  if (modelGeneral_.valid())
956  {
957  addToXs(fG, (modelGeneral_().*method)(args ...));
958  }
959  if (model1DispersedIn2_.valid())
960  {
961  addToXs(f1D2, (model1DispersedIn2_().*method)(args ...));
962  }
963  if (model2DispersedIn1_.valid())
964  {
965  addToXs(f2D1, (model2DispersedIn1_().*method)(args ...));
966  }
967  if (model1SegregatedWith2_.valid())
968  {
969  addToXs(fS, (model1SegregatedWith2_().*method)(args ...));
970  }
971  forAll(interface_.fluid().phases(), phasei)
972  {
973  if (modelsGeneralDisplaced_.set(phasei))
974  {
975  addToXs
976  (
977  fGD[phasei],
978  (modelsGeneralDisplaced_[phasei].*method)(args ...)
979  );
980  }
981  if (models1DispersedIn2Displaced_.set(phasei))
982  {
983  addToXs
984  (
985  f1D2D[phasei],
986  (models1DispersedIn2Displaced_[phasei].*method)(args ...)
987  );
988  }
989  if (models2DispersedIn1Displaced_.set(phasei))
990  {
991  addToXs
992  (
993  f2D1D[phasei],
994  (models2DispersedIn1Displaced_[phasei].*method)(args ...)
995  );
996  }
997  if (models1SegregatedWith2Displaced_.set(phasei))
998  {
999  addToXs
1000  (
1001  fSD[phasei],
1002  (models1SegregatedWith2Displaced_[phasei].*method)(args ...)
1003  );
1004  }
1005  }
1006 
1007  return xs;
1008 }
1009 
1010 
1011 template<class ModelType>
1012 template<class ... Args>
1014 (
1015  bool (ModelType::*method)(Args ...) const,
1016  Args ... args
1017 ) const
1018 {
1019  check();
1020 
1021  bool result = false;
1022 
1023  if (modelGeneral_.valid())
1024  {
1025  result = result || (modelGeneral_().*method)(args ...);
1026  }
1027  if (model1DispersedIn2_.valid())
1028  {
1029  result = result || (model1DispersedIn2_().*method)(args ...);
1030  }
1031  if (model2DispersedIn1_.valid())
1032  {
1033  result = result || (model2DispersedIn1_().*method)(args ...);
1034  }
1035  if (model1SegregatedWith2_.valid())
1036  {
1037  result = result || (model1SegregatedWith2_().*method)(args ...);
1038  }
1039 
1040  forAll(interface_.fluid().phases(), phasei)
1041  {
1042  if (modelsGeneralDisplaced_.set(phasei))
1043  {
1044  result =
1045  result
1046  || (modelsGeneralDisplaced_[phasei].*method)(args ...);
1047  }
1048  if (models1DispersedIn2Displaced_.set(phasei))
1049  {
1050  result =
1051  result
1052  || (models1DispersedIn2Displaced_[phasei].*method)(args ...);
1053  }
1054  if (models2DispersedIn1Displaced_.set(phasei))
1055  {
1056  result =
1057  result
1058  || (models2DispersedIn1Displaced_[phasei].*method)(args ...);
1059  }
1060  if (models1SegregatedWith2Displaced_.set(phasei))
1061  {
1062  result =
1063  result
1064  || (models1SegregatedWith2Displaced_[phasei].*method)(args ...);
1065  }
1066  }
1067 
1068  return result;
1069 }
1070 
1071 
1072 template<class ModelType>
1073 template<class ... Args>
1075 (
1076  const hashedWordList& (ModelType::*method)(Args ...) const,
1077  Args ... args
1078 ) const
1079 {
1080  check();
1081 
1082  wordList result;
1083 
1084  if (modelGeneral_.valid())
1085  {
1086  result.append((modelGeneral_().*method)(args ...));
1087  }
1088  if (model1DispersedIn2_.valid())
1089  {
1090  result.append((model1DispersedIn2_().*method)(args ...));
1091  }
1092  if (model2DispersedIn1_.valid())
1093  {
1094  result.append((model2DispersedIn1_().*method)(args ...));
1095  }
1096  if (model1SegregatedWith2_.valid())
1097  {
1098  result.append((model1SegregatedWith2_().*method)(args ...));
1099  }
1100 
1101  forAll(interface_.fluid().phases(), phasei)
1102  {
1103  if (modelsGeneralDisplaced_.set(phasei))
1104  {
1105  result.append
1106  (
1107  (modelsGeneralDisplaced_[phasei].*method)(args ...)
1108  );
1109  }
1110  if (models1DispersedIn2Displaced_.set(phasei))
1111  {
1112  result.append
1113  (
1114  (models1DispersedIn2Displaced_[phasei].*method)(args ...)
1115  );
1116  }
1117  if (models2DispersedIn1Displaced_.set(phasei))
1118  {
1119  result.append
1120  (
1121  (models2DispersedIn1Displaced_[phasei].*method)(args ...)
1122  );
1123  }
1124  if (models1SegregatedWith2Displaced_.set(phasei))
1125  {
1126  result.append
1127  (
1128  (models1SegregatedWith2Displaced_[phasei].*method)(args ...)
1129  );
1130  }
1131  }
1132 
1133  return hashedWordList(move(result));
1134 }
1135 
1136 
1137 // * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
1138 
1139 template<class ModelType>
1140 template<class ... Args>
1142 (
1143  const UPtrList<const dictionary>& subDicts,
1144  const phaseInterface& interface,
1145  const dictionary& blendingDict,
1146  const Args& ... args
1147 )
1148 :
1149  regIOobject
1150  (
1151  IOobject
1152  (
1153  IOobject::groupName(typeName, interface.name()),
1154  interface.fluid().mesh().time().name(),
1155  interface.fluid().mesh()
1156  )
1157  ),
1158  interface_(interface),
1159  blending_
1160  (
1161  blendingMethod::New(ModelType::typeName, blendingDict, interface)
1162  ),
1163  modelGeneral_(),
1164  model1DispersedIn2_(),
1165  model2DispersedIn1_(),
1166  model1SegregatedWith2_(),
1167  modelsGeneralDisplaced_(interface.fluid().phases().size()),
1168  models1DispersedIn2Displaced_(interface.fluid().phases().size()),
1169  models2DispersedIn1Displaced_(interface.fluid().phases().size()),
1170  models1SegregatedWith2Displaced_(interface.fluid().phases().size()),
1171  checkTimeIndex_(-1)
1172 {
1173  // Construct the models
1174  PtrList<phaseInterface> interfaces;
1175  PtrList<ModelType> models;
1177  <
1178  ModelType,
1185  >
1186  (
1187  interfaces,
1188  models,
1189  interface.fluid(),
1190  subDicts,
1191  wordHashSet({"blending"}),
1192  interface,
1193  args ...
1194  );
1195 
1196  // Unpack the interface and model lists to populate the models used for the
1197  // different parts of the blending space
1198  forAll(interfaces, i)
1199  {
1200  const phaseInterface& interface = interfaces[i];
1201 
1202  autoPtr<ModelType>* modelPtrPtr;
1203  PtrList<ModelType>* modelPtrsPtr;
1204 
1205  if (isA<dispersedPhaseInterface>(interface))
1206  {
1207  const phaseModel& dispersed =
1208  refCast<const dispersedPhaseInterface>(interface).dispersed();
1209 
1210  modelPtrPtr =
1211  interface_.index(dispersed) == 0
1212  ? &model1DispersedIn2_
1213  : &model2DispersedIn1_;
1214  modelPtrsPtr =
1215  interface_.index(dispersed) == 0
1216  ? &models1DispersedIn2Displaced_
1217  : &models2DispersedIn1Displaced_;
1218  }
1219  else if (isA<segregatedPhaseInterface>(interface))
1220  {
1221  modelPtrPtr = &model1SegregatedWith2_;
1222  modelPtrsPtr = &models1SegregatedWith2Displaced_;
1223  }
1224  else
1225  {
1226  modelPtrPtr = &modelGeneral_;
1227  modelPtrsPtr = &modelsGeneralDisplaced_;
1228  }
1229 
1230  if (!isA<displacedPhaseInterface>(interface))
1231  {
1232  *modelPtrPtr = models.set(i, nullptr);
1233  }
1234  else
1235  {
1236  const phaseModel& displacing =
1237  refCast<const displacedPhaseInterface>(interface).displacing();
1238 
1239  modelPtrsPtr->set(displacing.index(), models.set(i, nullptr));
1240  }
1241  }
1242 
1243  // Write out the blending space if needed
1244  if (blendingDict.found("format"))
1245  {
1246  const word format = blendingDict.lookup<word>("format");
1247 
1248  const label nPhases = interface_.fluid().phases().size();
1249 
1250  if
1251  (
1252  (nPhases <= 2 && format != noSetWriter::typeName)
1253  || (nPhases > 2 && format != noSurfaceWriter::typeName)
1254  )
1255  {
1256  postProcessBlendingCoefficients(format);
1257  }
1258  }
1259 }
1260 
1261 
1262 // * * * * * * * * * * * * * * * * Destructor * * * * * * * * * * * * * * * //
1263 
1264 template<class ModelType>
1266 {}
1267 
1268 
1269 // * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * * //
1270 
1271 template<class ModelType>
1272 const Foam::phaseInterface&
1274 {
1275  return interface_;
1276 }
1277 
1278 
1279 template<class ModelType>
1281 {
1282  return os.good();
1283 }
1284 
1285 
1286 // ************************************************************************* //
label k
#define forAll(list, i)
Loop across all elements in list.
Definition: UList.H:449
#define forAllConstIter(Container, container, iter)
Iterate across all elements in the container object of type.
Definition: UList.H:492
Wrapper class for interfacial models for which multiple instances of the model are used for different...
const phaseInterface & interface() const
Access the interface.
bool writeData(Ostream &os) const
Dummy write for regIOobject.
BlendedInterfacialModel(const UPtrList< const dictionary > &subDicts, const phaseInterface &interface, const dictionary &blendingDict, const Args &... args)
Construct from a dictionary, an interface and a blending dictionary.
tmp< GeometricField< Type, GeoMesh > > evaluate(tmp< GeometricField< Type, GeoMesh >>(ModelType::*method)(Args ...) const, const word &name, const dimensionSet &dims, Args ... args) const
Return a blended field.
Generic GeometricField class.
A HashTable specialisation for hashing pointers.
Definition: HashPtrTable.H:68
bool insert(const Key &, const T &newElmt)
Insert a new hashedEntry.
Definition: HashTableI.H:80
bool found(const Key &) const
Return true if hashedEntry is found in table.
Definition: HashTable.C:138
IOobject defines the attributes of an object for which implicit objectRegistry management is supporte...
Definition: IOobject.H:99
bool good() const
Return true if next operation might succeed.
Definition: IOstream.H:333
void append(const T &)
Append an element at the end of the list.
Definition: ListI.H:178
void resize(const label)
Alias for setSize(const label)
Definition: ListI.H:138
void size(const label)
Override size to be inconsistent with allocated storage.
Definition: ListI.H:164
An Ostream is an abstract base class for all output systems (streams, files, token lists,...
Definition: Ostream.H:57
bool set(const label) const
Is element set.
Definition: PtrListI.H:62
A templated 1D list of pointers to objects of type <T>, where the size of the array is known and used...
Definition: UPtrList.H:66
UPtrList< T2 > convert()
Convert to list of different pointer type.
T & first()
Return reference to the first element of the list.
Definition: UPtrListI.H:43
bool set(const label) const
Is element set.
Definition: UPtrListI.H:87
Abstract base class for functions that are used to combine interfacial sub-models according to the vo...
A list of keywords followed by any number of values (e.g. words and numbers) or sub-dictionaries.
Definition: dictionary.H:162
ITstream & lookup(const word &, bool recursive=false, bool patternMatch=true) const
Find and return an entry data stream.
Definition: dictionary.C:669
bool found(const word &, bool recursive=false, bool patternMatch=true) const
Search dictionary for given keyword.
Definition: dictionary.C:468
Dimension set for the base types.
Definition: dimensionSet.H:125
const word & name() const
Return const reference to name.
Class to represent a interface between phases where one phase is considered dispersed within the othe...
Class to represent a interface between phases where one phase is considered dispersed within the othe...
Class to represent an interface between phases which has been displaced to some extent by a third pha...
const Time & time() const
Return the top-level database.
Definition: fvMesh.H:433
A wordList with hashed indices for faster lookup by name.
Class to represent an interface between phases. Derivations can further specify the configuration of ...
const phaseSystem & fluid() const
Return the phase system.
virtual word name() const
Name.
label index(const phaseModel &phase) const
Return the index of the given phase. Generates a FatalError if.
const phaseSystem & fluid() const
Return the system to which this phase belongs.
Definition: phaseModel.C:110
label index() const
Return the index of the phase.
Definition: phaseModel.C:104
const phaseModelList & phases() const
Return the phase models.
Definition: phaseSystemI.H:104
Motion of the mesh specified as a list of pointMeshMovers.
regIOobject is an abstract class derived from IOobject to handle automatic object registration with t...
Definition: regIOobject.H:55
Class to represent a interface between phases where the two phases are considered to be segregated,...
Class to represent a interface between phases where the two phases are considered to be segregated; t...
A class for managing temporary objects.
Definition: tmp.H:55
Template function which returns the un-mangled name of a given type. Useful for types which do not ha...
A class for handling words, derived from string.
Definition: word.H:63
Foam::fvMesh mesh(Foam::IOobject(regionName, runTime.name(), runTime, Foam::IOobject::MUST_READ), false)
volScalarField scalarField(fieldObject, mesh)
static List< word > fieldNames
Definition: globalFoam.H:46
const pointField & points
gmvFile<< "tracers "<< particles.size()<< nl;forAllConstIter(lagrangian::Cloud< passiveParticle >, particles, iter){ gmvFile<< iter().position().x()<< " ";}gmvFile<< nl;forAllConstIter(lagrangian::Cloud< passiveParticle >, particles, iter){ gmvFile<< iter().position().y()<< " ";}gmvFile<< nl;forAllConstIter(lagrangian::Cloud< passiveParticle >, particles, iter){ gmvFile<< iter().position().z()<< " ";}gmvFile<< nl;forAll(lagrangianScalarNames, i){ word name=lagrangianScalarNames[i];IOField< scalar > s(IOobject(name, runTime.name(), lagrangian::cloud::prefix, mesh, IOobject::MUST_READ, IOobject::NO_WRITE))
Info<< "Calculating turbulent flame speed field St\n"<< endl;volScalarField St(IOobject("St", runTime.name(), mesh, IOobject::NO_READ, IOobject::AUTO_WRITE), flameWrinkling->Xi() *Su);multivariateSurfaceInterpolationScheme< scalar >::fieldTable fields
Definition: createFields.H:234
volScalarField alpha(IOobject("alpha", runTime.name(), mesh, IOobject::READ_IF_PRESENT, IOobject::AUTO_WRITE), lambda *max(Ua &U, zeroSensitivity))
#define WarningInFunction
Report a warning using Foam::Warning.
bool valid(const PtrList< ModelType > &l)
tmp< GeoField > interpolate(tmp< volScalarField > f)
const dimensionSet dimless
const dimensionSet time
static tmp< SurfaceField< Type > > interpolate(const VolField< Type > &tvf, const surfaceScalarField &faceFlux, Istream &schemeData)
Interpolate field onto faces using scheme given by Istream.
static const coefficient A("A", dimPressure, 611.21)
Namespace for OpenFOAM.
static const zero Zero
Definition: zero.H:97
List< word > wordList
A List of words.
Definition: fileName.H:54
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
Ostream & endl(Ostream &os)
Add newline and flush stream.
Definition: Ostream.H:288
void addField(const label phasei, const word &name, tmp< GeoField > field, PtrList< GeoField > &fieldList)
Definition: phaseSystemI.H:265
String typeName(const std::type_info &info)
Return the un-mangled name given the standard type info.
void generateInterfacialModels(PtrList< phaseInterface > &interfaces, PtrList< ModelType > &models, const phaseSystem &fluid, const UPtrList< const dictionary > &subDicts, const wordHashSet &ignoreKeys, const phaseInterface &interface, const Args &... args)
dimensionedScalar sin(const dimensionedScalar &ds)
messageStream Info
vectorField pointField
pointField is a vectorField.
Definition: pointFieldFwd.H:42
vector point
Point is a vector.
Definition: point.H:41
tmp< scalarField > linearSequence01(const label n)
Definition: scalarField.C:107
VolField< scalar > volScalarField
Definition: volFieldsFwd.H:62
const dictionary & blendingDict(const phaseSystem &fluid, const dictionary &dict)
word name(const LagrangianState state)
Return a string representation of a Lagrangian state enumeration.
HashSet wordHashSet
A HashSet with word keys.
Definition: HashSet.H:210
tmp< DimensionedField< scalar, GeoMesh, Field > > mag(const DimensionedField< Type, GeoMesh, PrimitiveField > &df)
fvMesh zeroDimensionalFvMesh(const objectRegistry &db)
Construct a zero-dimensional unit-cube FV mesh.
List< face > faceList
Definition: faceListFwd.H:41
tmp< DimensionedField< TypeR, GeoMesh, Field > > New(const tmp< DimensionedField< TypeR, GeoMesh, Field >> &tdf1, const word &name, const dimensionSet &dimensions)
dimensionedScalar cos(const dimensionedScalar &ds)
dimensioned< Type > max(const DimensionedField< Type, GeoMesh, PrimitiveField > &df)
dimensioned< scalar > dimensionedScalar
Dimensioned scalar obtained from generic dimensioned type.
label timeIndex
Definition: getTimeIndex.H:4
word format(conversionProperties.lookup("format"))
face triFace(3)
labelList f(nPoints)
Foam::argList args(argc, argv)