baffle splitting in parallel

This commit is contained in:
mattijs
2008-07-18 12:59:26 +01:00
parent e3f44132cf
commit 537f11310f
6 changed files with 527 additions and 1238 deletions

View File

@ -154,100 +154,6 @@ void insertDuplicateMerge
} }
// Get points on the inside of baffle regions.
labelList getNonManifoldPointsInsideBaffles
(
const primitiveMesh& mesh,
const labelList& baffleFaces
)
{
// Get points to split. These are the edges of the duplicate-faces
// region
indirectPrimitivePatch dupPatch
(
IndirectList<face>(mesh.faces(), baffleFaces),
mesh.points()
);
labelHashSet insideSet(dupPatch.nPoints());
// Pick up all points but the ones on the edge of the region.
// Edge of the region since has two faces along edge.
forAll(dupPatch.meshPoints(), i)
{
insideSet.insert(dupPatch.meshPoints()[i]);
}
forAll(dupPatch.edgeFaces(), edgeI)
{
const labelList& eFaces = dupPatch.edgeFaces()[edgeI];
if (eFaces.size() == 2)
{
const edge& e = dupPatch.edges()[edgeI];
insideSet.erase(dupPatch.meshPoints()[e[0]]);
insideSet.erase(dupPatch.meshPoints()[e[1]]);
}
}
return insideSet.toc();
}
// Find all non-manifold points on the outside of the mesh.
labelList getAllNonManifoldPoints
(
const polyMesh& mesh,
const labelList& boundaryFaces
)
{
indirectPrimitivePatch allOutside
(
IndirectList<face>(mesh.faces(), boundaryFaces),
mesh.points()
);
// All points on non-manifold edges.
boolList nonManifoldPoint(mesh.nPoints(), false);
forAll(allOutside.meshPoints(), localPointI)
{
label pointI = allOutside.meshPoints()[localPointI];
if (!localPointRegion::isSingleCellRegion(mesh, pointI))
{
nonManifoldPoint[pointI] = true;
}
}
// Splittable only if all processors decide to split it.
syncTools::syncPointList
(
mesh,
nonManifoldPoint,
andEqOp<bool>(), // combineop
true, // null value
false // no separation
);
// Extract 'true' elements
labelList nonManifPoints(findIndices(nonManifoldPoint, true));
// Write to pointSet for ease of postprocessing
pointSet nonManifPointSet(mesh, "nonManifoldPoints", nonManifPoints);
Pout<< "Writing " << nonManifPointSet.size()
<< " non-manif points to " << nonManifPointSet.objectPath()
<< endl;
nonManifPointSet.write();
return nonManifPoints;
}
int main(int argc, char *argv[]) int main(int argc, char *argv[])
{ {
argList::validOptions.insert("split", ""); argList::validOptions.insert("split", "");
@ -316,26 +222,14 @@ int main(int argc, char *argv[])
<< ", i.e. duplicating points internal to duplicate surfaces." << ", i.e. duplicating points internal to duplicate surfaces."
<< nl << endl; << nl << endl;
labelList nonManifPoints
(
getAllNonManifoldPoints
(
mesh,
boundaryFaces
)
);
// Analyse which points need to be duplicated // Analyse which points need to be duplicated
localPointRegion regionSide(mesh, nonManifPoints); localPointRegion regionSide(mesh);
// Point duplication engine
duplicatePoints pointDuplicator(mesh); duplicatePoints pointDuplicator(mesh);
pointDuplicator.setRefinement // Insert topo changes
( pointDuplicator.setRefinement(regionSide, meshMod);
nonManifPoints,
regionSide,
meshMod
);
} }
else else
{ {

View File

@ -40,7 +40,7 @@ License
#include "polyRemoveFace.H" #include "polyRemoveFace.H"
#include "polyAddPoint.H" #include "polyAddPoint.H"
#include "localPointRegion.H" #include "localPointRegion.H"
//#include "directDuplicatePoints.H" #include "duplicatePoints.H"
#include "OFstream.H" #include "OFstream.H"
#include "regionSplit.H" #include "regionSplit.H"
#include "removeCells.H" #include "removeCells.H"
@ -458,11 +458,6 @@ Foam::autoPtr<Foam::mapPolyMesh> Foam::meshRefinement::createBaffles
{ {
mesh_.movePoints(map().preMotionPoints()); mesh_.movePoints(map().preMotionPoints());
} }
else
{
// Delete mesh volumes. No other way to do this?
mesh_.clearOut();
}
//- Redo the intersections on the newly create baffle faces. Note that //- Redo the intersections on the newly create baffle faces. Note that
// this changes also the cell centre positions. // this changes also the cell centre positions.
@ -1182,11 +1177,6 @@ Foam::autoPtr<Foam::mapPolyMesh> Foam::meshRefinement::mergeBaffles
{ {
mesh_.movePoints(map().preMotionPoints()); mesh_.movePoints(map().preMotionPoints());
} }
else
{
// Delete mesh volumes. No other way to do this?
mesh_.clearOut();
}
// Update intersections. Recalculate intersections on merged faces since // Update intersections. Recalculate intersections on merged faces since
// this seems to give problems? Note: should not be nessecary since // this seems to give problems? Note: should not be nessecary since
@ -2050,273 +2040,29 @@ Foam::autoPtr<Foam::mapPolyMesh> Foam::meshRefinement::splitMesh
// split them. // split them.
Foam::autoPtr<Foam::mapPolyMesh> Foam::meshRefinement::dupNonManifoldPoints() Foam::autoPtr<Foam::mapPolyMesh> Foam::meshRefinement::dupNonManifoldPoints()
{ {
// Pick all points
// - on the outside
// - that are used by faces that are not all reachable by single
// cell-face-cell walk
indirectPrimitivePatch allOutside
(
IndirectList<face>
(
mesh_.faces(),
identity
(
mesh_.nFaces()
- mesh_.nInternalFaces()
)
+ mesh_.nInternalFaces()
),
mesh_.points()
);
// All points on non-manifold regions.
labelList nonManifPoints;
{
pointSet nonManifPointSet
(
mesh_,
"nonManifoldPoints",
mesh_.nPoints()/1000
);
forAll(allOutside.meshPoints(), localPointI)
{
label pointI = allOutside.meshPoints()[localPointI];
if (!localPointRegion::isSingleCellRegion(mesh_, pointI))
{
nonManifPointSet.insert(pointI);
}
}
// Remove points on coupled boundaries since these are too hard
// to handle (and snapping probably goes wrong on them anyway)
const polyBoundaryMesh& patches = mesh_.boundaryMesh();
forAll(patches, patchI)
{
const polyPatch& pp = patches[patchI];
if (pp.coupled())
{
const labelList& meshPoints = pp.meshPoints();
forAll(meshPoints, i)
{
nonManifPointSet.erase(meshPoints[i]);
}
}
}
if (debug)
{
Pout<< "Writing " << nonManifPointSet.size()
<< " non-manif points to " << nonManifPointSet.objectPath()
<< endl;
nonManifPointSet.write();
}
label nNonManifPoints = nonManifPointSet.size();
reduce(nNonManifPoints, sumOp<label>());
Info<< "dupNonManifoldPoints : Found : " << nNonManifPoints
<< " non-manifold points (out of "
<< mesh_.globalData().nTotalPoints()
<< ')' << endl;
nonManifPoints = nonManifPointSet.toc();
}
// Topochange container // Topochange container
polyTopoChange meshMod(mesh_); polyTopoChange meshMod(mesh_);
// // Analyse which points need to be duplicated // Analyse which points need to be duplicated
// localPointRegion regionSide(mesh_, nonManifPoints); localPointRegion regionSide(mesh_);
//
// Pout<< "dupNonManifoldPoints : Found regions:"
// << regionSide.nRegions() << endl;
//
// // Topo change engine
// directDuplicatePoints pointDuplicator(mesh_);
//
// pointDuplicator.setRefinement
// (
// nonManifPoints,
// regionSide,
// meshMod
// );
//DIY replacement of localPointRegion+directDuplicatePoints label nNonManifPoints = returnReduce
{ (
labelListList pointFaceRegion(nonManifPoints.size()); regionSide.meshPointMap().size(),
forAll(nonManifPoints, i) sumOp<label>()
{ );
label pointI = nonManifPoints[i];
const labelList& pFaces = mesh_.pointFaces()[pointI]; Info<< "dupNonManifoldPoints : Found : " << nNonManifPoints
<< " non-manifold points (out of "
<< mesh_.globalData().nTotalPoints()
<< ')' << endl;
labelList& pRegions = pointFaceRegion[i]; // Topo change engine
pRegions.setSize(pFaces.size()); duplicatePoints pointDuplicator(mesh_);
pRegions = -1;
// Walk cell face cell on the point
label regionI = 0;
forAll(pRegions, j)
{
label faceI = pFaces[j];
label nChanged = localPointRegion::walkCellFaceCell
(
mesh_,
mesh_.faceOwner()[faceI],
pointI,
regionI,
pRegions
);
if (nChanged > 0)
{
regionI++;
}
}
if (regionI <= 1)
{
FatalErrorIn("dupNonManifoldPoints")
<< "Problem pointI:" << pointI
<< " pRegions:" << pRegions << " nRegions:" << regionI
<< abort(FatalError);
}
}
// Do point duplication
// ~~~~~~~~~~~~~~~~~~~~
// Copy of faces
faceList newFaces(mesh_.faces());
// For all points-to-be-duplicated replace occurences of region with
// newly added point
forAll(nonManifPoints, nonI)
{
label pointI = nonManifPoints[nonI];
// Per local region the point to use (original or added)
Map<label> regionToPoint;
// original point gets used for region 0
regionToPoint.insert(0, pointI);
const labelList& pFaces = mesh_.pointFaces()[pointI];
const labelList& pRegions = pointFaceRegion[nonI];
forAll(pFaces, pFaceI)
{
// Get connected face and its region
label faceI = pFaces[pFaceI];
label region = pRegions[pFaceI];
label newPointI = -1;
Map<label>::const_iterator iter = regionToPoint.find(region);
if (iter != regionToPoint.end())
{
newPointI = iter();
}
else
{
// Add a point for this region
newPointI = meshMod.setAction
(
polyAddPoint
(
mesh_.points()[pointI], // point
pointI, // master point
-1, // zone for point
true // supports a cell
)
);
regionToPoint.insert(region, newPointI);
}
// Replace point with newPointI
if (newPointI != pointI)
{
const face& f = mesh_.faces()[faceI];
label fp = findIndex(f, pointI);
newFaces[faceI][fp] = newPointI;
// Extra check.
{
labelList indices = findIndices(f, pointI);
if (indices.size() != 1)
{
FatalErrorIn("dupNonManifoldPoints")
<< "point:" << pointI
<< " coord:" << mesh_.points()[pointI]
<< " face:" << faceI << " verts:" << f
<< " indices:" << indices
<< abort(FatalError);
}
}
}
}
}
// Modify faces
forAll(newFaces, faceI)
{
if (newFaces[faceI] != mesh_.faces()[faceI])
{
label own = mesh_.faceOwner()[faceI];
label nei = -1;
label patchID = -1;
if (mesh_.isInternalFace(faceI))
{
nei = mesh_.faceNeighbour()[faceI];
}
else
{
patchID = mesh_.boundaryMesh().whichPatch(faceI);
}
// Get current zone info
label zoneID = mesh_.faceZones().whichZone(faceI);
bool zoneFlip = false;
if (zoneID >= 0)
{
const faceZone& fZone = mesh_.faceZones()[zoneID];
zoneFlip = fZone.flipMap()[fZone.whichFace(faceI)];
}
meshMod.setAction
(
polyModifyFace
(
newFaces[faceI], // modified face
faceI, // label of face being modified
own, // owner
nei, // neighbour
false, // face flip
patchID, // patch for face
false, // remove from zone
zoneID, // zone for face
zoneFlip // face flip in zone
)
);
}
}
}
//END OF DIY
// Insert changes into meshMod
pointDuplicator.setRefinement(regionSide, meshMod);
// Change the mesh (no inflation, parallel sync) // Change the mesh (no inflation, parallel sync)
autoPtr<mapPolyMesh> map = meshMod.changeMesh(mesh_, false, true); autoPtr<mapPolyMesh> map = meshMod.changeMesh(mesh_, false, true);
@ -2329,11 +2075,6 @@ Foam::autoPtr<Foam::mapPolyMesh> Foam::meshRefinement::dupNonManifoldPoints()
{ {
mesh_.movePoints(map().preMotionPoints()); mesh_.movePoints(map().preMotionPoints());
} }
else
{
// Delete mesh volumes. No other way to do this?
mesh_.clearOut();
}
// Update intersections. Is mapping only (no faces created, positions stay // Update intersections. Is mapping only (no faces created, positions stay
// same) so no need to recalculate intersections. // same) so no need to recalculate intersections.
@ -2757,11 +2498,6 @@ Foam::autoPtr<Foam::mapPolyMesh> Foam::meshRefinement::zonify
{ {
mesh_.movePoints(map().preMotionPoints()); mesh_.movePoints(map().preMotionPoints());
} }
else
{
// Delete mesh volumes. No other way to do this?
mesh_.clearOut();
}
return map; return map;
} }

View File

@ -30,6 +30,9 @@ License
#include "polyAddPoint.H" #include "polyAddPoint.H"
#include "polyModifyFace.H" #include "polyModifyFace.H"
#include "polyMesh.H" #include "polyMesh.H"
#include "OFstream.H"
#include "meshTools.H"
#include "Time.H"
// * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * // // * * * * * * * * * * * * * * Static Data Members * * * * * * * * * * * * * //
@ -40,8 +43,6 @@ defineTypeNameAndDebug(duplicatePoints, 0);
} }
// * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * //
// * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * // // * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * //
@ -57,98 +58,92 @@ Foam::duplicatePoints::duplicatePoints(const polyMesh& mesh)
void Foam::duplicatePoints::setRefinement void Foam::duplicatePoints::setRefinement
( (
const labelList& nonManifPoints,
const localPointRegion& regionSide, const localPointRegion& regionSide,
polyTopoChange& meshMod polyTopoChange& meshMod
) )
{ {
const labelList& faceRegion = regionSide.faceRegion(); const Map<label>& meshPointMap = regionSide.meshPointMap();
const labelList& meshFaces = regionSide.meshFaces(); const labelListList& pointRegions = regionSide.pointRegions();
const Map<label>& localFaces = regionSide.localFaces(); const Map<label>& meshFaceMap = regionSide.meshFaceMap();
const faceList& faceRegions = regionSide.faceRegions();
const polyBoundaryMesh& patches = mesh_.boundaryMesh();
// New faces // Create duplicates for points. One for each region.
faceList newFaces(faceRegion.size()); // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Initialise to current faces // Per point-to-be-duplicated, in order of the regions the point added.
forAll(meshFaces, localFaceI) duplicates_.setSize(meshPointMap.size());
forAllConstIter(Map<label>, meshPointMap, iter)
{ {
newFaces[localFaceI] = mesh_.faces()[meshFaces[localFaceI]]; label pointI = iter.key();
label localI = iter();
const labelList& regions = pointRegions[localI];
duplicates_[localI].setSize(regions.size());
duplicates_[localI][0] = pointI;
for (label i = 1; i < regions.size(); i++)
{
duplicates_[localI][i] = meshMod.addPoint
(
mesh_.points()[pointI], // point
pointI, // master point
-1, // zone for point
true // supports a cell
);
}
//Pout<< "For point:" << pointI << " coord:" << mesh_.points()[pointI]
// << endl;
//forAll(duplicates_[localI], i)
//{
// Pout<< " region:" << regions[i]
// << " addedpoint:" << duplicates_[localI][i]
// << endl;
//}
} }
duplicates_.setSize(regionSide.pointRegions().size());
// Create new point for point with more than one region // Modfify faces according to face region
forAll(regionSide.pointRegions(), localPointI) // ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
face newFace;
forAllConstIter(Map<label>, meshFaceMap, iter)
{ {
label pointI = nonManifPoints[localPointI]; label faceI = iter.key();
const labelList& regions = regionSide.pointRegions()[localPointI]; label localI = iter();
if (regions.size() > 1) // Replace points with duplicated ones.
const face& fRegion = faceRegions[localI];
const face& f = mesh_.faces()[faceI];
newFace.setSize(f.size());
forAll(f, fp)
{ {
// First region for point gets the original point label label pointI = f[fp];
duplicates_[localPointI].setSize(regions.size());
duplicates_[localPointI][0] = pointI;
for (label i = 1; i < regions.size(); i++) Map<label>::const_iterator iter = meshPointMap.find(pointI);
if (iter != meshPointMap.end())
{ {
// Add a point for the point for all faces with the same region // Point has been duplicated. Find correct one for my
label addedPointI = meshMod.setAction // region.
(
polyAddPoint
(
mesh_.points()[pointI], // point
pointI, // master point
-1, // zone for point
true // supports a cell
)
);
// Store added point // Get the regions and added points for this point
duplicates_[localPointI][i] = addedPointI; const labelList& regions = pointRegions[iter()];
const labelList& dupPoints = duplicates_[iter()];
const labelList& pFaces = mesh_.pointFaces()[pointI]; // Look up index of my region in the regions for this point
label index = findIndex(regions, fRegion[fp]);
// Replace all the vertices with the same region with the new // Get the corresponding added point
// point label. newFace[fp] = dupPoints[index];
forAll(pFaces, pFaceI) }
{ else
label faceI = pFaces[pFaceI]; {
label localFaceI = localFaces[faceI]; newFace[fp] = pointI;
if (faceRegion[localFaceI] == regions[i])
{
const face& f = mesh_.faces()[faceI];
forAll(f, fp)
{
if (f[fp] == pointI)
{
newFaces[localFaceI][fp] = addedPointI;
}
}
}
}
} }
}
}
// Modify the faces
forAll(meshFaces, localFaceI)
{
label faceI = meshFaces[localFaceI];
label own = mesh_.faceOwner()[faceI];
label nei = -1;
label patchID = -1;
if (mesh_.isInternalFace(faceI))
{
nei = mesh_.faceNeighbour()[faceI];
}
else
{
patchID = mesh_.boundaryMesh().whichPatch(faceI);
} }
// Get current zone info // Get current zone info
@ -160,22 +155,37 @@ void Foam::duplicatePoints::setRefinement
zoneFlip = fZone.flipMap()[fZone.whichFace(faceI)]; zoneFlip = fZone.flipMap()[fZone.whichFace(faceI)];
} }
meshMod.setAction meshMod.modifyFace
( (
polyModifyFace newFace, // modified face
( faceI, // label of face being modified
newFaces[localFaceI], // modified face mesh_.faceOwner()[faceI], // owner
faceI, // label of face being modified -1, // neighbour
own, // owner false, // face flip
nei, // neighbour patches.whichPatch(faceI), // patch for face
false, // face flip zoneID, // zone for face
patchID, // patch for face zoneFlip // face flip in zone
false, // remove from zone
zoneID, // zone for face
zoneFlip // face flip in zone
)
); );
} }
if (debug)
{
// Output duplicated points
{
OFstream str(mesh_.time().path()/"duplicatedPoints.obj");
forAllConstIter(Map<label>, meshPointMap, iter)
{
label localI = iter();
const labelList& dups = duplicates_[localI];
forAll(dups, i)
{
meshTools::writeOBJ(str, meshMod.points()[dups[i]]);
}
}
}
}
} }

