lumpedPointMovement.C
Go to the documentation of this file.
1 /*---------------------------------------------------------------------------*\
2  ========= |
3  \\ / F ield | OpenFOAM: The Open Source CFD Toolbox
4  \\ / O peration |
5  \\ / A nd | www.openfoam.com
6  \\/ M anipulation |
7 -------------------------------------------------------------------------------
8  Copyright (C) 2016-2022 OpenCFD Ltd.
9 -------------------------------------------------------------------------------
10 License
11  This file is part of OpenFOAM.
12 
13  OpenFOAM is free software: you can redistribute it and/or modify it
14  under the terms of the GNU General Public License as published by
15  the Free Software Foundation, either version 3 of the License, or
16  (at your option) any later version.
17 
18  OpenFOAM is distributed in the hope that it will be useful, but WITHOUT
19  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
20  FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
21  for more details.
22 
23  You should have received a copy of the GNU General Public License
24  along with OpenFOAM. If not, see <http://www.gnu.org/licenses/>.
25 
26 \*---------------------------------------------------------------------------*/
27 
28 #include "lumpedPointMovement.H"
29 #include "lumpedPointIOMovement.H"
30 #include "Fstream.H"
31 #include "volFields.H"
32 #include "surfaceFields.H"
33 #include "PtrMap.H"
34 #include "triFace.H"
35 #include "labelPair.H"
36 #include "indexedOctree.H"
37 #include "treeDataPoint.H"
38 #include "pointIndexHit.H"
39 #include "pointPatch.H"
40 #include "PstreamReduceOps.H"
41 
42 // * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
43 
45 (
46  ::Foam::debug::debugSwitch("lumpedPointMovement", 0)
47 );
48 
49 
50 const Foam::word
51 Foam::lumpedPointMovement::canonicalName("lumpedPointMovement");
52 
53 
54 const Foam::Enum
55 <
57 >
59 ({
60  { outputFormatType::PLAIN, "plain" },
61  { outputFormatType::DICTIONARY, "dictionary" },
62 });
63 
64 
65 const Foam::Enum
66 <
68 >
70 ({
71  { scalingType::LENGTH, "length" },
72  { scalingType::FORCE, "force" },
73  { scalingType::MOMENT, "moment" },
74 });
75 
76 
77 
78 // * * * * * * * * * * * * * * * Local Functions * * * * * * * * * * * * * * //
79 
80 namespace Foam
81 {
82 
84 //- Space-separated vector value (ASCII)
85 static inline Ostream& putPlain(Ostream& os, const vector& v)
86 {
87  return os << v.x() << ' ' << v.y() << ' ' << v.z();
88 }
89 
90 
92 //- Write list content with size, bracket, content, bracket one-per-line.
93 // This makes for consistent for parsing, regardless of the list length.
94 template <class T>
95 static void writeList(Ostream& os, const string& header, const UList<T>& list)
96 {
97  const label len = list.size();
98 
99  // Header string
100  os << header.c_str() << nl;
101 
102  // Write size and start delimiter
103  os << len << nl << token::BEGIN_LIST << nl;
104 
105  // Write contents
106  for (label i=0; i < len; ++i)
107  {
108  os << list[i] << nl;
109  }
110 
111  // Write end delimiter
113 }
115 
116 } // End namespace Foam
117 
118 
119 // * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
120 
122 :
123  origin_(Zero),
124  state0_(),
125  state_(),
126  originalIds_(),
127  controllers_(),
128  patchControls_(),
129  relax_(1),
130  ownerId_(-1),
131  forcesDict_(),
132  coupler_(),
133  inputName_("positions.in"),
134  outputName_("forces.out"),
135  logName_("movement.log"),
136  inputFormat_(lumpedPointState::inputFormatType::DICTIONARY),
137  outputFormat_(outputFormatType::DICTIONARY),
138  scaleInput_(-1),
139  scaleOutput_(-1),
140  calcFrequency_(1),
141  lastTrigger_(-1)
142 {}
143 
146 (
147  const dictionary& dict,
148  label ownerId
149 )
150 :
151  origin_(Zero),
152  state0_(),
153  state_(),
154  originalIds_(),
155  controllers_(),
156  patchControls_(),
157  relax_(1),
158  ownerId_(ownerId),
159  forcesDict_(),
160  coupler_(),
161  inputName_("positions.in"),
162  outputName_("forces.out"),
163  logName_("movement.log"),
164  scaleInput_(-1),
165  scaleOutput_(-1),
166  calcFrequency_(1),
167  lastTrigger_(-1)
168 {
169  readDict(dict);
170 }
171 
172 
173 // * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * //
174 
176 {
177  return (timeIndex >= lastTrigger_ + calcFrequency_);
178 }
180 
182 {
183  lastTrigger_ = timeIndex;
184 }
186 
188 {
189  origin_ = dict.getOrDefault<point>("origin", Zero);
190 
191  // Initial point locations (zero rotation)
192  auto tpoints0 = tmp<pointField>::New();
193  auto& points0 = tpoints0.ref();
194 
195  dict.readEntry("points", points0);
196  points0 += origin_;
197 
198  originalIds_.clear();
199  controllers_.clear();
200  patchControls_.clear();
201 
202 
203  // The FEA ids for the points (optional)
204  Map<label> pointIdMap;
205 
206  if (dict.readIfPresent("pointLabels", originalIds_) && originalIds_.size())
207  {
208  if (originalIds_.size() != points0.size())
209  {
211  << "Incorrect number of pointLabels. Had "
212  << originalIds_.size() << " for " << points0.size() << " points"
213  << nl << endl
214  << exit(FatalIOError);
215  }
216 
217  labelHashSet duplicates;
218 
219  label pointi = 0;
220 
221  for (const label id : originalIds_)
222  {
223  if (!pointIdMap.insert(id, pointi))
224  {
225  duplicates.set(id);
226  }
227 
228  ++pointi;
229  }
230 
231  if (!duplicates.empty())
232  {
234  << "Found duplicate point ids "
235  << flatOutput(duplicates.sortedToc()) << nl << endl
236  << exit(FatalIOError);
237  }
238  }
239 
240  const dictionary* dictptr = dict.findDict("controllers");
241  if (dictptr)
242  {
243  controllers_ = HashPtrTable<lumpedPointController>(*dictptr);
244 
245  // Verify the input
246  forAllIters(controllers_, iter)
247  {
248  (*iter)->remapPointLabels(points0.size(), pointIdMap);
249  }
250  }
251  else
252  {
253  // Add single global controller
254  // Warning?
255 
256  controllers_.clear();
257  }
258 
259  relax_ = dict.getOrDefault<scalar>("relax", 1);
260  relax_ = clamp(relax_, zero_one{});
261 
262  forcesDict_.merge(dict.subOrEmptyDict("forces"));
263 
264  const dictionary& commDict = dict.subDict("communication");
265  coupler_.readDict(commDict);
266 
267  calcFrequency_ = commDict.getOrDefault<label>("calcFrequency", 1);
268 
269  // Leave trigger intact
270 
271  commDict.readEntry("inputName", inputName_);
272  commDict.readEntry("outputName", outputName_);
273  commDict.readIfPresent("logName", logName_);
274 
275  inputFormat_ = lumpedPointState::formatNames.get("inputFormat", commDict);
276  outputFormat_ = formatNames.get("outputFormat", commDict);
277 
278  scaleInput_ = -1;
279  scaleOutput_ = -1;
280 
281  const dictionary* scaleDict = nullptr;
282 
283  if ((scaleDict = commDict.findDict("scaleInput")) != nullptr)
284  {
285  for (int i=0; i < scaleInput_.size(); ++i)
286  {
287  const word& key = scalingNames[scalingType(i)];
288 
289  if
290  (
291  scaleDict->readIfPresent(key, scaleInput_[i])
292  && scaleInput_[i] > 0
293  )
294  {
295  Info<<"Using input " << key << " multiplier: "
296  << scaleInput_[i] << nl;
297  }
298  }
299  }
300 
301  if ((scaleDict = commDict.findDict("scaleOutput")) != nullptr)
302  {
303  for (int i=0; i < scaleOutput_.size(); ++i)
304  {
305  const word& key = scalingNames[scalingType(i)];
306 
307  if
308  (
309  scaleDict->readIfPresent(key, scaleOutput_[i])
310  && scaleOutput_[i] > 0
311  )
312  {
313  Info<<"Using output " << key << " multiplier: "
314  << scaleOutput_[i] << nl;
315  }
316  }
317  }
318 
319  state0_ = lumpedPointState
320  (
321  tpoints0,
322  quaternion::eulerOrderNames.getOrDefault
323  (
324  "rotationOrder",
325  dict,
326  quaternion::eulerOrder::ZXZ
327  ),
328  dict.getOrDefault("degrees", false)
329  );
330 
331  state0_.scalePoints(scaleInput_[scalingType::LENGTH]);
332 
333  state_ = state0_;
334 }
335 
338 (
339  const polyPatch& pp
340 ) const
341 {
342  return hasPatchControl(pp.index());
343 }
344 
347 (
348  const pointPatch& fpatch
349 ) const
350 {
351  return hasInterpolator(fpatch.index());
352 }
353 
356 (
357  const polyPatch& pp
358 ) const
359 {
360  const auto ctrlIter = patchControls_.cfind(pp.index());
361 
362  if (!ctrlIter.good())
363  {
365  << "No controllers for patch " << pp.name()
366  << exit(FatalError);
367  }
368 
369  const patchControl& ctrl = *ctrlIter;
370 
371  for (const word& ctrlName : ctrl.names_)
372  {
373  const auto iter = controllers_.cfind(ctrlName);
374 
375  if (!iter.good())
376  {
378  << "No controller: " << ctrlName << nl
379  << " For patch " << pp.name()
380  << exit(FatalError);
381  }
382  }
383 }
384 
387 (
388  const polyPatch& pp,
389  const wordList& ctrlNames,
390  const pointField& points0
391 )
392 {
393  // Reference mass centres
394  const pointField& lumpedCentres0 = state0().points();
395 
396  const label patchIndex = pp.index();
397 
398  // Info<<"Add patch control for patch " << patchIndex << " "
399  // << pp.name() << nl;
400 
401  patchControl& ctrl = patchControls_(patchIndex);
402  ctrl.names_ = ctrlNames;
403 
404  labelList& faceToPoint = ctrl.faceToPoint_;
405  faceToPoint.resize(pp.size(), -1);
406 
407  checkPatchControl(pp);
408 
409  const faceList& faces = pp.boundaryMesh().mesh().faces();
410 
411  // Subset of points to search (if specified)
412  labelHashSet subsetPointIds;
413 
414  for (const word& ctrlName : ctrl.names_)
415  {
416  const auto iter = controllers_.cfind(ctrlName);
417 
418  if (!iter.good())
419  {
421  << "No controller: " << ctrlName << nl
422  << exit(FatalError);
423  }
424 
425  const labelList& pointLabels = (*iter)->pointLabels();
426 
427  subsetPointIds.insert(pointLabels);
428  }
429 
430  if (ctrl.names_.size() && subsetPointIds.empty())
431  {
433  << "Controllers specified, but without any points" << nl
434  << exit(FatalError);
435  }
436 
437 
438  treeBoundBox bb(lumpedCentres0);
439  bb.inflate(0.01);
440 
442  (
444  (
445  lumpedCentres0,
446  subsetPointIds.sortedToc(),
447  subsetPointIds.size() // subset is optional/required
448  ),
449  bb, // overall search domain
450  8, // maxLevel
451  10, // leafsize
452  3.0 // duplicity
453  );
454  const auto& treePoints = ppTree.shapes();
455 
456  const scalar searchDistSqr(sqr(GREAT));
457 
458  const label patchStart = pp.start();
459  forAll(pp, patchFacei)
460  {
461  const point fc(faces[patchStart + patchFacei].centre(points0));
462 
463  // Store the original pointId, not subset id
464  faceToPoint[patchFacei] =
465  treePoints.objectIndex
466  (
467  ppTree.findNearest(fc, searchDistSqr).index()
468  );
469  }
470 
471  if (debug)
472  {
473  Pout<<"Added face mapping for patch: " << patchIndex << endl;
474  }
475 }
476 
479 (
480  const pointPatch& fpatch,
481  const pointField& points0
482 )
483 {
484  // Reference mass centres
485  const pointField& lumpedCentres0 = state0().points();
486 
487  const label patchIndex = fpatch.index();
488 
489  patchControl& ctrl = patchControls_(patchIndex);
490 
491  List<lumpedPointInterpolator>& interpList = ctrl.interp_;
492  interpList.clear();
493 
494  // The connectivity, adjacency list
495  Map<labelHashSet> adjacency;
496 
497  // Subset of points to search (if specified)
498  labelHashSet subsetPointIds;
499 
500  for (const word& ctrlName : ctrl.names_)
501  {
502  const auto iter = controllers_.cfind(ctrlName);
503 
504  if (!iter.good())
505  {
507  << "No controller: " << ctrlName << nl
508  << exit(FatalError);
509  }
510 
511  const labelList& pointLabels = (*iter)->pointLabels();
512 
513  subsetPointIds.insert(pointLabels);
514 
515  // Adjacency lists
516  forAll(pointLabels, i)
517  {
518  const label curr = pointLabels[i];
519 
520  if (i)
521  {
522  const label prev = pointLabels[i-1];
523  adjacency(prev).insert(curr);
524  adjacency(curr).insert(prev);
525  }
526  else if (!adjacency.found(curr))
527  {
528  adjacency(curr).clear();
529  }
530  }
531  }
532 
533  if (ctrl.names_.empty())
534  {
535  // Adjacency lists
536  const label len = state0().size();
537 
538  for (label i=0; i < len; ++i)
539  {
540  const label curr = i;
541 
542  if (i)
543  {
544  const label prev = i-1;
545  adjacency(prev).insert(curr);
546  adjacency(curr).insert(prev);
547  }
548  else if (!adjacency.found(curr))
549  {
550  adjacency(curr).clear();
551  }
552  }
553  }
554 
555  if (ctrl.names_.size() && subsetPointIds.empty())
556  {
558  << "Controllers specified, but without any points" << nl
559  << exit(FatalError);
560  }
561 
562 
563  // Pairs defining connecting points as triangles
564  Map<labelPairList> adjacencyPairs;
565 
566  barycentric2D bary;
567 
568  {
569  // Pairs for the ends
571 
572  // Mag sin(angle) around the triangle point 0,
573  // used to sort the generated triangles according to the acute angle
574  DynamicList<scalar> acuteAngles;
575 
576  forAllConstIters(adjacency, iter)
577  {
578  const label nearest = iter.key();
579 
580  labelList neighbours(iter.val().sortedToc());
581 
582  const label len = neighbours.size();
583 
584  pairs.clear();
585  acuteAngles.clear();
586 
587  const point& nearPt = lumpedCentres0[nearest];
588 
589  for (label j=1; j < len; ++j)
590  {
591  for (label i=0; i < j; ++i)
592  {
593  labelPair neiPair(neighbours[i], neighbours[j]);
594 
595  triPointRef tri
596  (
597  nearPt,
598  lumpedCentres0[neiPair.first()],
599  lumpedCentres0[neiPair.second()]
600  );
601 
602  // Require non-degenerate triangles
603  if (tri.pointToBarycentric(tri.a(), bary) > SMALL)
604  {
605  // Triangle OK
606  pairs.append(neiPair);
607 
608  vector ab(normalised(tri.b() - tri.a()));
609  vector ac(normalised(tri.c() - tri.a()));
610 
611  // Angle between neighbouring edges
612  // Use negative cosine to map [0-180] -> [-1 .. +1]
613 
614  acuteAngles.append(-(ab & ac));
615  }
616  }
617  }
618 
619  if (pairs.size() > 1)
620  {
621  // Sort by acute angle
622  labelList order(sortedOrder(acuteAngles));
623  inplaceReorder(order, pairs);
624  }
625 
626  adjacencyPairs.insert(nearest, pairs);
627  }
628  }
629 
630  if (debug & 2)
631  {
632  Info<< "Adjacency table for patch: " << fpatch.name() << nl;
633 
634  for (const label own : adjacency.sortedToc())
635  {
636  Info<< own << " =>";
637  for (const label nei : adjacency[own].sortedToc())
638  {
639  Info<< ' ' << nei;
640  }
641 
642  if (originalIds_.size())
643  {
644  Info<< " # " << originalIds_[own] << " =>";
645  for (const label nei : adjacency[own].sortedToc())
646  {
647  Info<< ' ' << originalIds_[nei];
648  }
649  }
650 
651  Info<< " # tri " << flatOutput(adjacencyPairs[own]);
652  Info<< nl;
653  }
654  }
655 
656  treeBoundBox bb(lumpedCentres0);
657  bb.inflate(0.01);
658 
660  (
662  (
663  lumpedCentres0,
664  subsetPointIds.sortedToc(),
665  subsetPointIds.size() // subset is optional/required
666  ),
667  bb, // overall search domain
668  8, // maxLevel
669  10, // leafsize
670  3.0 // duplicity
671  );
672  const auto& treePoints = ppTree.shapes();
673 
674 
675  // Searching
676 
677  const scalar searchDistSqr(sqr(GREAT));
678 
679  const labelList& meshPoints = fpatch.meshPoints();
680 
681  interpList.resize(meshPoints.size());
682 
683  DynamicList<scalar> unsortedNeiWeightDist;
684  DynamicList<label> unsortedNeighbours;
685 
686  forAll(meshPoints, pointi)
687  {
688  const point& ptOnMesh = points0[meshPoints[pointi]];
689 
690  // Nearest (original) point id
691  const label nearest =
692  treePoints.objectIndex
693  (
694  ppTree.findNearest(ptOnMesh, searchDistSqr).index()
695  );
696 
697  interpList[pointi].nearest(nearest);
698 
699  if (nearest == -1)
700  {
701  // Should not really happen
702  continue;
703  }
704 
705  // Have the nearest lumped point, now find the next nearest
706  // but check that the direction is also correct.
707 
708  // OK: within the 0-1 bounds
709  // 1+----+0
710  // .
711  // .
712  // +pt
713 
714  const point& nearPt = lumpedCentres0[nearest];
715 
716  const linePointRef toMeshPt(nearPt, ptOnMesh);
717 
718  const labelPairList& adjacentPairs = adjacencyPairs[nearest];
719 
720  unsortedNeighbours = adjacency[nearest].toc();
721  unsortedNeiWeightDist.resize(unsortedNeighbours.size());
722 
723  forAll(unsortedNeighbours, nbri)
724  {
725  unsortedNeiWeightDist[nbri] =
726  magSqr(ptOnMesh - lumpedCentres0[unsortedNeighbours[nbri]]);
727  }
728 
729  // Sort by distance
730  labelList distOrder(sortedOrder(unsortedNeiWeightDist));
731 
732  label ngood = 0;
733 
734  // Recalculate distance as weighting
735  for (const label nbri : distOrder)
736  {
737  const label nextPointi = unsortedNeighbours[nbri];
738 
739  const point& nextPt = lumpedCentres0[nextPointi];
740 
741  const linePointRef toNextPt(nearPt, nextPt);
742 
743  const scalar weight =
744  (toMeshPt.vec() & toNextPt.unitVec()) / toNextPt.mag();
745 
746  if (weight < 0)
747  {
748  // Reject: wrong direction or other bad value
749  continue;
750  }
751  else
752  {
753  // Store weight
754  unsortedNeiWeightDist[nbri] = weight;
755 
756  // Retain good weight
757  distOrder[ngood] = nbri;
758  ++ngood;
759  }
760  }
761 
762  distOrder.resize(ngood);
763 
764  if (distOrder.size() < 1)
765  {
766  continue;
767  }
768 
769  UIndirectList<label> neighbours(unsortedNeighbours, distOrder);
770  UIndirectList<scalar> neiWeight(unsortedNeiWeightDist, distOrder);
771 
772  bool useFirst = true;
773 
774  if (neighbours.size() > 1 && adjacentPairs.size())
775  {
776  // Check for best two neighbours
777 
778  bitSet neiPointid;
779  neiPointid.set(neighbours);
780 
781  for (const labelPair& ends : adjacentPairs)
782  {
783  label nei1 = ends.first();
784  label nei2 = ends.second();
785 
786  if (!neiPointid.test(nei1) || !neiPointid.test(nei2))
787  {
788  // Reject, invalid combination for this point location
789  continue;
790  }
791  else if (neighbours.find(nei2) < neighbours.find(nei1))
792  {
793  // Order by distance, which is not really needed,
794  // but helps with diagnostics
795  std::swap(nei1, nei2);
796  }
797 
798 
799  triFace triF(nearest, nei1, nei2);
800 
801  if
802  (
803  triF.tri(lumpedCentres0).pointToBarycentric(ptOnMesh, bary)
804  > SMALL
805  && !bary.outside()
806  )
807  {
808  // Use barycentric weights
809  interpList[pointi].set(triF, bary);
810 
811  useFirst = false;
812  break;
813  }
814  }
815  }
816 
817  if (useFirst)
818  {
819  // Use geometrically closest neighbour
820  interpList[pointi].next(neighbours.first(), neiWeight.first());
821  }
822  }
823 }
824 
827 (
828  const polyMesh& pmesh
829 ) const
830 {
831  const label nLumpedPoints = state0().size();
832 
833  List<scalar> zoneAreas(nLumpedPoints, Zero);
834 
835  if (patchControls_.empty())
836  {
838  << "Attempted to calculate areas without setMapping()"
839  << nl << endl;
840  return zoneAreas;
841  }
842 
843  const polyBoundaryMesh& patches = pmesh.boundaryMesh();
844 
845  // fvMesh and has pressure field
846  if (isA<fvMesh>(pmesh))
847  {
848  const auto& mesh = refCast<const fvMesh>(pmesh);
849 
850  // Face areas (on patches)
851  const surfaceVectorField::Boundary& patchSf =
852  mesh.Sf().boundaryField();
853 
854  forAllConstIters(patchControls_, iter)
855  {
856  const label patchIndex = iter.key();
857  const patchControl& ctrl = iter.val();
858 
859  const labelList& faceToPoint = ctrl.faceToPoint_;
860 
861  const polyPatch& pp = patches[patchIndex];
862 
863  forAll(pp, patchFacei)
864  {
865  // Force from the patch-face into sum
866  const label pointIndex = faceToPoint[patchFacei];
867 
868  if (pointIndex < 0)
869  {
870  // Unmapped, for whatever reason?
871  continue;
872  }
873 
874  // Force from the patch-face into sum
875  zoneAreas[pointIndex] += mag(patchSf[patchIndex][patchFacei]);
876  }
877  }
878  }
879 
881 
882  return zoneAreas;
883 }
884 
887 (
888  const polyMesh& pmesh,
889  List<vector>& forces,
890  List<vector>& moments
891 ) const
892 {
893  const label nLumpedPoints = state0().size();
894 
895  forces.resize(nLumpedPoints);
896  moments.resize(nLumpedPoints);
897 
898  if (patchControls_.empty())
899  {
901  << "Attempted to calculate forces without setMapping()"
902  << nl << endl;
903 
904  forces.resize(nLumpedPoints, Zero);
905  moments.resize(nLumpedPoints, Zero);
906  return false;
907  }
908 
909  // Initialize with zero
910  forces = Zero;
911  moments = Zero;
912 
913  // Current mass centres
914  const pointField& lumpedCentres = state().points();
915 
916  const polyBoundaryMesh& patches = pmesh.boundaryMesh();
917 
918  const word pName(forcesDict_.getOrDefault<word>("p", "p"));
919  scalar pRef = forcesDict_.getOrDefault<scalar>("pRef", 0);
920  scalar rhoRef = forcesDict_.getOrDefault<scalar>("rhoRef", 1);
921 
922 
923  // Calculated force per patch - cache
924  PtrMap<vectorField> forceOnPatches;
925 
926  const volScalarField* pPtr = pmesh.findObject<volScalarField>(pName);
927 
928  // fvMesh and has pressure field
929  if (isA<fvMesh>(pmesh) && pPtr)
930  {
931  const auto& mesh = refCast<const fvMesh>(pmesh);
932  const volScalarField& p = *pPtr;
933 
934  // Face centres (on patches)
935  const surfaceVectorField::Boundary& patchCf = mesh.Cf().boundaryField();
936 
937  // Face areas (on patches)
938  const surfaceVectorField::Boundary& patchSf = mesh.Sf().boundaryField();
939 
940  // Pressure (on patches)
941  const volScalarField::Boundary& patchPress = p.boundaryField();
942 
943  // rhoRef if the pressure field is dynamic, i.e. p/rho otherwise 1
944  rhoRef = (p.dimensions() == dimPressure ? 1.0 : rhoRef);
945 
946  // Scale pRef by density for incompressible simulations
947  pRef /= rhoRef;
948 
949  forAllConstIters(patchControls_, iter)
950  {
951  const label patchIndex = iter.key();
952  const patchControl& ctrl = iter.val();
953 
954  const labelList& faceToPoint = ctrl.faceToPoint_;
955 
956  if (!forceOnPatches.found(patchIndex))
957  {
958  // Patch faces are +ve outwards,
959  // so the forces (exerted by fluid on solid)
960  // already have the correct sign
961  forceOnPatches.set
962  (
963  patchIndex,
964  (
965  rhoRef
966  * patchSf[patchIndex] * (patchPress[patchIndex] - pRef)
967  ).ptr()
968  );
969  }
970 
971  const vectorField& forceOnPatch = *forceOnPatches[patchIndex];
972 
973  const polyPatch& pp = patches[patchIndex];
974 
975  forAll(pp, patchFacei)
976  {
977  // Force from the patch-face into sum
978  const label pointIndex = faceToPoint[patchFacei];
979 
980  if (pointIndex < 0)
981  {
982  // Unmapped, for whatever reason?
983  continue;
984  }
985 
986  // Force from the patch-face into sum
987  forces[pointIndex] += forceOnPatch[patchFacei];
988 
989  // Effective torque arm:
990  // - translated into the lumped-points coordinate system
991  // prior to determining the distance
992  const vector lever
993  (
994  patchCf[patchIndex][patchFacei]
995  - lumpedCentres[pointIndex]
996  );
997 
998  // Moment from the patch-face into sum
999  moments[pointIndex] += lever ^ forceOnPatch[patchFacei];
1000  }
1001  }
1002  }
1003  else
1004  {
1005  Info<<"No pressure field" << endl;
1006  }
1007 
1010 
1011  return true;
1012 }
1013 
1014 
1017 (
1018  const pointPatch& fpatch,
1019  const pointField& points0
1020 ) const
1021 {
1022  return pointsDisplacement(state(), fpatch, points0);
1023 }
1024 
1025 
1028 (
1029  const lumpedPointState& state,
1030  const pointPatch& fpatch,
1031  const pointField& points0
1032 ) const
1033 {
1034  const label patchIndex = fpatch.index();
1035 
1036  // Reference mass centres
1037  const pointField& lumpedCentres0 = state0().points();
1038 
1039  // Current mass centres
1040  const pointField& lumpedCentres = state.points();
1041 
1042  // The local-to-global transformation tensor
1043  const tensorField& localToGlobal = state.rotations();
1044 
1045  const labelList& meshPoints = fpatch.meshPoints();
1046 
1047  // Could also verify the sizes (state vs original)
1048 
1049  auto tdisp = tmp<pointField>::New(fpatch.size());
1050  auto& disp = tdisp.ref();
1051 
1052  // The interpolator
1053  const List<lumpedPointInterpolator>& interpList
1054  = patchControls_[patchIndex].interp_;
1055 
1056  forAll(meshPoints, pointi)
1057  {
1058  const lumpedPointInterpolator& interp = interpList[pointi];
1059 
1060  const point& p0 = points0[meshPoints[pointi]];
1061 
1062  const vector origin0 = interp.interpolate(lumpedCentres0);
1063  const vector origin = interp.interpolate(lumpedCentres);
1064  const tensor rotTensor = interp.interpolate(localToGlobal);
1065 
1066  disp[pointi] = (rotTensor & (p0 - origin0)) + origin - p0;
1067  }
1068 
1069  return tdisp;
1070 }
1071 
1072 
1075 (
1076  const lumpedPointState& state,
1077  const pointPatch& fpatch,
1078  const pointField& points0
1079 ) const
1080 {
1081  const label patchIndex = fpatch.index();
1082 
1083  // Reference mass centres
1084  const pointField& lumpedCentres0 = state0().points();
1085 
1086  // Current mass centres
1087  const pointField& lumpedCentres = state.points();
1088 
1089  // The local-to-global transformation tensor
1090  const tensorField& localToGlobal = state.rotations();
1091 
1092  const labelList& meshPoints = fpatch.meshPoints();
1093 
1094  // Could also verify the sizes (state vs original)
1095 
1096  auto tdisp = tmp<pointField>::New(fpatch.size());
1097  auto& disp = tdisp.ref();
1098 
1099  // The interpolator
1100  const List<lumpedPointInterpolator>& interpList =
1101  patchControls_[patchIndex].interp_;
1102 
1103  forAll(meshPoints, pointi)
1104  {
1105  const lumpedPointInterpolator& interp = interpList[pointi];
1106 
1107  const point& p0 = points0[meshPoints[pointi]];
1108 
1109  const vector origin0 = interp.interpolate(lumpedCentres0);
1110  const vector origin = interp.interpolate(lumpedCentres);
1111  const tensor rotTensor = interp.interpolate(localToGlobal);
1112 
1113  disp[pointi] = (rotTensor & (p0 - origin0)) + origin;
1114  }
1115 
1116  return tdisp;
1117 }
1119 
1121 {
1122  // os.writeEntry("axis", axis_);
1123  // os.writeEntry("locations", locations_);
1124 }
1126 
1128 {
1129  lumpedPointState prev = state_;
1130 
1131  const bool status = state_.readData
1132  (
1133  inputFormat_,
1134  coupler().resolveFile(inputName_),
1135  state0().rotationOrder(),
1136  state0().degrees()
1137  );
1138 
1139  scalePoints(state_);
1140 
1141  state_.relax(relax_, prev);
1142 
1143  return status;
1144 }
1145 
1148 (
1149  Ostream& os,
1150  const UList<vector>& forces,
1151  const UList<vector>& moments,
1152  const outputFormatType fmt,
1153  const Tuple2<scalar, scalar>* timesWritten
1154 ) const
1155 {
1156  const bool writeMoments = (moments.size() == forces.size());
1157 
1158  if (fmt == outputFormatType::PLAIN)
1159  {
1160  os <<"########" << nl;
1161  if (timesWritten)
1162  {
1163  os << "# Time value=" << timesWritten->first() << nl
1164  << "# Time prev=" << timesWritten->second() << nl;
1165  }
1166  os << "# size=" << this->size() << nl
1167  << "# columns (points) (forces)";
1168 
1169  if (writeMoments)
1170  {
1171  os << " (moments)";
1172  }
1173 
1174  os << nl;
1175 
1176  bool report = false;
1177  scalar scaleLength = scaleOutput_[scalingType::LENGTH];
1178  scalar scaleForce = scaleOutput_[scalingType::FORCE];
1179  scalar scaleMoment = scaleOutput_[scalingType::MOMENT];
1180 
1181  if (scaleLength > 0)
1182  {
1183  report = true;
1184  }
1185  else
1186  {
1187  scaleLength = 1.0;
1188  }
1189 
1190  if (scaleForce > 0)
1191  {
1192  report = true;
1193  }
1194  else
1195  {
1196  scaleForce = 1.0;
1197  }
1198 
1199  if (writeMoments)
1200  {
1201  if (scaleMoment > 0)
1202  {
1203  report = true;
1204  }
1205  else
1206  {
1207  scaleMoment = 1.0;
1208  }
1209  }
1210 
1211  if (report)
1212  {
1213  os <<"# scaling points=" << scaleLength
1214  <<" forces=" << scaleForce;
1215 
1216  if (writeMoments)
1217  {
1218  os <<" moments=" << scaleMoment;
1219  }
1220 
1221  os << nl;
1222  }
1223 
1224  os <<"########" << nl;
1225 
1226  forAll(state0().points(), i)
1227  {
1228  const point& pt = state0().points()[i];
1229 
1230  putPlain(os, scaleLength * pt) << ' ';
1231 
1232  if (i < forces.size())
1233  {
1234  const vector val(scaleForce * forces[i]);
1235  putPlain(os, val);
1236  }
1237  else
1238  {
1239  putPlain(os, vector::zero);
1240  }
1241 
1242  if (writeMoments)
1243  {
1244  os << ' ';
1245  if (i < moments.size())
1246  {
1247  const vector val(scaleMoment * moments[i]);
1248  putPlain(os, val);
1249  }
1250  else
1251  {
1252  putPlain(os, vector::zero);
1253  }
1254  }
1255 
1256  os << nl;
1257  }
1258  }
1259  else
1260  {
1261  // Make it easier for external programs to parse
1262  // - exclude the usual OpenFOAM 'FoamFile' header
1263  // - ensure lists have consistent format
1264 
1265  os <<"////////" << nl;
1266  if (timesWritten)
1267  {
1268  os.writeEntry("time", timesWritten->first());
1269  os.writeEntry("prevTime", timesWritten->second());
1270  }
1271  os << nl;
1272 
1273  writeList(os, "points", state0().points());
1274  writeList(os, "forces", forces);
1275 
1276  if (writeMoments)
1277  {
1278  writeList(os, "moments", moments);
1279  }
1280  }
1281 
1282  return true;
1283 }
1284 
1287 (
1288  const UList<vector>& forces,
1289  const UList<vector>& moments,
1290  const Tuple2<scalar, scalar>* timesWritten
1291 ) const
1292 {
1293  if (!Pstream::master())
1294  {
1295  return false;
1296  }
1297 
1298  // Regular output
1299  {
1300  OFstream os
1301  (
1302  coupler().resolveFile(outputName_)
1303  );
1304 
1305  writeData(os, forces, moments, outputFormat_, timesWritten);
1306  }
1307 
1308  // Log output
1309  {
1310  OFstream os
1311  (
1312  coupler().resolveFile(logName_),
1313  IOstreamOption(),
1315  );
1316 
1317  writeData(os, forces, moments, outputFormatType::PLAIN, timesWritten);
1318  }
1319 
1320  return true;
1321 }
1322 
1323 
1324 // ************************************************************************* //
Foam::surfaceFields.
EnumType get(const word &enumName) const
The enumeration corresponding to the given name.
Definition: Enum.C:68
Point unitVec() const
Return the unit vector (start-to-end)
Definition: lineI.H:122
const T & first() const noexcept
Access the first element.
Definition: Pair.H:137
bool set(const Key &key)
Same as insert (no value to overwrite)
Definition: HashSet.H:235
dictionary dict
void size(const label n)
Older name for setAddressableSize.
Definition: UList.H:116
const surfaceVectorField & Sf() const
Return cell face area vectors.
A triangle primitive used to calculate face normals and swept volumes. Uses referred points...
Definition: triangle.H:217
A line primitive.
Definition: line.H:52
List< scalar > areas(const polyMesh &pmesh) const
The areas for each pressure-zone.
void set(const bitSet &bitset)
Set specified bits from another bitset.
Definition: bitSetI.H:571
Inter-processor communication reduction functions.
Holds (reference to) pointField. Encapsulation of data needed for octree searches. Used for searching for nearest point. No bounding boxes around points. Only overlaps and calcNearest are implemented, rest makes little sense.
Definition: treeDataPoint.H:58
errorManipArg< error, int > exit(error &err, const int errNo=1)
Definition: errorManip.H:125
tmp< pointField > pointsPosition(const lumpedPointState &state, const pointPatch &fpatch, const pointField &points0) const
The points absolute position according to specified state.
dimensioned< typename typeOfMag< Type >::type > mag(const dimensioned< Type > &dt)
barycentric2D pointToBarycentric(const point &pt) const
Calculate the barycentric coordinates from the given point.
Definition: triangleI.H:424
labelList pointLabels(nPoints, -1)
static void writeData(Ostream &os, const Type &val)
void resize(const label len)
Adjust allocated size of list.
Definition: ListI.H:132
scalar mag() const
The magnitude (length) of the line.
Definition: lineI.H:96
error FatalError
Error stream (stdout output on all processes), with additional &#39;FOAM FATAL ERROR&#39; header text and sta...
A list of keyword definitions, which are a keyword followed by a number of values (eg...
Definition: dictionary.H:120
#define FatalErrorInFunction
Report an error message using Foam::FatalError.
Definition: error.H:578
const surfaceVectorField & Cf() const
Return face centres as surfaceVectorField.
void setInterpolator(const pointPatch &fpatch, const pointField &points0)
Check if patch control exists for specified patch.
triPointRef tri(const UList< point > &points) const
Return the triangle.
Definition: triFaceI.H:167
bool found(const label &key) const
Same as contains()
Definition: HashTable.H:1333
const Type & shapes() const noexcept
Reference to shape.
labelList sortedOrder(const UList< T > &input)
Return the (stable) sort order for the list.
bool outside() const
True if any coordinate is negative.
void inflate(const scalar factor)
Expand box by factor*mag(span) in all dimensions.
Definition: boundBoxI.H:381
std::enable_if< std::is_same< bool, TypeT >::value, bool >::type set(const label i, bool val=true)
A bitSet::set() method for a list of bool.
Definition: List.H:472
dimensionedSymmTensor sqr(const dimensionedVector &dv)
Output to file stream, using an OSstream.
Definition: OFstream.H:49
static const Enum< outputFormatType > formatNames
Names for the output format types.
constexpr char nl
The newline &#39;\n&#39; character (0x0a)
Definition: Ostream.H:49
void inplaceReorder(const labelUList &oldToNew, ListType &input, const bool prune=false)
Inplace reorder the elements of a list.
int debugSwitch(const char *name, const int deflt=0)
Lookup debug switch or add default value.
Definition: debug.C:222
void resize(const label len)
Alter addressable list size, allocating new space if required while recovering old content...
Definition: DynamicListI.H:351
Ostream & endl(Ostream &os)
Add newline and flush stream.
Definition: Ostream.H:487
A HashTable of pointers to objects of type <T> with a label key.
Definition: HashTableFwd.H:42
T interpolate(const UList< T > &input) const
Linear interpolated value between nearest and next locations.
Begin list [isseparator].
Definition: token.H:158
void couplingCompleted(const label timeIndex) const
Register that coupling is completed at this calcFrequency.
void readDict(const dictionary &dict)
Update settings from dictionary.
Ostream & writeEntry(const keyType &key, const T &value)
Write a keyword/value entry.
Definition: Ostream.H:312
const Type * findObject(const word &name, const bool recursive=false) const
Return const pointer to the object of the given Type.
T & first()
Access first element of the list, position [0].
A simple container for options an IOstream can normally have.
End entry [isseparator].
Definition: token.H:157
A HashTable of pointers to objects of type <T>, with deallocation management of the pointers...
Definition: HashPtrTable.H:51
quaternion normalised(const quaternion &q)
Return the normalised (unit) quaternion of the given quaternion.
Definition: quaternionI.H:673
bool writeData(Ostream &os, const UList< vector > &forces, const UList< vector > &moments, const outputFormatType fmt=outputFormatType::PLAIN, const Tuple2< scalar, scalar > *timesWritten=nullptr) const
Write points, forces, moments. Only call from the master process.
bool insert(const Key &key)
Insert a new entry, not overwriting existing entries.
Definition: HashSet.H:227
void setPatchControl(const polyPatch &pp, const wordList &ctrlNames, const pointField &points0)
Define pressure-zones mapping for faces in the specified patches.
const dictionary & subDict(const word &keyword, enum keyType::option matchOpt=keyType::REGEX) const
Find and return a sub-dictionary.
Definition: dictionary.C:453
static const Enum< eulerOrder > eulerOrderNames
The names for Euler-angle and Tait-Bryan angles, including "rollPitchYaw" and "yawPitchRoll" aliases...
Definition: quaternion.H:132
static const Enum< scalingType > scalingNames
Names for the scaling types.
scalingType
Output format types.
#define forAll(list, i)
Loop across all elements in list.
Definition: stdFoam.H:414
static const word canonicalName
The canonical name ("lumpedPointMovement") for the dictionary.
Templated 2D Barycentric derived from VectorSpace. Has 3 components, one of which is redundant...
Definition: Barycentric2D.H:50
static const Enum< inputFormatType > formatNames
Names for the input format types.
bool insert(const Key &key, const T &obj)
Copy insert a new entry, not overwriting existing entries.
Definition: HashTableI.H:152
bool readEntry(const word &keyword, T &val, enum keyType::option matchOpt=keyType::REGEX, IOobjectOption::readOption readOpt=IOobjectOption::MUST_READ) const
Find entry and assign to T val. FatalIOError if it is found and the number of tokens is incorrect...
label size() const noexcept
The number of elements in table.
Definition: HashTable.H:331
A simple linear interpolator between two locations, which are referenced by index.
dynamicFvMesh & mesh
const pointField & points
A 1D vector of objects of type <T> that resizes itself as necessary to accept the new objects...
Definition: DynamicList.H:51
const_iterator cfind(const Key &key) const
Find and return an const_iterator set at the hashed entry.
Definition: HashTableI.H:113
void clear()
Clear the list, i.e. set size to zero.
Definition: ListI.H:109
const polyBoundaryMesh & boundaryMesh() const noexcept
Return boundary mesh.
Definition: polyMesh.H:584
A class for handling words, derived from Foam::string.
Definition: word.H:63
A triangular face using a FixedList of labels corresponding to mesh vertices.
Definition: triFace.H:65
bool forcesAndMoments(const polyMesh &pmesh, List< vector > &forces, List< vector > &moments) const
The forces and moments acting on each pressure-zone.
void clear()
Clear all entries from table.
Definition: HashTable.C:705
bool hasInterpolator(const pointPatch &fpatch) const
Check if patch control exists for specified patch.
#define forAllIters(container, iter)
Iterate across all elements in the container object.
Definition: stdFoam.H:329
outputFormatType
Output format types.
static tmp< T > New(Args &&... args)
Construct tmp with forwarding arguments.
Definition: tmp.H:212
virtual const word & name() const =0
Return name.
End list [isseparator].
Definition: token.H:159
Vector< scalar > vector
Definition: vector.H:57
void append(const T &val)
Copy append an element to the end of this list.
Definition: DynamicList.H:570
label size() const noexcept
The number of elements in the list.
const dimensionSet dimPressure
label find(const T &val, label pos=0) const
Find index of the first occurrence of the value.
bool test(const label pos) const
Test for True value at specified position, never auto-vivify entries.
Definition: bitSet.H:341
A 1D vector of objects of type <T>, where the size of the vector is known and can be used for subscri...
Definition: HashTable.H:99
A polyBoundaryMesh is a polyPatch list with additional search methods and registered IO...
An Ostream is an abstract base class for all output systems (streams, files, token lists...
Definition: Ostream.H:55
bool empty() const noexcept
True if the hash table is empty.
Definition: HashTable.H:326
void writeList(vtk::formatter &fmt, const UList< uint8_t > &values)
Write a list of uint8_t values.
A Vector of values with scalar precision, where scalar is float/double depending on the compilation f...
int debug
Static debugging option.
bool readIfPresent(const word &keyword, T &val, enum keyType::option matchOpt=keyType::REGEX) const
Find an entry if present, and assign to T val. FatalIOError if it is found and the number of tokens i...
tmp< pointField > pointsDisplacement(const pointPatch &fpatch, const pointField &points0) const
Displace points according to the current state.
OBJstream os(runTime.globalPath()/outputName)
bool readState()
Read state from file, applying relaxation as requested.
void clear() noexcept
Clear the addressed list, i.e. set the size to zero.
Definition: DynamicListI.H:389
dictionary subOrEmptyDict(const word &keyword, enum keyType::option matchOpt=keyType::REGEX, const bool mandatory=false) const
Find and return a sub-dictionary as a copy, otherwise return an empty dictionary. ...
Definition: dictionary.C:533
void scalePoints(const scalar scaleFactor)
Scale points by given factor.
void writeDict(Ostream &os) const
Write axis, locations, division as a dictionary.
Represents 0/1 range or concept. Used for tagged dispatch or clamping.
Definition: pTraits.H:84
Point vec() const
Return start-to-end vector.
Definition: lineI.H:109
void checkPatchControl(const polyPatch &pp) const
Check if patch control exists for specified patch.
pointField points0(pointIOField(IOobject("points", mesh.time().constant(), polyMesh::meshSubDir, mesh, IOobject::MUST_READ, IOobject::NO_WRITE, IOobject::NO_REGISTER)))
bool couplingPending(const label timeIndex) const
Check if coupling is pending (according to the calcFrequency)
Non-pointer based hierarchical recursive searching.
A bitSet stores bits (elements with only two states) in packed internal format and supports a variety...
Definition: bitSet.H:59
#define WarningInFunction
Report a warning using Foam::Warning.
Enum is a wrapper around a list of names/values that represent particular enumeration (or int) values...
#define FatalIOErrorInFunction(ios)
Report an error message using Foam::FatalIOError.
Definition: error.H:607
Basic pointPatch represents a set of points from the mesh.
Definition: pointPatch.H:61
virtual label index() const =0
Return the index of this patch in the pointBoundaryMesh.
auto key(const Type &t) -> typename std::enable_if< std::is_enum< Type >::value, typename std::underlying_type< Type >::type >::type
Definition: foamGltfBase.H:103
const tensorField & rotations() const
The local-to-global transformation for each point.
List< Key > sortedToc() const
The table of contents (the keys) in sorted order.
Definition: HashTable.C:130
A List with indirect addressing. Like IndirectList but does not store addressing. ...
Definition: faMatrix.H:56
const T2 & second() const noexcept
Access the second element.
Definition: Tuple2.H:142
static bool master(const label communicator=worldComm)
True if process corresponds to the master rank in the communicator.
Definition: UPstream.H:1037
const polyBoundaryMesh & patches
const pointField & points() const
The points corresponding to mass centres.
static int debug
Debug switch.
Standard boundBox with extra functionality for use in octree.
Definition: treeBoundBox.H:90
List< label > sortedToc(const UList< bool > &bools)
Return the (sorted) values corresponding to &#39;true&#39; entries.
Definition: BitOps.C:195
messageStream Info
Information stream (stdout output on master, null elsewhere)
Mesh consisting of general polyhedral cells.
Definition: polyMesh.H:73
List< label > toc() const
The table of contents (the keys) in unsorted order.
Definition: HashTable.C:115
T getOrDefault(const word &keyword, const T &deflt, enum keyType::option matchOpt=keyType::REGEX) const
Find and return a T, or return the given default value. FatalIOError if it is found and the number of...
volScalarField & p
A class for managing temporary objects.
Definition: HashPtrTable.H:50
A patch is a list of labels that address the faces in the global face list.
Definition: polyPatch.H:69
const T & second() const noexcept
Access the second element.
Definition: Pair.H:147
const T1 & first() const noexcept
Access the first element.
Definition: Tuple2.H:132
bool readData(Istream &is, const quaternion::eulerOrder rotOrder=quaternion::eulerOrder::ZXZ, const bool degrees=false)
Read input as dictionary content.
Tensor of scalars, i.e. Tensor<scalar>.
bool hasPatchControl(const label patchIndex) const
Check if patch control exists for specified patch.
const Boundary & boundaryField() const noexcept
Return const-reference to the boundary field.
prefixOSstream Pout
OSstream wrapped stdout (std::cout) with parallel prefix.
lumpedPointMovement()
Default construct.
const volScalarField & p0
Definition: EEqn.H:36
dimensioned< typename typeOfMag< Type >::type > magSqr(const dimensioned< Type > &dt)
The state of lumped points corresponds to positions and rotations.
uindirectPrimitivePatch pp(UIndirectList< face >(mesh.faces(), faceLabels), mesh.points())
Namespace for OpenFOAM.
virtual const labelList & meshPoints() const =0
Return mesh points.
forAllConstIters(mixture.phases(), phase)
Definition: pEqn.H:28
dimensionSet clamp(const dimensionSet &a, const dimensionSet &range)
Definition: dimensionSet.C:271
label timeIndex
Definition: getTimeIndex.H:24
const dimensionSet & dimensions() const noexcept
Return dimensions.
static void listCombineReduce(List< T > &values, const CombineOp &cop, const int tag=UPstream::msgType(), const label comm=UPstream::worldComm)
After completion all processors have the same data.
A topoSetPointSource to select all points based on usage in given faceSet(s).
Definition: faceToPoint.H:170
virtual label size() const =0
Return size.
FlatOutput::OutputAdaptor< Container, Delimiters > flatOutput(const Container &obj, Delimiters delim)
Global flatOutput() function with specified output delimiters.
Definition: FlatOutput.H:225
const dictionary * findDict(const word &keyword, enum keyType::option matchOpt=keyType::REGEX) const
Find and return a sub-dictionary pointer if present (and a sub-dictionary) otherwise return nullptr...
Definition: dictionaryI.H:120
IOerror FatalIOError
Error stream (stdout output on all processes), with additional &#39;FOAM FATAL IO ERROR&#39; header text and ...
static constexpr const zero Zero
Global zero (0)
Definition: zero.H:133