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 #ifndef MINT_PARTICLEMESH_HPP_
7 #define MINT_PARTICLEMESH_HPP_
8
9 #include "axom/core/Macros.hpp" // for axom macros
10
11 #include "axom/mint/config.hpp" // for mint compile-time definitions
12 #include "axom/mint/mesh/Mesh.hpp" // for mint::Mesh base class
13
14 #include "axom/slic/interface/slic.hpp" // for slic Macros
15
16 namespace axom
17 {
18 // Sidre Forward Declarations
19 namespace sidre
20 {
21 class Group;
22 }
23
24 namespace mint
25 {
26 // Mint Forward Declarations
27 class MeshCoordinates;
28
29 /*!
30 * \class ParticleMesh
31 *
32 * \brief Provides the ability to store and operate on a set of particles.
33 *
34 * The ParticleMesh class derives from the top-level Mesh base class and
35 * provides the ability to store and operate on a collection of particles and
36 * associated data. Each particle has a position and may have associated
37 * scalar, vector and tensor fields.
38 *
39 * A ParticleMesh object may be constructed using (a) native storage, (b)
40 * external storage, or, (c) from a Sidre blueprint conforming hierarchy:
41 *
42 * * <b> Native Storage </b> <br />
43 *
44 * When using native storage, the ParticleMesh object owns all memory
45 * associated with the particle data. The storage can grow dynamically as
46 * needed by the application, i.e., adding more particles. Once the
47 * ParticleMesh object goes out-of-scope, all memory associated with it is
48 * returned to the system.
49 *
50 * * <b> External Storage </b> <br />
51 *
52 * A ParticleMesh may also be constructed from external, user-supplied
53 * buffers. In this case, all memory associated with the particle data
54 * is owned by the caller. Consequently, the number of particles and
55 * associated data cannot grow dynamically.
56 *
57 * * <b> Sidre </b> <br />
58 *
59 * A ParticleMesh may also be constructed from a Sidre hierarchy that is
60 * conforming to the <a href="http://llnl-conduit.readthedocs.io/en/latest/">
61 * mesh blueprint </a> conventions. In this case, all operations are
62 * supported, including dynamically adding new particles and growing the
63 * associated storage. However, Sidre owns all the memory. Once the
64 * ParticleMesh object goes out-of-scope, the data remains persistent in
65 * Sidre.
66 *
67 * \see mint::Mesh
68 */
69 class ParticleMesh : public Mesh
70 {
71 public:
72 /*!
73 * \brief Default constructor. Disabled.
74 */
75 ParticleMesh() = delete;
76
77 /// \name Native Storage Constructors
78 /// @{
79
80 /*!
81 * \brief Constructs a ParticleMesh instance of specified dimension that
82 * holds the specified number of particles.
83 *
84 * \param [in] dimension the ambient dimension of the particle mesh.
85 * \param [in] numParticles the number of particles in this
86 * \param [in] capacity max particle capacity (optional)
87 *
88 * \pre 1 <= dimension <= 3
89 * \pre numParticles >= 0
90 *
91 * \post getNumParticles() == numParticles
92 * \post getNumParticles() <= capacity()
93 * \post hasSidreGroup() == false
94 */
95 ParticleMesh(int dimension,
96 IndexType numParticles,
97 IndexType capacity = USE_DEFAULT);
98
99 /// @}
100
101 /// \name External Storage Constructors
102 /// @{
103
104 /*!
105 * \brief Creates a ParticleMesh instance that points to the supplied external
106 * particle position buffers.
107 *
108 * \param [in] numParticles the number of particles in the supplied buffers.
109 * \param [in] x pointer to the particle x-coordinate positions
110 * \param [in] y pointer to the particle y-coordinate positions (optional)
111 * \param [in] z pointer to the particle z-coordinate positions (optional)
112 *
113 * \note This constructor wraps the supplied particle position buffers.
114 * Consequently, the resulting ParticleMesh object does not own the memory
115 * associated with the particle positions.
116 *
117 * \warning All calls to shrink(), append(), resize() and reserve will fail
118 * on a ParticleMesh instance that is constructed using this constructor.
119 *
120 * \note The supplied buffers must have sufficient storage for numParticles
121 *
122 * \pre x != nullptr
123 * \pre y != nullptr, if dimension==2 || dimension==3
124 * \pre z != nullptr, if dimension==3
125 * \pre numParticles >= 1
126 *
127 * \post 1 <= getDimension() <= 3
128 * \post getNumParticles() == numParticles
129 */
130 ParticleMesh(IndexType numParticles,
131 double* x,
132 double* y = nullptr,
133 double* z = nullptr);
134
135 /// @}
136
137 #ifdef AXOM_MINT_USE_SIDRE
138 /// \name Sidre Storage Constructors
139 /// @{
140
141 /*!
142 * \brief Creates a ParticleMesh instance from a given Sidre group that holds
143 * particle mesh data that according to the conventions of the computational
144 * mesh blueprint.
145 *
146 * \param [in] group pointer to the blueprint root group in Sidre
147 * \param [in] topo the name of the associated topology (optional)
148 *
149 * \note The supplied group is expected to contain particle mesh data
150 * that is valid and conforming to the conventions described in the mesh
151 * <a href="http://llnl-conduit.readthedocs.io/en/latest/"> blueprint </a>.
152 *
153 * \note If a topology name is not provided, the implementation will construct
154 * a mesh based on the 1st topology group under the parent "topologies"
155 * group.
156 *
157 * \note When using this constructor, all data is owned by Sidre. Once the
158 * ParticleMesh object goes out-of-scope, the data remains persistent in
159 * Sidre.
160 *
161 * \pre group != nullptr
162 * \pre blueprint::isValidRootGroup( group )
163 * \post hasSidreGroup() == true
164 */
165 explicit ParticleMesh(sidre::Group* group, const std::string& topo = "");
166
167 /*!
168 * \brief Creates a ParticleMesh object on an empty Sidre group.
169 *
170 * \param [in] dimension the ambient dimension of the particle mesh.
171 * \param [in] numParticles the number of particles this instance holds
172 * \param [in] group pointer to a group in Sidre where
173 * \param [in] topo the name of the associated topology (optional)
174 * \param [in] coordset the name of the coordset group in Sidre (optional)
175 * \param [in] capacity max particle capacity (optional).
176 *
177 * \note If a topology and coordset name is not provided, internal defaults
178 * will be used by the implementation.
179 *
180 * \note When using this constructor, all data is owned by Sidre. Once the
181 * ParticleMesh object goes out-of-scope, the data remains persistent in
182 * Sidre.
183 *
184 * \pre 1 <= dimension <= 3
185 * \pre group != nullptr
186 * \pre group->getNumViews()==0
187 * \pre group->getNumGroups()==0
188 *
189 * \post hasSidreGroup()==true
190 */
191 /// @{
192
193 ParticleMesh(int dimension,
194 IndexType numParticles,
195 sidre::Group* group,
196 const std::string& topo,
197 const std::string& coordset,
198 IndexType capacity = USE_DEFAULT);
199
200 ParticleMesh(int dimension,
201 IndexType numParticles,
202 sidre::Group* group,
203 IndexType capacity = USE_DEFAULT);
204 /// @}
205
206 /// @}
207
208 #endif /* AXOM_MINT_USE_SIDRE */
209
210 /// \name Virtual methods
211 /// @{
212
213 /*!
214 * \brief Destructor.
215 */
216 virtual ~ParticleMesh();
217
218 /// \name Cells
219 /// @{
220
221 /*!
222 * \brief Return the number of cells in the mesh.
223 */
getNumberOfCells() const224 virtual IndexType getNumberOfCells() const final override
225 {
226 return getNumberOfNodes();
227 }
228
229 /*!
230 * \brief Return the capacity for cells.
231 */
getCellCapacity() const232 virtual IndexType getCellCapacity() const final override
233 {
234 return getNodeCapacity();
235 }
236
getNumberOfCellNodes(IndexType AXOM_UNUSED_PARAM (cellID)=0) const237 virtual IndexType getNumberOfCellNodes(
238 IndexType AXOM_UNUSED_PARAM(cellID) = 0) const final override
239 {
240 return 1;
241 }
242
getCellType(IndexType AXOM_UNUSED_PARAM (cellID)=0) const243 virtual CellType getCellType(IndexType AXOM_UNUSED_PARAM(cellID) = 0) const final override
244 {
245 return VERTEX;
246 }
247
248 virtual IndexType getCellNodeIDs(IndexType cellID,
249 IndexType* cell) const final override;
250
251 /*!
252 * \brief Return the number of faces associated with the given cell. For the
253 * ParticleMesh this is always zero.
254 *
255 * \param [in] cellID the ID of the cell in question.
256 */
getNumberOfCellFaces(IndexType AXOM_UNUSED_PARAM (cellID)=0) const257 virtual IndexType getNumberOfCellFaces(
258 IndexType AXOM_UNUSED_PARAM(cellID) = 0) const final override
259 {
260 return 0;
261 }
262
263 /*!
264 * \brief Populates the given buffer with the IDs of the faces of the given
265 * cell and returns the number of faces. Since the ParticleMesh has no faces
266 * this method errors out.
267 *
268 * \param [in] cellID the ID of the cellID in question.
269 * \param [out] faces buffer to populate with the face IDs. Must be of length
270 * at least getNumberOfCellFaces( cellID ).
271 */
getCellFaceIDs(IndexType AXOM_UNUSED_PARAM (cellID),IndexType * AXOM_UNUSED_PARAM (faces)) const272 virtual IndexType getCellFaceIDs(IndexType AXOM_UNUSED_PARAM(cellID),
273 IndexType* AXOM_UNUSED_PARAM(faces)) const final override
274 {
275 SLIC_ERROR("ParticleMesh does not implement this method.");
276 return 0;
277 }
278
279 /// @}
280
281 /// \name Nodes
282 /// @{
283
284 /*!
285 * \brief Return the number of nodes in the mesh.
286 */
getNumberOfNodes() const287 virtual IndexType getNumberOfNodes() const final override
288 {
289 return m_positions->numNodes();
290 }
291
292 /*!
293 * \brief Return the capacity for nodes.
294 */
getNodeCapacity() const295 virtual IndexType getNodeCapacity() const final override
296 {
297 return m_positions->capacity();
298 }
299
300 /*!
301 * \brief Copy the coordinates of the given node into the provided buffer.
302 *
303 * \param [in] nodeID the ID of the node in question.
304 * \param [in] coords the buffer to copy the coordinates into, of length at
305 * least getDimension().
306 *
307 * \pre 0 <= nodeID < getNumberOfNodes()
308 * \pre coords != nullptr
309 */
getNode(IndexType nodeID,double * node) const310 virtual void getNode(IndexType nodeID, double* node) const final override
311 {
312 m_positions->getCoordinates(nodeID, node);
313 }
314
315 /*!
316 * \brief Returns pointer to the particle positions in the specified dimension
317 *
318 * \param[in] dim the specified dimension
319 * \return coord pointer
320 *
321 * \pre 1 <= dim <= 3
322 * \post coord != nullptr
323 */
324 /// @{
325
getCoordinateArray(int dim)326 virtual double* getCoordinateArray(int dim) final override
327 {
328 return m_positions->getCoordinateArray(dim);
329 }
330
getCoordinateArray(int dim) const331 virtual const double* getCoordinateArray(int dim) const final override
332 {
333 return m_positions->getCoordinateArray(dim);
334 }
335
336 /// @}
337
338 /// @}
339
340 /// \name Faces
341 /// @{
342
343 /*!
344 * \brief Return the number of faces in the mesh.
345 */
getNumberOfFaces() const346 virtual IndexType getNumberOfFaces() const final override { return 0; }
347
348 /*!
349 * \brief Return the type of the given face.
350 *
351 * \param [in] faceID the ID of the face in question.
352 *
353 * \note The particle mesh does not have any faces so this call errors out.
354 */
getFaceType(IndexType AXOM_UNUSED_PARAM (faceID)) const355 virtual CellType getFaceType(IndexType AXOM_UNUSED_PARAM(faceID)) const final override
356 {
357 SLIC_ERROR("ParticleMesh does not implement this method.");
358 return UNDEFINED_CELL;
359 }
360
361 /*!
362 * \brief Return the number of nodes associated with the given face.
363 *
364 * \param [in] faceID the ID of the face in question.
365 *
366 * \note The particle mesh does not have any faces so this call errors out.
367 */
getNumberOfFaceNodes(IndexType AXOM_UNUSED_PARAM (faceID)) const368 virtual IndexType getNumberOfFaceNodes(
369 IndexType AXOM_UNUSED_PARAM(faceID)) const final override
370 {
371 SLIC_ERROR("ParticleMesh does not implement this method.");
372 return -1;
373 }
374
375 /*!
376 * \brief Copy the IDs of the nodes that compose the given face into the
377 * provided buffer.
378 *
379 * \param [in] faceID the ID of the face in question.
380 * \param [out] nodes the buffer into which the node IDs are copied, must
381 * be of length at least getNumberOfFaceNodes().
382 *
383 * \return The number of nodes for the given face, which is zero for the
384 * ParticleMesh.
385 *
386 * \note The particle mesh does not have any faces so this call errors out.
387 */
getFaceNodeIDs(IndexType AXOM_UNUSED_PARAM (faceID),IndexType * AXOM_UNUSED_PARAM (nodes)) const388 virtual IndexType getFaceNodeIDs(IndexType AXOM_UNUSED_PARAM(faceID),
389 IndexType* AXOM_UNUSED_PARAM(nodes)) const final override
390 {
391 SLIC_ERROR("ParticleMesh does not implement this method.");
392 return -1;
393 }
394
395 /*!
396 * \brief Copy the IDs of the cells adjacent to the given face into the
397 * provided indices.
398 *
399 * \param [in] faceID the ID of the face in question.
400 * \param [out] cellIDOne the ID of the first cell.
401 * \param [out] cellIDTwo the ID of the second cell.
402 *
403 * \note The particle mesh does not have any faces so this call errors out.
404 */
getFaceCellIDs(IndexType AXOM_UNUSED_PARAM (faceID),IndexType & AXOM_UNUSED_PARAM (cellIDOne),IndexType & AXOM_UNUSED_PARAM (cellIDTwo)) const405 virtual void getFaceCellIDs(IndexType AXOM_UNUSED_PARAM(faceID),
406 IndexType& AXOM_UNUSED_PARAM(cellIDOne),
407 IndexType& AXOM_UNUSED_PARAM(cellIDTwo)) const final override
408 {
409 SLIC_ERROR("ParticleMesh does not implement this method.");
410 }
411
412 /// @}
413
414 /// \name Edges
415 /// @{
416
417 /*!
418 * \brief Return the number of edges in the mesh.
419 */
getNumberOfEdges() const420 virtual IndexType getNumberOfEdges() const final override { return 0; }
421
422 /*!
423 * \brief Return the capacity for edges.
424 */
getEdgeCapacity() const425 virtual IndexType getEdgeCapacity() const final override { return 0; }
426
427 /// @}
428
429 /*!
430 * \brief Return true iff particle positions are stored in external arrays.
431 * \return status true iff the particle positions point to external buffers.
432 */
isExternal() const433 virtual bool isExternal() const final override
434 {
435 return m_positions->isExternal();
436 }
437
438 /// @}
439
440 /// \name Attribute get/set Methods
441 /// @{
442
443 /// \name Nodes
444 /// @{
445
446 /*!
447 * \brief Return the node resize ratio.
448 */
getNodeResizeRatio() const449 double getNodeResizeRatio() const { return m_positions->getResizeRatio(); }
450
451 /*!
452 * \brief Increase the number of particles this ParticleMesh instance can hold
453 * \param [in] newSize the number of particles this instance will now hold
454 * \post getNumParticles() == newSize
455 */
456 void resize(IndexType newSize);
457
458 /*!
459 * \brief Increase the max particle capacity of this ParticleMesh instance
460 * \param [in] newCapacity
461 */
462 void reserve(IndexType newCapacity);
463
464 /*!
465 * \brief Shrinks the max particle capacity to the actual number of particles.
466 *
467 * \post getNumberOfNodes() == capacity()
468 * \post f->getCapacity() == getNumberOfNodes() for all particle fields.
469 */
470 void shrink();
471
472 /// @}
473
474 /*!
475 * \brief Return true iff the mesh holds no particles.
476 */
empty() const477 bool empty() const { return m_positions->empty(); }
478
479 /*!
480 * \brief Return true iff the particle positions are stored in sidre.
481 */
isInSidre() const482 bool isInSidre() const { return m_positions->isInSidre(); }
483
484 /// \name Data Access Methods
485 /// @{
486
487 /*!
488 * \brief Appends a new particle to the ParticleMesh
489 *
490 * \param [in] x the x-coordinate of the particle position
491 * \param [in] y the y-coordinate of the particle position (valid in 2-D)
492 * \param [in] z the z-coordinate of the particle position (valid in 3-D)
493 *
494 * \post increments the number of particles by one.
495 */
496 /// @{
497
498 void append(double x);
499 void append(double x, double y);
500 void append(double x, double y, double z);
501
502 /// @}
503
504 /// @}
505
506 private:
507 /*!
508 * \brief Helper method to initialize a ParticleMesh instance.
509 * \note Called from the constructor.
510 */
511 void initialize();
512
513 /*!
514 * \brief Checks if the internal data array are consistent.
515 * \return status true if the consistency checks pass, else, false.
516 */
517 bool checkConsistency();
518
519 MeshCoordinates* m_positions;
520
521 DISABLE_COPY_AND_ASSIGNMENT(ParticleMesh);
522 DISABLE_MOVE_AND_ASSIGNMENT(ParticleMesh);
523 };
524
525 //------------------------------------------------------------------------------
526 // IN-LINE METHOD IMPLEMENTATION
527 //------------------------------------------------------------------------------
528
529 //------------------------------------------------------------------------------
getCellNodeIDs(IndexType cellID,IndexType * cell) const530 inline IndexType ParticleMesh::getCellNodeIDs(IndexType cellID,
531 IndexType* cell) const
532 {
533 SLIC_ASSERT(cell != nullptr);
534 SLIC_ASSERT(0 <= cellID && cellID <= getNumberOfCells());
535 cell[0] = cellID;
536 return 1;
537 }
538
539 //------------------------------------------------------------------------------
append(double x)540 inline void ParticleMesh::append(double x)
541 {
542 SLIC_ASSERT(m_positions != nullptr);
543 SLIC_ERROR_IF(m_ndims != 1, "ParticleMesh::append(x) is only valid in 1-D");
544
545 m_positions->append(x);
546 m_mesh_fields[NODE_CENTERED]->resize(m_positions->numNodes());
547
548 SLIC_ASSERT(checkConsistency());
549 }
550
551 //------------------------------------------------------------------------------
append(double x,double y)552 inline void ParticleMesh::append(double x, double y)
553 {
554 SLIC_ASSERT(m_positions != nullptr);
555 SLIC_ERROR_IF(m_ndims != 2, "ParticleMesh::append(x,y) is only valid in 2-D");
556
557 m_positions->append(x, y);
558 m_mesh_fields[NODE_CENTERED]->resize(m_positions->numNodes());
559
560 SLIC_ASSERT(checkConsistency());
561 }
562
563 //------------------------------------------------------------------------------
append(double x,double y,double z)564 inline void ParticleMesh::append(double x, double y, double z)
565 {
566 SLIC_ASSERT(m_positions != nullptr);
567 SLIC_ERROR_IF(m_ndims != 3,
568 "ParticleMesh::append(x,y,z) is only valid in 3-D");
569
570 m_positions->append(x, y, z);
571 m_mesh_fields[NODE_CENTERED]->resize(m_positions->numNodes());
572
573 SLIC_ASSERT(checkConsistency());
574 }
575
576 //------------------------------------------------------------------------------
resize(IndexType newSize)577 inline void ParticleMesh::resize(IndexType newSize)
578 {
579 SLIC_ASSERT(m_positions != nullptr);
580 m_positions->resize(newSize);
581 m_mesh_fields[NODE_CENTERED]->resize(newSize);
582 SLIC_ASSERT(checkConsistency());
583 }
584
585 //------------------------------------------------------------------------------
reserve(IndexType newCapacity)586 inline void ParticleMesh::reserve(IndexType newCapacity)
587 {
588 SLIC_ASSERT(m_positions != nullptr);
589 m_positions->reserve(newCapacity);
590 m_mesh_fields[NODE_CENTERED]->reserve(newCapacity);
591 }
592
593 //------------------------------------------------------------------------------
shrink()594 inline void ParticleMesh::shrink()
595 {
596 SLIC_ASSERT(m_positions != nullptr);
597 m_positions->shrink();
598 m_mesh_fields[NODE_CENTERED]->shrink();
599 }
600
601 } /* namespace mint */
602 } /* namespace axom */
603
604 #endif /* MINT_PARTICLEMESH_HPP_ */
605