VTK  9.7.20261114
PResolveHangingNodesTestUtilities.h
Go to the documentation of this file.
1// SPDX-FileCopyrightText: Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
2// SPDX-License-Identifier: BSD-3-Clause
3// Helpers of the tests of vtkPResolveHangingNodes: the partitions of the 2D and 3D meshes, the
4// blocks of a vtkNonOverlappingAMR, the check of the output gathered from all the processes, and a
5// mock filter.
6#ifndef PResolveHangingNodesTestUtilities_h
7#define PResolveHangingNodesTestUtilities_h
8
9#include "vtkAppendFilter.h"
10#include "vtkCellArray.h"
11#include "vtkCellData.h"
12#include "vtkCellType.h"
13#include "vtkCellValidator.h"
14#include "vtkCompositeDataSet.h"
15#include "vtkDataSet.h"
16#include "vtkDoubleArray.h"
17#include "vtkFeatureEdges.h"
18#include "vtkGeometryFilter.h"
19#include "vtkIdList.h"
20#include "vtkImageData.h"
21#include "vtkIntArray.h"
22#include "vtkMath.h"
24#include "vtkNew.h"
25#include "vtkObjectFactory.h"
27#include "vtkPointData.h"
28#include "vtkPoints.h"
29#include "vtkPolyData.h"
30#include "vtkPolygon.h"
31#include "vtkSmartPointer.h"
32#include "vtkUnstructuredGrid.h"
33
34#include <array>
35#include <cmath>
36#include <iostream>
37#include <map>
38#include <string>
39#include <vector>
40
41// The functions are inline, so that a test that does not use one gets no warning.
42namespace
43{
44constexpr double EPSILON = 1e-6;
45
46// A block of level `level` of a vtkNonOverlappingAMR: a vtkImageData of `numCells` cells (0 along z
47// in 2D) of size `spacing`, from `origin`. Point data "Values" holds x + 10 y + 100 z, and "Levels"
48// the level.
49inline vtkSmartPointer<vtkImageData> CreateBlock(int level, const std::array<double, 3>& origin,
50 const std::array<int, 3>& numCells, double spacing)
51{
53 block->SetOrigin(origin.data());
54 block->SetSpacing(spacing, spacing, spacing);
55 block->SetDimensions(numCells[0] + 1, numCells[1] + 1, numCells[2] + 1);
57 values->SetName("Values");
59 levels->SetName("Levels");
60 for (vtkIdType i = 0; i < block->GetNumberOfPoints(); ++i)
61 {
62 const double* p = block->GetPoint(i);
63 values->InsertNextValue(p[0] + 10.0 * p[1] + 100.0 * p[2]);
64 levels->InsertNextValue(level);
65 }
66 block->GetPointData()->AddArray(values);
67 block->GetPointData()->AddArray(levels);
68 return block;
69}
70
71// 2D 3-level mesh over the domain [0,8] x [0,4], with a quad in each of the 8 partitions, the
72// partitions 2 rank and 2 rank + 1 being on the process rank:
73// - Partition 0: level 0 quad [0,4] x [0,4] (area 16). Its edge x = 4 has the hanging point (4,2)
74// of the partitions 1, on the same process, and 3.
75// - Partitions 1, 2 and 3: level 1 quads [4,6] x [0,2], [6,8] x [0,2] and [4,6] x [2,4] (area 4).
76// The edge y = 2 of partition 2 has the hanging point (7,2) of the partitions 4 and 5, and the
77// edge x = 6 of partition 3 has the hanging point (6,3) of the partitions 4 and 6.
78// - Partitions 4 to 7: level 2 quads [6,8] x [2,4] subdivided 2 x 2 (area 1), by rows.
79// Each quad [x0,x1] x [y0,y1] is { x0, y0, x1, y1 }.
80const std::array<std::array<double, 4>, 8> QUADS = { {
81 { 0, 0, 4, 4 },
82 { 4, 0, 6, 2 },
83 { 6, 0, 8, 2 },
84 { 4, 2, 6, 4 },
85 { 6, 2, 7, 3 },
86 { 7, 2, 8, 3 },
87 { 6, 3, 7, 4 },
88 { 7, 3, 8, 4 },
89} };
90
91// The quad of a partition. Point data "Values" holds x + 10 y + 100 partition, to check where a
92// point comes from: of the copies of a point received from several partitions, the one of the
93// first partition is used.
94inline vtkSmartPointer<vtkPolyData> CreateQuadPartition(int partition)
95{
97 points->SetDataType(VTK_DOUBLE);
99 values->SetName("Values");
100 auto add = [&](double x, double y)
101 {
102 values->InsertNextValue(x + 10.0 * y + 100.0 * partition);
103 return points->InsertNextPoint(x, y, 0.0);
104 };
105 const auto& [x0, y0, x1, y1] = QUADS[partition];
107 const vtkIdType quad[4] = { add(x0, y0), add(x1, y0), add(x1, y1), add(x0, y1) };
108 polys->InsertNextCell(4, quad);
109
111 pd->SetPoints(points);
112 pd->SetPolys(polys);
113 pd->GetPointData()->AddArray(values);
114 return pd;
115}
116
117// 3D 2-level mesh over the domain [0,4] x [0,4] x [0,2], in 8 partitions, the partitions 2 rank and
118// 2 rank + 1 being on the process rank:
119// - Partition 0 (A): level 0 hexahedron [0,2] x [0,2] x [0,2]. Its face x = 2 borders D.
120// - Partition 2 (B): level 0 hexahedron [0,2] x [2,4] x [0,2]. It touches D only along the edge
121// x = 2, y = 2, so it only gets the hanging point (2,2,1) from the partitions 4 and 6.
122// - Partition 3 (C): level 0 hexahedron [2,4] x [2,4] x [0,2], on the process of B. Its face
123// y = 2 borders D.
124// - The other partitions (D): level 1 hexahedra [2,4] x [0,2] x [0,2] subdivided 2 x 2 x 2. The
125// partitions 1, 4 and 5 have the rows of 2 hexahedra [2,4] x [j,j+1] x [k,k+1] for (j, k) =
126// (0, 0), (1, 0) and (0, 1), and the partitions 6 and 7 the hexahedra [2,3] x [1,2] x [1,2] and
127// [3,4] x [1,2] x [1,2].
128// Point data "Values" holds x + 10 y + 100 z + 1000 partition, to check where a point comes from:
129// of the copies of a point received from several partitions, the one of the first partition is
130// used.
131inline vtkSmartPointer<vtkUnstructuredGrid> CreateHexPartition(int partition)
132{
134 points->SetDataType(VTK_DOUBLE);
136 values->SetName("Values");
137 std::map<std::array<int, 3>, vtkIdType> pointIds;
138 auto point = [&](int x, int y, int z)
139 {
140 auto it = pointIds.find({ x, y, z });
141 if (it != pointIds.end())
142 {
143 return it->second;
144 }
145 values->InsertNextValue(x + 10.0 * y + 100.0 * z + 1000.0 * partition);
146 const vtkIdType id = points->InsertNextPoint(x, y, z);
147 pointIds[{ x, y, z }] = id;
148 return id;
149 };
150
152 ug->AllocateExact(8, 64);
153 auto hex = [&](int x, int y, int z, int size)
154 {
155 const vtkIdType ids[8] = { point(x, y, z), point(x + size, y, z), point(x + size, y + size, z),
156 point(x, y + size, z), point(x, y, z + size), point(x + size, y, z + size),
157 point(x + size, y + size, z + size), point(x, y + size, z + size) };
158 ug->InsertNextCell(VTK_HEXAHEDRON, 8, ids);
159 };
160
161 if (partition == 0)
162 {
163 hex(0, 0, 0, 2);
164 }
165 else if (partition == 2)
166 {
167 hex(0, 2, 0, 2);
168 }
169 else if (partition == 3)
170 {
171 hex(2, 2, 0, 2);
172 }
173 else if (partition >= 6)
174 {
175 hex(partition - 4, 1, 1, 1);
176 }
177 else
178 {
179 const int j = partition == 4 ? 1 : 0;
180 const int k = partition == 5 ? 1 : 0;
181 for (int i = 0; i < 2; ++i)
182 {
183 hex(2 + i, j, k, 1);
184 }
185 }
186 ug->SetPoints(points);
187 ug->GetPointData()->AddArray(values);
188 return ug;
189}
190
191inline vtkIdType FindPoint(vtkDataSet* ds, const std::array<double, 3>& p)
192{
193 for (vtkIdType i = 0; i < ds->GetNumberOfPoints(); ++i)
194 {
195 if (vtkMath::Distance2BetweenPoints(p.data(), ds->GetPoint(i)) < EPSILON)
196 {
197 return i;
198 }
199 }
200 return -1;
201}
202
203// Total length (2D) or area (3D) of the edges or faces used by a single cell. It is the boundary
204// measure of the domain if and only if the mesh is conforming: a remaining hanging node leaves both
205// sides of an interface unmatched.
206template <int Dim>
207inline double BoundaryMeasure(vtkDataSet* ds)
208{
210 geometry->SetInputData(ds);
211 geometry->Update();
212 vtkSmartPointer<vtkPolyData> boundary = geometry->GetOutput();
213 if constexpr (Dim == 2)
214 {
216 edges->SetInputData(boundary);
217 edges->BoundaryEdgesOn();
218 edges->FeatureEdgesOff();
219 edges->NonManifoldEdgesOff();
220 edges->ManifoldEdgesOff();
221 edges->Update();
222 boundary = edges->GetOutput();
223 }
224 double measure = 0.0;
226 for (vtkIdType c = 0; c < boundary->GetNumberOfCells(); ++c)
227 {
228 boundary->GetCellPoints(c, ids);
229 if constexpr (Dim == 2)
230 {
231 double p0[3], p1[3];
232 boundary->GetPoint(ids->GetId(0), p0);
233 boundary->GetPoint(ids->GetId(1), p1);
234 measure += std::sqrt(vtkMath::Distance2BetweenPoints(p0, p1));
235 }
236 else
237 {
238 double normal[3];
239 measure += vtkPolygon::ComputeArea(
240 boundary->GetPoints(), ids->GetNumberOfIds(), ids->GetPointer(0), normal);
241 }
242 }
243 return measure;
244}
245
246// Gather the partitions of `output` on process 0, merge the points they share, and check that the
247// whole mesh is conforming, valid in 3D, and covers the domain, of area (2D) or volume (3D)
248// `expectedMeasure` and of boundary length (2D) or area (3D) `expectedBoundaryMeasure`.
249template <int Dim>
250inline bool CheckGlobalOutput(const std::string& name, vtkMultiProcessController* controller,
251 vtkDataObject* output, double expectedMeasure, double expectedBoundaryMeasure)
252{
253 // The partitions of this process, in one dataset to gather, empty without partitions (since
254 // vtkAppendFilter needs an input)
256 const auto partitions = vtkCompositeDataSet::GetDataSets(output);
257 if (!partitions.empty())
258 {
259 vtkNew<vtkAppendFilter> localAppend;
260 for (vtkDataSet* partition : partitions)
261 {
262 localAppend->AddInputData(partition);
263 }
264 localAppend->Update();
265 local = localAppend->GetOutput();
266 }
267 std::vector<vtkSmartPointer<vtkDataObject>> outputs;
268 controller->Gather(local, outputs, 0);
269 if (controller->GetLocalProcessId() != 0)
270 {
271 return true;
272 }
274 append->MergePointsOn();
275 for (const auto& partition : outputs)
276 {
277 append->AddInputData(partition);
278 }
279 append->Update();
280 vtkUnstructuredGrid* mesh = append->GetOutput();
281
282 const double boundaryMeasure = BoundaryMeasure<Dim>(mesh);
283 if (std::abs(boundaryMeasure - expectedBoundaryMeasure) > EPSILON)
284 {
285 std::cerr << name << ": the boundary " << (Dim == 2 ? "length" : "area") << " is "
286 << boundaryMeasure << " instead of " << expectedBoundaryMeasure
287 << ", the output is not conforming\n";
288 return false;
289 }
290
291 if (Dim == 3)
292 {
293 vtkNew<vtkCellValidator> validator;
294 validator->SetInputData(mesh);
295 validator->Update();
296 vtkDataArray* states =
297 vtkDataSet::SafeDownCast(validator->GetOutput())->GetCellData()->GetArray("ValidityState");
298 for (vtkIdType c = 0; c < mesh->GetNumberOfCells(); ++c)
299 {
300 if (states->GetTuple1(c) != static_cast<short>(vtkCellValidator::State::Valid))
301 {
302 std::cerr << name << ": cell " << c << " of type " << mesh->GetCellType(c)
303 << " is not valid\n";
304 return false;
305 }
306 }
307 }
308
309 vtkDataArray* inverseMeasure =
310 mesh->GetCellData()->GetArray(Dim == 2 ? "InverseArea" : "InverseVolume");
311 double measure = 0.0;
312 for (vtkIdType c = 0; inverseMeasure && c < mesh->GetNumberOfCells(); ++c)
313 {
314 measure += 1.0 / inverseMeasure->GetTuple1(c);
315 }
316 if (std::abs(measure - expectedMeasure) > EPSILON)
317 {
318 std::cerr << name << ": the cells cover " << (Dim == 2 ? "an area" : "a volume") << " of "
319 << measure << " instead of " << expectedMeasure << "\n";
320 return false;
321 }
322 return true;
323}
324
325// Mock subclass recording the partitionedDataSetIds of the local blocks of a vtkNonOverlappingAMR,
326// their levels, and checking that a block only receives the points of the blocks of the other
327// levels, here or on other processes, whose point data "Levels" holds their level.
328class vtkPLevelsTestFilter : public vtkPResolveHangingNodes
329{
330public:
331 static vtkPLevelsTestFilter* New() { VTK_STANDARD_NEW_BODY(vtkPLevelsTestFilter); }
332 vtkTypeMacro(vtkPLevelsTestFilter, vtkPResolveHangingNodes);
333
334 std::vector<int> PartitionedDataSetIds;
335 bool ReceivedOnlyOtherLevels = true;
336
337protected:
338 std::vector<vtkSmartPointer<vtkPolyData>> GetRemotePoints(
339 const std::vector<vtkPolyData*>& boundaries,
340 const std::vector<int>& partitionedDataSetIds) override
341 {
342 this->PartitionedDataSetIds = partitionedDataSetIds;
343 std::vector<vtkSmartPointer<vtkPolyData>> remotePoints =
344 this->Superclass::GetRemotePoints(boundaries, partitionedDataSetIds);
345 for (std::size_t p = 0; p < remotePoints.size(); ++p)
346 {
347 vtkDataArray* levels =
348 remotePoints[p] ? remotePoints[p]->GetPointData()->GetArray("Levels") : nullptr;
349 for (vtkIdType i = 0; levels && i < levels->GetNumberOfTuples(); ++i)
350 {
351 if (levels->GetTuple1(i) == partitionedDataSetIds[p])
352 {
353 this->ReceivedOnlyOtherLevels = false;
354 }
355 }
356 }
357 return remotePoints;
358 }
359};
360} // namespace
361
362#endif
vtkIdType GetNumberOfTuples() const
Get the number of complete tuples (a component group) in the array.
static std::vector< DataSetT * > GetDataSets(vtkDataObject *dobj, bool preserveNull=false)
Extract datasets from the given data object.
double GetTuple1(vtkIdType tupleIdx)
These methods are included as convenience for the wrappers.
general representation of visualization data
abstract class to specify dataset behavior
Definition vtkDataSet.h:165
static vtkDataSet * SafeDownCast(vtkObjectBase *o)
virtual vtkIdType GetNumberOfPoints()=0
Determine the number of points composing the dataset.
virtual double * GetPoint(vtkIdType ptId)=0
Get point coordinates with ptId such that: 0 <= ptId < NumberOfPoints.
vtkCellData * GetCellData()
Return a pointer to this dataset's cell data.
Definition vtkDataSet.h:437
vtkDataArray * GetArray(int i)
Not recommended for use.
static ReturnTypeT Distance2BetweenPoints(const TupleRangeT1 &p1, const TupleRangeT2 &p2)
Compute distance squared between two points p1 and p2.
Definition vtkMath.h:2096
Multiprocessing communication superclass.
int Gather(const int *sendBuffer, int *recvBuffer, vtkIdType length, int destProcessId)
Gather collects arrays in the process with id destProcessId.
int GetLocalProcessId()
Tells you which process [0, NumProcess) you are in.
Allocate and hold a VTK object.
Definition vtkNew.h:168
T * GetPointer() const noexcept
Get a raw pointer to the contained object.
Definition vtkNew.h:257
resolve the hanging nodes of a 2D or 3D multi-resolution mesh distributed over several processes
std::vector< vtkSmartPointer< vtkPolyData > > GetRemotePoints(const std::vector< vtkPolyData * > &boundaries, const std::vector< int > &partitionedDataSetIds) override
Exchange the points of the boundary of the partitions with their neighbors, on this process or on oth...
double ComputeArea()
Compute the area of a polygon.
Hold a reference to a vtkObjectBase instance.
static vtkSmartPointer< T > New()
Create an instance of a VTK object.
dataset represents arbitrary combinations of all possible cell types
vtkIdType GetNumberOfCells() override
Standard vtkDataSet methods; see vtkDataSet.h for documentation.
int GetCellType(vtkIdType cellId) override
Get the type of the cell with the given cellId.
const std::string pointIds
@ point
Definition vtkX3D.h:237
@ points
Definition vtkX3D.h:447
@ name
Definition vtkX3D.h:220
@ size
Definition vtkX3D.h:254
std::pair< boost::graph_traits< vtkGraph * >::edge_iterator, boost::graph_traits< vtkGraph * >::edge_iterator > edges(vtkGraph *g)
@ VTK_HEXAHEDRON
Definition vtkCellType.h:49
#define vtkDataArray
#define VTK_STANDARD_NEW_BODY(thisClass)
int vtkIdType
Definition vtkType.h:366
#define VTK_DOUBLE
Definition vtkType.h:44