View File

@ -103,10 +103,10 @@ public:
// Topology changes // Topology changes
//- Play commands into polyTopoChange to duplicate points. Gets //- Play commands into polyTopoChange to duplicate points. Gets
// structure with regions per point. // localPointRegion structure which is per non-manifold point
// the regions per point.
void setRefinement void setRefinement
( (
const labelList& nonManifPoints,
const localPointRegion& regionSide, const localPointRegion& regionSide,
polyTopoChange& polyTopoChange&
); );

View File

@ -47,6 +47,8 @@ SourceFiles
#include "Map.H" #include "Map.H"
#include "labelList.H" #include "labelList.H"
#include "labelHashSet.H" #include "labelHashSet.H"
#include "faceList.H"
#include "boolList.H"
// * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * //
@ -67,64 +69,40 @@ class localPointRegion
{ {
// Private data // Private data
//- (global) number of regions ////- (global) number of regions
label nRegions_; //label nRegions_;
//- From mesh face to local face //- Per point that is to duplicated to the local index
Map<label> localFaces_; Map<label> meshPointMap_;
//- From local face addressing to mesh faces. //- Per local point the regions it is in
labelList meshFaces_;
//- Per local face the region
labelList faceRegion_;
//- Per local point (i.e. index in pointsToBeDuplicated) the list
// of regions
labelListList pointRegions_; labelListList pointRegions_;
//- Per face that uses a duplicated point the local index
Map<label> meshFaceMap_;
//- Per face the region of its points
faceList faceRegions_;
// Private Member Functions // Private Member Functions
//- Set face (and its connected points) to global region nRegions_. //- Given minimum cell the points on a face are connected to
void setFaceRegion // determine the points to be duplicated.
void countPointRegions
( (
const polyMesh&, const polyMesh& mesh,
const Map<label>&, const Map<label>& candidateFace,
const label faceI, faceList& minRegion
labelListList& pointFaceRegion // in pointFaces addressing.
); );
//- Set point (and its connected faces) to global region nRegions_ //- Do all: calculate points that need to be duplicated.
void setPointRegion void calcPointRegions
( (
const polyMesh&, const polyMesh& mesh,
const Map<label>&, boolList& candidatePoint
const label pointI,
const label localPointI,
const label faceI,
labelListList& pointFaceRegion
); );
//- Gets local face regions (local to each point) and merges them into
// global face region (always possible since faces cannot cross
// a baffle.
void mergeLocalRegions
(
const polyMesh&,
const labelList& pointsToBeDuplicated,
labelListList& pointFaceRegion
);
//- Change region
void changeRegion
(
const polyMesh&,
const label faceI,
const label oldRegionI,
const label newRegionI,
labelHashSet& changedFaces
);
//- Check if two faces are equal. If forward = false checks f1 in //- Check if two faces are equal. If forward = false checks f1 in
// reverse order. // reverse order.
@ -143,11 +121,15 @@ public:
// Constructors // Constructors
//- Construct from mesh and points that should be separated. //- Construct from mesh. Assumes all non-coupled boundary points
// are candidates for duplication
localPointRegion(const polyMesh& mesh);
//- Construct from mesh and candidate points for duplication
localPointRegion localPointRegion
( (
const polyMesh& mesh, const polyMesh& mesh,
const labelList& pointsToBeDuplicated const labelList& candidatePoints
); );
@ -167,60 +149,40 @@ public:
const labelList& const labelList&
); );
//- Are all the cells using pointI reachable through (processor
// local) cell-face-cell walk.
static bool isSingleCellRegion
(
const primitiveMesh& mesh,
const label pointI
);
//- See walkPointConnectedFaces. Temporarily exposed so as to
// use outside since par problem with localPointRegion (which is
// probably fixed now with plusEqOp v.s. sumOp in gather/scatter)
static label walkCellFaceCell
(
const primitiveMesh&,
const label startCellI,
const label startPointI,
const label regionI,
labelList& regionPerFace
);
// Access // Access
//- Global number of regions ////- Global number of regions. TBD. regions not compacted yet.
label nRegions() const //label nRegions() const
//{
// return nRegions_;
//}
//- Per point that is to duplicated to the local index
const Map<label>& meshPointMap() const
{ {
return nRegions_; return meshPointMap_;
} }
//- From local face addressing to mesh faces. //- Per local point the regions it is in
const labelList& meshFaces() const
{
return meshFaces_;
}
//- From mesh face to local face
const Map<label>& localFaces() const
{
return localFaces_;
}
//- Per local face the global region. Regions are not consecutive
// per processor. They will be -1..nRegions_.
const labelList& faceRegion() const
{
return faceRegion_;
}
//- Per pointsToBeDuplicated the list of regions
const labelListList& pointRegions() const const labelListList& pointRegions() const
{ {
return pointRegions_; return pointRegions_;
} }
//- Per face that uses a duplicated point the local index
const Map<label>& meshFaceMap() const
{
return meshFaceMap_;
}
//- Per face the region of its points
const faceList& faceRegions() const
{
return faceRegions_;
}
// Edit // Edit
//- Force recalculation of locally stored data on topological change //- Force recalculation of locally stored data on topological change