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