1 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
2 // Copyright (c) Lawrence Livermore National Security, LLC and other Ascent
3 // Project developers. See top-level LICENSE AND COPYRIGHT files for dates and
4 // other details. No copyright assignment is required to contribute to Ascent.
5 //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~//
6 
7 
8 //-----------------------------------------------------------------------------
9 ///
10 /// file: ascent_mfem_data_adapter.cpp
11 ///
12 //-----------------------------------------------------------------------------
13 #include "ascent_mfem_data_adapter.hpp"
14 
15 #include <ascent_logging.hpp>
16 
17 // standard lib includes
18 #include <iostream>
19 #include <string.h>
20 #include <limits.h>
21 #include <cstdlib>
22 #include <sstream>
23 
24 // third party includes
25 #include <conduit_blueprint.hpp>
26 // mpi
27 #ifdef ASCENT_MPI_ENABLED
28 #include <mpi.h>
29 #endif
30 
31 #include <mfem.hpp>
32 
33 using namespace std;
34 using namespace conduit;
35 
36 //-----------------------------------------------------------------------------
37 // -- begin ascent:: --
38 //-----------------------------------------------------------------------------
39 namespace ascent
40 {
41 
MFEMDataSet()42 MFEMDataSet::MFEMDataSet()
43   : m_cycle(0)
44 {
45 
46 }
47 
~MFEMDataSet()48 MFEMDataSet::~MFEMDataSet()
49 {
50   delete m_mesh;
51 
52   for(auto it = m_fields.begin(); it != m_fields.end(); ++it)
53   {
54     delete it->second;
55   }
56 }
57 
MFEMDataSet(mfem::Mesh * mesh)58 MFEMDataSet::MFEMDataSet(mfem::Mesh *mesh)
59  : m_mesh(mesh),
60    m_cycle(0),
61    m_time(0.0)
62 {
63 
64 }
65 
66 int
cycle()67 MFEMDataSet::cycle()
68 {
69   return m_cycle;
70 }
71 
72 double
time()73 MFEMDataSet::time()
74 {
75   return m_time;
76 }
77 
78 void
time(double time)79 MFEMDataSet::time(double time)
80 {
81   m_time = time;
82 }
83 
84 void
cycle(int cycle)85 MFEMDataSet::cycle(int cycle)
86 {
87   m_cycle = cycle;
88 }
89 
90 void
set_mesh(mfem::Mesh * mesh)91 MFEMDataSet::set_mesh(mfem::Mesh *mesh)
92 {
93   m_mesh = mesh;
94 }
95 
96 mfem::Mesh*
get_mesh()97 MFEMDataSet::get_mesh()
98 {
99   return m_mesh;
100 }
101 
102 void
add_field(mfem::GridFunction * field,const std::string & name)103 MFEMDataSet::add_field(mfem::GridFunction *field, const std::string &name)
104 {
105   m_fields[name] = field;
106 }
107 
108 MFEMDataSet::FieldMap
get_field_map()109 MFEMDataSet::get_field_map()
110 {
111   return m_fields;
112 }
113 
114 bool
has_field(const std::string & field_name)115 MFEMDataSet::has_field(const std::string &field_name)
116 {
117   auto it = m_fields.find(field_name);
118   return it != m_fields.end();
119 }
120 
121 mfem::GridFunction*
get_field(const std::string & field_name)122 MFEMDataSet::get_field(const std::string &field_name)
123 {
124   if(!has_field(field_name))
125   {
126     std::string msg = "MFEMDataSet: no field named : " + field_name;
127     ASCENT_ERROR(msg);
128   }
129 
130   return m_fields[field_name];
131 }
132 
133 int
num_fields()134 MFEMDataSet::num_fields()
135 {
136   return m_fields.size();
137 }
138 
139 //-----------------------------------------------------------------------------
140 // MFEMDataAdapter public methods
141 //-----------------------------------------------------------------------------
142 
143 //-----------------------------------------------------------------------------
144 MFEMDomains*
BlueprintToMFEMDataSet(const Node & node,const std::string & topo_name)145 MFEMDataAdapter::BlueprintToMFEMDataSet(const Node &node,
146                                         const std::string &topo_name)
147 {
148 
149     // treat everything as a multi-domain data set
150 
151     MFEMDomains *res = new MFEMDomains;
152 
153 
154     // get the number of domains and check for id consistency
155     const int num_domains = node.number_of_children();
156 
157 
158     for(int i = 0; i < num_domains; ++i)
159     {
160       MFEMDataSet *dset = new MFEMDataSet();
161       const conduit::Node &dom = node.child(i);
162       // this should exist
163       int domain_id = dom["state/domain_id"].to_int();
164       // insert domain conversion
165 
166       bool zero_copy = true;
167       mfem::Mesh *mesh = nullptr;
168       mesh = mfem::ConduitDataCollection::BlueprintMeshToMesh(dom, topo_name, zero_copy);
169       dset->set_mesh(mesh);
170 
171       int cycle = 0;
172       if(dom.has_path("state/cycle"))
173       {
174         cycle = dom["state/cycle"].to_int32();
175       }
176       dset->cycle(cycle);
177 
178       double time = 0;
179       if(dom.has_path("state/time"))
180       {
181         time = dom["state/time"].to_float64();
182       }
183       dset->time(time);
184 
185       std::string t_name = topo_name;
186       // no topology name provied, use the first
187       if(t_name == "")
188       {
189         std::vector<std::string> tnames = dom["topologies"].child_names();
190         if(tnames.size() > 0)
191         {
192           t_name = tnames[0];
193         }
194       }
195 
196       if(t_name == "")
197       {
198         ASCENT_ERROR("Unable to determine topology");
199       }
200 
201       std::string nodes_gf_name = "";
202       const Node &n_topo = dom["topologies/" + t_name];
203       if (n_topo.has_child("grid_function"))
204       {
205         nodes_gf_name = n_topo["grid_function"].as_string();
206       }
207       if(dom.has_path("fields"))
208       {
209         const int num_fields = dom["fields"].number_of_children();
210         std::vector<std::string> fnames = dom["fields"].child_names();
211         for(int f = 0; f < num_fields; ++f)
212         {
213           const conduit::Node &field = dom["fields"].child(f);
214 
215           // skip any field that has a unsupported basis type
216           //      (we only supprt H1 (continuos) and L2 (discon)
217           bool unsupported = false;
218           if(field.has_child("basis"))
219           {
220             std::string basis = field["basis"].as_string();
221             if(basis.find("H1_") == std::string::npos &&
222                basis.find("L2_") == std::string::npos)
223             {
224               unsupported = true;
225             }
226           }
227           // skip mesh nodes gf since they are already processed
228           // skip attribute fields, they aren't grid functions
229           if ( fnames[f] != nodes_gf_name &&
230               fnames[f].find("_attribute") == std::string::npos &&
231               !unsupported)
232           {
233             mfem::GridFunction *gf = mfem::ConduitDataCollection::BlueprintFieldToGridFunction(mesh,
234                                                                                                field,
235                                                                                                zero_copy);
236             dset->add_field(gf, fnames[f]);
237           }
238         }
239       }
240 
241       res->m_data_sets.push_back(dset);
242       res->m_domain_ids.push_back(domain_id);
243 
244     }
245     return res;
246 }
247 
248 // although is should probably never be the case that on domain
249 // is high order and the others are not, this method will
250 // return true if at least one domian is higher order
251 bool
IsHighOrder(const conduit::Node & n)252 MFEMDataAdapter::IsHighOrder(const conduit::Node &n)
253 {
254 
255   // treat everything as a multi-domain data set
256   const int num_domains = n.number_of_children();
257   for(int i = 0; i < num_domains; ++i)
258   {
259     const conduit::Node &dom = n.child(i);
260     if(dom.has_path("fields"))
261     {
262       const conduit::Node &fields = dom["fields"];
263       const int num_fields= fields.number_of_children();
264       for(int t = 0; t < num_fields; ++t)
265       {
266         const conduit::Node &field = fields.child(t);
267         if(field.has_path("basis")) return true;
268       }
269 
270     }
271   }
272 
273   return false;
274 }
275 //                                          VDim
276 // +------------+--------------------+------------------+
277 // | Space Type | FE Collection Type | Vector Dimension |
278 // +------------+--------------------+------------------+
279 // | SC         | H1_FESpace         | 1                | scalar continous (nodes)
280 // +------------+--------------------+------------------+
281 // | SD         | L1                 | 1                | scalar discontinous (zonal sort of)
282 // +------------+--------------------+------------------+
283 // | VC         | H1                 | D                | vector continious
284 // +------------+--------------------+------------------+
285 // | TC         | SCColl             | (D*(D+1))/2      | tensor continous
286 // +------------+--------------------+------------------+
287 // | TD         | SDColl             | (D*(D+1))/2      | tensor discontinous
288 // +------------+--------------------+------------------+
289 // | RT         | RTColl             | 1                |
290 // +------------+--------------------+------------------+
291 // | ND         | NDColl             | 1                |
292 // +------------+--------------------+------------------+
293 void
Linearize(MFEMDomains * ho_domains,conduit::Node & output,const int refinement)294 MFEMDataAdapter::Linearize(MFEMDomains *ho_domains, conduit::Node &output, const int refinement)
295 {
296   const int n_doms = ho_domains->m_data_sets.size();
297 
298   output.reset();
299   for(int i = 0; i < n_doms; ++i)
300   {
301 
302     conduit::Node &n_dset = output.append();
303     n_dset["state/domain_id"] = int(ho_domains->m_domain_ids[i]);
304     n_dset["state/cycle"] = int(ho_domains->m_data_sets[i]->cycle());
305     n_dset["state/time"] = double(ho_domains->m_data_sets[i]->time());
306 
307     // get the high order data
308     mfem::Mesh *ho_mesh = ho_domains->m_data_sets[i]->get_mesh();
309     const mfem::FiniteElementSpace *ho_fes_space = ho_mesh->GetNodalFESpace();
310     const mfem::FiniteElementCollection *ho_fes_col = ho_fes_space->FEColl();
311     // refine the mesh and convert to blueprint
312     mfem::Mesh *lo_mesh = new mfem::Mesh(ho_mesh, refinement, mfem::BasisType::GaussLobatto);
313     MeshToBlueprintMesh (lo_mesh, n_dset);
314 
315     int conn_size = n_dset["topologies/main/elements/connectivity"].dtype().number_of_elements();
316 
317     conduit::Node &n_fields = n_dset["fields"];
318     auto field_map = ho_domains->m_data_sets[i]->get_field_map();
319 
320     for(auto it = field_map.begin(); it != field_map.end(); ++it)
321     {
322 
323       mfem::GridFunction *ho_gf = it->second;
324       std::string basis(ho_gf->FESpace()->FEColl()->Name());
325       // we only have L2 or H2 at this point
326       bool node_centered = basis.find("H1_") != std::string::npos;
327 
328       mfem::FiniteElementSpace *ho_fes = ho_gf->FESpace();
329       if(ho_fes == nullptr)
330       {
331         ASCENT_ERROR("Linearize: high order gf finite element space is null")
332       }
333       // create the low order grid function
334       mfem::FiniteElementCollection *lo_col = nullptr;
335       if(node_centered)
336       {
337         lo_col = new mfem::LinearFECollection;
338       }
339       else
340       {
341         int  p = 0; // single scalar
342         lo_col = new mfem::L2_FECollection(p, ho_mesh->Dimension(), 1);
343       }
344       mfem::FiniteElementSpace *lo_fes = new mfem::FiniteElementSpace(lo_mesh, lo_col, ho_fes->GetVDim());
345       mfem::GridFunction *lo_gf = new mfem::GridFunction(lo_fes);
346       // transform the higher order function to a low order function somehow
347       mfem::OperatorHandle hi_to_lo;
348       lo_fes->GetTransferOperator(*ho_fes, hi_to_lo);
349       hi_to_lo.Ptr()->Mult(*ho_gf, *lo_gf);
350       // extract field
351       conduit::Node &n_field = n_fields[it->first];
352       GridFunctionToBlueprintField(lo_gf, n_field);
353       // all supported grid functions coming out of mfem end up being associtated with vertices
354       if(node_centered)
355       {
356         n_field["association"] = "vertex";
357       }
358       else
359       {
360         n_field["association"] = "element";
361       }
362 
363       delete lo_col;
364       delete lo_fes;
365       delete lo_gf;
366     }
367 
368     conduit::Node info;
369     bool success = conduit::blueprint::verify("mesh",n_dset,info);
370     if(!success)
371     {
372       info.print();
373       ASCENT_ERROR("Linearize: failed to build a blueprint conforming data set from mfem")
374     }
375     delete lo_mesh;
376 
377   }
378   //output.schema().print();
379 }
380 
381 void
GridFunctionToBlueprintField(mfem::GridFunction * gf,Node & n_field,const std::string & main_topology_name)382 MFEMDataAdapter::GridFunctionToBlueprintField(mfem::GridFunction *gf,
383                                               Node &n_field,
384                                               const std::string &main_topology_name)
385 {
386    n_field["basis"] = gf->FESpace()->FEColl()->Name();
387    n_field["topology"] = main_topology_name;
388 
389    int vdim  = gf->FESpace()->GetVDim();
390    int ndofs = gf->FESpace()->GetNDofs();
391 
392    const double * values = gf->HostRead();
393    if (vdim == 1) // scalar case
394    {
395       //n_field["values"].set_external(gf->GetData(),
396       //                               ndofs);
397       n_field["values"].set(values,
398                             ndofs);
399    }
400    else // vector case
401    {
402       // deal with striding of all components
403 
404      mfem::Ordering::Type ordering = gf->FESpace()->GetOrdering();
405 
406       int entry_stride = (ordering == mfem::Ordering::byNODES ? 1 : vdim);
407       int vdim_stride  = (ordering == mfem::Ordering::byNODES ? ndofs : 1);
408 
409       index_t offset = 0;
410       index_t stride = sizeof(double) * entry_stride;
411 
412       for (int d = 0;  d < vdim; d++)
413       {
414          std::ostringstream oss;
415          oss << "v" << d;
416          std::string comp_name = oss.str();
417          //n_field["values"][comp_name].set_external(gf->GetData(),
418          //                                          ndofs,
419          //                                          offset,
420          //                                          stride);
421          n_field["values"][comp_name].set(values,
422                                           ndofs,
423                                           offset,
424                                           stride);
425          offset +=  sizeof(double) * vdim_stride;
426       }
427    }
428 
429 }
430 
431 void
MeshToBlueprintMesh(mfem::Mesh * mesh,Node & n_mesh,const std::string & coordset_name,const std::string & main_topology_name,const std::string & boundary_topology_name)432 MFEMDataAdapter::MeshToBlueprintMesh(mfem::Mesh *mesh,
433                                      Node &n_mesh,
434                                      const std::string &coordset_name,
435                                      const std::string &main_topology_name,
436                                      const std::string &boundary_topology_name)
437 {
438    int dim = mesh->SpaceDimension();
439 
440    if(dim < 1 || dim > 3)
441    {
442      ASCENT_ERROR("invalid mesh dimension "<<dim);;
443    }
444 
445    ////////////////////////////////////////////
446    // Setup main coordset
447    ////////////////////////////////////////////
448 
449    // Assumes  mfem::Vertex has the layout of a double array.
450 
451    // this logic assumes an mfem vertex is always 3 doubles wide
452    int stride = sizeof(mfem::Vertex);
453    int num_vertices = mesh->GetNV();
454 
455    if(stride != 3 * sizeof(double) )
456    {
457      ASCENT_ERROR("Unexpected stride for mfem vertex");
458    }
459 
460    Node &n_mesh_coords = n_mesh["coordsets"][coordset_name];
461    n_mesh_coords["type"] =  "explicit";
462 
463 
464    double *coords_ptr = mesh->GetVertex(0);
465 
466    n_mesh_coords["values/x"].set(coords_ptr,
467                                  num_vertices,
468                                  0,
469                                  stride);
470 
471    if (dim >= 2)
472    {
473       n_mesh_coords["values/y"].set(coords_ptr,
474                                     num_vertices,
475                                     sizeof(double),
476                                     stride);
477    }
478    if (dim >= 3)
479    {
480       n_mesh_coords["values/z"].set(coords_ptr,
481                                     num_vertices,
482                                     sizeof(double) * 2,
483                                     stride);
484    }
485 
486    ////////////////////////////////////////////
487    // Setup main topo
488    ////////////////////////////////////////////
489 
490    Node &n_topo = n_mesh["topologies"][main_topology_name];
491 
492    n_topo["type"]  = "unstructured";
493    n_topo["coordset"] = coordset_name;
494 
495    mfem::Element::Type ele_type = static_cast<mfem::Element::Type>(mesh->GetElement(
496                                                           0)->GetType());
497 
498    std::string ele_shape = ElementTypeToShapeName(ele_type);
499 
500    n_topo["elements/shape"] = ele_shape;
501 
502    mfem::GridFunction *gf_mesh_nodes = mesh->GetNodes();
503 
504    if (gf_mesh_nodes != NULL)
505    {
506       n_topo["grid_function"] =  "mesh_nodes";
507    }
508 
509    // connectivity
510    // TODO: generic case, i don't think we can zero-copy (mfem allocs
511    // an array per element) so we alloc our own contig array and
512    // copy out. Some other cases (sidre) may actually have contig
513    // allocation but I am  not sure how to detect this case from mfem
514    int num_ele = mesh->GetNE();
515    int geom = mesh->GetElementBaseGeometry(0);
516    int idxs_per_ele = mfem::Geometry::NumVerts[geom];
517    int num_conn_idxs =  num_ele * idxs_per_ele;
518 
519    n_topo["elements/connectivity"].set(DataType::c_int(num_conn_idxs));
520 
521    int *conn_ptr = n_topo["elements/connectivity"].value();
522 
523    for (int i=0; i < num_ele; i++)
524    {
525       const mfem::Element *ele = mesh->GetElement(i);
526       const int *ele_verts = ele->GetVertices();
527 
528       memcpy(conn_ptr, ele_verts, idxs_per_ele * sizeof(int));
529 
530       conn_ptr += idxs_per_ele;
531    }
532 
533    if (gf_mesh_nodes != NULL)
534    {
535       GridFunctionToBlueprintField(gf_mesh_nodes,
536                                    n_mesh["fields/mesh_nodes"],
537                                    main_topology_name);
538       n_mesh["fields/mesh_nodes/association"] = "vertex";
539    }
540 
541    ////////////////////////////////////////////
542    // Setup mesh attribute
543    ////////////////////////////////////////////
544 
545    Node &n_mesh_att = n_mesh["fields/element_attribute"];
546 
547    n_mesh_att["association"] = "element";
548    n_mesh_att["topology"] = main_topology_name;
549    n_mesh_att["values"].set(DataType::c_int(num_ele));
550 
551    int_array att_vals = n_mesh_att["values"].value();
552    for (int i = 0; i < num_ele; i++)
553    {
554       att_vals[i] = mesh->GetAttribute(i);
555    }
556 
557    ////////////////////////////////////////////
558    // Setup bndry topo "boundary"
559    ////////////////////////////////////////////
560 
561    // guard vs if we have boundary elements
562    if (mesh->GetNBE() > 0)
563    {
564       n_topo["boundary_topology"] = boundary_topology_name;
565 
566       Node &n_bndry_topo = n_mesh["topologies"][boundary_topology_name];
567 
568       n_bndry_topo["type"]     = "unstructured";
569       n_bndry_topo["coordset"] = coordset_name;
570 
571       mfem::Element::Type bndry_ele_type = static_cast<mfem::Element::Type>(mesh->GetBdrElement(
572                                                                    0)->GetType());
573 
574       std::string bndry_ele_shape = ElementTypeToShapeName(bndry_ele_type);
575 
576       n_bndry_topo["elements/shape"] = bndry_ele_shape;
577 
578 
579       int num_bndry_ele = mesh->GetNBE();
580       int bndry_geom    = mesh->GetBdrElementBaseGeometry(0);
581       int bndry_idxs_per_ele  = mfem::Geometry::NumVerts[bndry_geom];
582       int num_bndry_conn_idxs =  num_bndry_ele * bndry_idxs_per_ele;
583 
584       n_bndry_topo["elements/connectivity"].set(DataType::c_int(num_bndry_conn_idxs));
585 
586       int *bndry_conn_ptr = n_bndry_topo["elements/connectivity"].value();
587 
588       for (int i=0; i < num_bndry_ele; i++)
589       {
590          const mfem::Element *bndry_ele = mesh->GetBdrElement(i);
591          const int *bndry_ele_verts = bndry_ele->GetVertices();
592 
593          memcpy(bndry_conn_ptr, bndry_ele_verts, bndry_idxs_per_ele  * sizeof(int));
594 
595          bndry_conn_ptr += bndry_idxs_per_ele;
596       }
597 
598       ////////////////////////////////////////////
599       // Setup bndry mesh attribute
600       ////////////////////////////////////////////
601 
602       Node &n_bndry_mesh_att = n_mesh["fields/boundary_attribute"];
603 
604       n_bndry_mesh_att["association"] = "element";
605       n_bndry_mesh_att["topology"] = boundary_topology_name;
606       n_bndry_mesh_att["values"].set(DataType::c_int(num_bndry_ele));
607 
608       int_array bndry_att_vals = n_bndry_mesh_att["values"].value();
609       for (int i = 0; i < num_bndry_ele; i++)
610       {
611          bndry_att_vals[i] = mesh->GetBdrAttribute(i);
612       }
613    }
614 }
615 std::string
ElementTypeToShapeName(mfem::Element::Type element_type)616 MFEMDataAdapter::ElementTypeToShapeName(mfem::Element::Type element_type)
617 {
618    // Adapted from SidreDataCollection
619 
620    // Note -- the mapping from Element::Type to string is based on
621    //   enum Element::Type { POINT, SEGMENT, TRIANGLE, QUADRILATERAL,
622    //                        TETRAHEDRON, HEXAHEDRON };
623    // Note: -- the string names are from conduit's blueprint
624 
625    switch (element_type)
626    {
627      case mfem::Element::POINT:          return "point";
628      case mfem::Element::SEGMENT:        return "line";
629      case mfem::Element::TRIANGLE:       return "tri";
630      case mfem::Element::QUADRILATERAL:  return "quad";
631      case mfem::Element::TETRAHEDRON:    return "tet";
632      case mfem::Element::HEXAHEDRON:     return "hex";
633      case mfem::Element::WEDGE:          return "wedge";
634    }
635 
636    return "unknown";
637 }
638 
639 };
640 //-----------------------------------------------------------------------------
641 // -- end ascent:: --
642