1 // Copyright (c) 2017-2021, Lawrence Livermore National Security, LLC and
2 // other Axom Project Developers. See the top-level LICENSE file for details.
3 //
4 // SPDX-License-Identifier: (BSD-3-Clause)
5 
6 /**
7  * \file InOutOctreeValidator.hpp
8  *
9  * \brief Defines helper class to validate an InOutOctree instance
10  */
11 
12 #ifndef AXOM_QUEST_INOUT_OCTREE_VALIDATOR__HPP_
13 #define AXOM_QUEST_INOUT_OCTREE_VALIDATOR__HPP_
14 
15 #include "axom/core.hpp"
16 #include "axom/slic.hpp"
17 #include "axom/slam.hpp"
18 #include "axom/primal.hpp"
19 
20 #include "BlockData.hpp"
21 #include "MeshWrapper.hpp"
22 
23 #include "axom/fmt.hpp"
24 
25 namespace axom
26 {
27 namespace quest
28 {
29 // Predeclare InOutOctree class
30 template <int DIM>
31 class InOutOctree;
32 
33 namespace detail
34 {
35 template <int DIM>
36 class InOutOctreeValidator
37 {
38 public:
39   using InOutOctreeType = InOutOctree<DIM>;
40   using CellIndexSet = typename InOutOctreeType::CellIndexSet;
41 
42   using OctreeBaseType = typename InOutOctreeType::OctreeBaseType;
43   using OctreeLevels = typename OctreeBaseType::OctreeLevels;
44   using BlockIndex = typename OctreeBaseType::BlockIndex;
45 
46   using SpacePt = typename InOutOctreeType::SpacePt;
47   using VertexIndex = typename InOutOctreeType::VertexIndex;
48   using CellIndex = typename InOutOctreeType::CellIndex;
49   using CellVertIndices = typename MeshWrapper<DIM>::CellVertIndices;
50   using GeometricBoundingBox = typename InOutOctreeType::GeometricBoundingBox;
51 
52 public:
InOutOctreeValidator(const InOutOctreeType & octree)53   InOutOctreeValidator(const InOutOctreeType& octree)
54     : m_octree(octree)
55     , m_generationState(m_octree.m_generationState)
56   { }
57 
58   /// This function checks that all leaf blocks in the tree were assigned a color
checkAllLeavesColored() const59   void checkAllLeavesColored() const
60   {
61     SLIC_DEBUG(
62       "--Checking that all leaves have a color -- black, white and gray");
63 
64     for(int lev = 0; lev < m_octree.m_levels.size(); ++lev)
65     {
66       checkAllLeavesColoredAtLevel(lev);
67     }
68   }
69 
70   /// This function checks that each leaf block at level \a level has a color
checkAllLeavesColoredAtLevel(int level) const71   void checkAllLeavesColoredAtLevel(int level) const
72   {
73     const auto& levelLeafMap = m_octree.getOctreeLevel(level);
74     auto itEnd = levelLeafMap.end();
75     for(auto it = levelLeafMap.begin(); it != itEnd; ++it)
76     {
77       if(!it->isLeaf()) continue;
78 
79       SLIC_ASSERT_MSG(
80         it->isColored(),
81         fmt::format(
82           "Error after coloring level {} leaf block {} was not colored.",
83           level,
84           BlockIndex(it.pt(), level)));
85     }
86   }
87 
88   /// This function checks that each vertex in the mesh is indexed by a leaf block of the octree
checkEachVertexIsIndexed() const89   void checkEachVertexIsIndexed() const
90   {
91     SLIC_DEBUG("--Checking that each vertex is in a leaf block of the tree.");
92 
93     const VertexIndex numVertices = m_octree.m_meshWrapper.numMeshVertices();
94     for(VertexIndex i = 0; i < numVertices; ++i)
95     {
96       const SpacePt& pos = m_octree.m_meshWrapper.vertexPosition(i);
97       BlockIndex vertBlock = m_octree.findLeafBlock(pos);
98       const InOutBlockData& leafData = m_octree[vertBlock];
99 
100       VertexIndex vertInBlock = m_octree.leafVertex(vertBlock, leafData);
101       AXOM_UNUSED_VAR(vertInBlock);
102 
103       // Check that we can find the leaf block indexing each vertex from its position
104       SLIC_ASSERT_MSG(
105         leafData.hasData() && vertInBlock == i,
106         fmt::format(
107           " Vertex {} at position {} was not indexed in block {} with bounding "
108           "box {} (point {} contained in block)."
109           " \n\n *** \n Leaf data: {} \n ***",
110           i,
111           pos,
112           vertBlock,
113           m_octree.blockBoundingBox(vertBlock),
114           (m_octree.blockBoundingBox(vertBlock).contains(pos) ? "is" : "is NOT"),
115           leafData));
116 
117       // Check that our cached value of the vertex's block is accurate
118       SLIC_ASSERT_MSG(
119         vertBlock == m_octree.m_vertexToBlockMap[i],
120         fmt::format(
121           "Cached block for vertex {} differs from its found block. "
122           "\n\t -- cached block {} -- is leaf? {}"
123           "\n\t -- actual block {} -- is leaf? {}"
124           "\n\t -- vertex set size: {}",
125           i,
126           m_octree.m_vertexToBlockMap[i],
127           (m_octree[m_octree.m_vertexToBlockMap[i]].isLeaf() ? "yes" : "no"),
128           vertBlock,
129           (m_octree[vertBlock].isLeaf() ? "yes" : "no"),
130           numVertices));
131     }
132   }
133 
134   // Check that for each a cell's vertices, that cell is indexed by the block containing that vertex
checkCellsReferencedInBoundaryVertexBlocks() const135   void checkCellsReferencedInBoundaryVertexBlocks() const
136   {
137     SLIC_DEBUG(
138       "--Checking that each cell is referenced by the leaf blocks "
139       "containing its vertices.");
140 
141     const axom::IndexType numCells = m_octree.m_meshWrapper.numMeshCells();
142     for(axom::IndexType cIdx = 0; cIdx < numCells; ++cIdx)
143     {
144       CellVertIndices cvRel = m_octree.m_meshWrapper.cellVertexIndices(cIdx);
145       for(int j = 0; j < cvRel.size(); ++j)
146       {
147         VertexIndex vIdx = cvRel[j];
148         BlockIndex vertBlock = m_octree.m_vertexToBlockMap[vIdx];
149         const InOutBlockData& leafData = m_octree[vertBlock];
150 
151         // Check that this cell is referenced in this block
152         bool foundCell = false;
153         CellIndexSet leafCells = m_octree.leafCells(vertBlock, leafData);
154         for(int k = 0; !foundCell && k < leafCells.size(); ++k)
155         {
156           if(leafCells[k] == cIdx) foundCell = true;
157         }
158 
159         SLIC_ASSERT_MSG(
160           foundCell,
161           fmt::format("Did not find cell with index {} and vertices [{}] "
162                       "in block {} containing vertex {}"
163                       " \n\n *** "
164                       "\n Leaf data: {} "
165                       "\n\t Cells in block? [{}] "
166                       "\n ***",
167                       cIdx,
168                       fmt::join(cvRel, ", "),
169                       vertBlock,
170                       vIdx,
171                       leafData,
172                       fmt::join(leafCells.begin(), leafCells.end(), ", ")));
173       }
174     }
175   }
176 
177   /// Checks some indexing contstaints on internal and leaf blocks
checkBlockIndexingConsistency() const178   void checkBlockIndexingConsistency() const
179   {
180     SLIC_DEBUG(
181       "--Checking that internal blocks have no data, and that leaves satisfy "
182       "all PM conditions");
183 
184     const double bb_scale_factor = m_octree.m_boundingBoxScaleFactor;
185 
186     for(int lev = 0; lev < m_octree.m_levels.size(); ++lev)
187     {
188       const auto& levelLeafMap = m_octree.getOctreeLevel(lev);
189       auto itEnd = levelLeafMap.end();
190       for(auto it = levelLeafMap.begin(); it != itEnd; ++it)
191       {
192         const BlockIndex block(it.pt(), lev);
193         const InOutBlockData& data = *it;
194 
195         if(!data.isLeaf())
196         {
197           SLIC_ASSERT(!data.hasData());  // internal blocks should not have data
198         }
199         else  // leaf block
200         {
201           if(data.hasData())
202           {
203             VertexIndex vIdx = m_octree.leafVertex(block, data);
204             CellIndexSet blockCells = m_octree.leafCells(block, data);
205             for(int i = 0; i < blockCells.size(); ++i)
206             {
207               CellIndex cIdx = blockCells[i];
208 
209               // Check that vIdx is one of this cell's vertices
210               CellVertIndices cvRel =
211                 m_octree.m_meshWrapper.cellVertexIndices(cIdx);
212 
213               SLIC_ASSERT_MSG(
214                 m_octree.m_meshWrapper.incidentInVertex(cvRel, vIdx),
215                 fmt::format("All cells in a gray block must be incident "
216                             " in a common vertex, but cell {} with "
217                             "vertices [{}] in block {}"
218                             " is not incident in vertex {}",
219                             cIdx,
220                             fmt::join(cvRel, ", "),
221                             block,
222                             vIdx));
223 
224               // Check that this cell intersects the bounding box of the block
225               GeometricBoundingBox blockBB = m_octree.blockBoundingBox(block);
226               blockBB.expand(bb_scale_factor);
227               SLIC_ASSERT_MSG(
228                 m_octree.blockIndexesElementVertex(cIdx, block) ||
229                   intersect(m_octree.m_meshWrapper.cellPositions(cIdx), blockBB),
230                 fmt::format("Cell {} was indexed in block {}"
231                             " but it does not intersect the block."
232                             "\n\tBlock bounding box: {}"
233                             "\n\tCell positions: {}"
234                             "\n\tCell vertex indices: [{}]"
235                             "\n\tLeaf vertex is: {}"
236                             "\n\tLeaf cells: {} ({})",
237                             cIdx,
238                             block,
239                             blockBB,
240                             m_octree.m_meshWrapper.cellPositions(cIdx),
241                             fmt::join(cvRel, ", "),
242                             vIdx,
243                             fmt::join(blockCells, ", "),
244                             blockCells.size()));
245             }
246           }
247         }
248       }
249     }
250   }
251 
252   /// Checks conditions about block colors
checkNeighboringBlockColors() const253   void checkNeighboringBlockColors() const
254   {
255     SLIC_DEBUG("--Checking that inside blocks do not neighbor outside blocks");
256     for(int lev = m_octree.maxLeafLevel() - 1; lev >= 0; --lev)
257     {
258       const auto& levelLeafMap = m_octree.getOctreeLevel(lev);
259       auto itEnd = levelLeafMap.end();
260       for(auto it = levelLeafMap.begin(); it != itEnd; ++it)
261       {
262         const BlockIndex block(it.pt(), lev);
263         const InOutBlockData& data = *it;
264 
265         if(data.isLeaf() && data.color() != InOutBlockData::Gray)
266         {
267           // When leaf is inside or outside -- face neighbors need same label
268           for(int i = 0; i < block.numFaceNeighbors(); ++i)
269           {
270             BlockIndex neighborBlk =
271               m_octree.coveringLeafBlock(block.faceNeighbor(i));
272             if(neighborBlk != BlockIndex::invalid_index())
273             {
274               const InOutBlockData& neighborData = m_octree[neighborBlk];
275               switch(neighborData.color())
276               {
277               case InOutBlockData::Black:  // intentional fallthrough
278               case InOutBlockData::White:
279                 SLIC_CHECK_MSG(data.color() == neighborData.color(),
280                                fmt::format("Problem at block {}  with data {} "
281                                            "--- neighbor {} with data {}."
282                                            " Neighboring leaves that are not "
283                                            "gray must have the same color.",
284                                            block,
285                                            data,
286                                            neighborBlk,
287                                            neighborData));
288                 break;
289               case InOutBlockData::Gray:  // intentional fallthrough
290               case InOutBlockData::Undetermined:
291                 break;
292               }
293             }
294           }
295         }
296       }
297     }
298   }
299 
checkValid() const300   void checkValid() const
301   {
302     // We are assumed to be valid before we insert the vertices
303     if(m_generationState < InOutOctreeType::INOUTOCTREE_VERTICES_INSERTED)
304       return;
305 
306     // Iterate through the tree
307     // Internal blocks should not have associated vertex data
308     // Leaf block consistency depends on 'color'
309     //   Black or White blocks should not have vertex data
310     //   Gray blocks should have a vertex reference; not necessarily located within the block
311     //   Gray blocks should have one or more cells.
312     // All cells should be incident in a common vertex -- the indexed vertex -- if it exists.
313     // The total sum of vertices located within a block should equal the number of mesh vertices.
314 
315     if(m_generationState > InOutOctreeType::INOUTOCTREE_VERTICES_INSERTED)
316     {
317       checkEachVertexIsIndexed();
318     }
319 
320     if(m_generationState >= InOutOctreeType::INOUTOCTREE_ELEMENTS_INSERTED)
321     {
322       checkCellsReferencedInBoundaryVertexBlocks();
323       checkBlockIndexingConsistency();
324     }
325 
326     if(m_generationState >= InOutOctreeType::INOUTOCTREE_LEAVES_COLORED)
327     {
328       checkAllLeavesColored();
329       checkNeighboringBlockColors();
330     }
331   }
332 
333 private:
334   const InOutOctreeType& m_octree;
335   typename InOutOctreeType::GenerationState m_generationState;
336 };
337 
338 }  // namespace detail
339 }  // namespace quest
340 }  // namespace axom
341 
342 #endif  // AXOM_QUEST_INOUT_OCTREE_VALIDATOR__HPP_
343