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