// Copyright (c) 2010-2023, Lawrence Livermore National Security, LLC. Produced // at the Lawrence Livermore National Laboratory. All Rights reserved. See files // LICENSE and NOTICE for details. LLNL-CODE-806117. // // This file is part of the MFEM library. For more information and source code // availability visit https://mfem.org. // // MFEM is free software; you can redistribute it and/or modify it under the // terms of the BSD-3 license. We welcome feedback and contributions, see file // CONTRIBUTING.md for details. #include "../config/config.hpp" #ifdef MFEM_USE_FMS #include "fmsconvert.hpp" #include using std::endl; // #define DEBUG_FMS_MFEM 1 // #define DEBUG_MFEM_FMS 1 namespace mfem { static inline int FmsBasisTypeToMfemBasis(FmsBasisType b) { int retval = -1; switch (b) { case FMS_NODAL_GAUSS_OPEN: retval = mfem::BasisType::GaussLegendre;; break; case FMS_NODAL_GAUSS_CLOSED: retval = mfem::BasisType::GaussLobatto;; break; case FMS_POSITIVE: retval = mfem::BasisType::Positive;; break; case FMS_NODAL_UNIFORM_OPEN: retval = mfem::BasisType::OpenUniform; break; case FMS_NODAL_UNIFORM_CLOSED: retval = mfem::BasisType::ClosedUniform; break; case FMS_NODAL_CHEBYSHEV_OPEN: case FMS_NODAL_CHEBYSHEV_CLOSED: mfem::out << "FMS_NODAL_CHEBYSHEV_OPEN, FMS_NODAL_CHEBYSHEV_CLOSED need conversion to MFEM types." << endl; break; } return retval; } // The following function is unused (for now), so it is commented out to // suppress compilation warning. #if 0 /** @brief Get the order and layout of the field. */ static int FmsFieldGetOrderAndLayout(FmsField f, FmsInt *f_order, FmsLayoutType *f_layout) { int err = 0; FmsFieldDescriptor fd; FmsLayoutType layout; FmsScalarType data_type; const void *data = nullptr; FmsInt order = 0; FmsFieldGet(f, &fd, NULL, &layout, &data_type, &data); FmsFieldDescriptorType f_fd_type; FmsFieldDescriptorGetType(fd, &f_fd_type); if (f_fd_type != FMS_FIXED_ORDER) { err = 1; } else { FmsFieldType field_type; FmsBasisType basis_type; FmsFieldDescriptorGetFixedOrder(fd, &field_type, &basis_type, &order); } *f_order = order; *f_layout = layout; return err; } #endif /** @brief This function converts an FmsField to an MFEM GridFunction. @note I took some of the Pumi example code from the mesh conversion function that converted coordinates and am trying to make it more general. Coordinates are just another field so it seems like a good starting point. We still have to support a bunch of other function types, etc. */ template int FmsFieldToGridFunction(FmsMesh fms_mesh, FmsField f, Mesh *mesh, GridFunction &func, bool setFE) { int err = 0; // NOTE: transplanted from the FmsMeshToMesh function // We should do this work once and save it. //-------------------------------------------------- FmsInt dim, n_elem, space_dim; // Find the first component that has coordinates - that will be the new mfem // mesh. FmsInt num_comp; FmsMeshGetNumComponents(fms_mesh, &num_comp); FmsComponent main_comp = NULL; FmsField coords = NULL; for (FmsInt comp_id = 0; comp_id < num_comp; comp_id++) { FmsComponent comp; FmsMeshGetComponent(fms_mesh, comp_id, &comp); FmsComponentGetCoordinates(comp, &coords); if (coords) { main_comp = comp; break; } } if (!main_comp) { return 1; } FmsComponentGetDimension(main_comp, &dim); FmsComponentGetNumEntities(main_comp, &n_elem); FmsInt n_ents[FMS_NUM_ENTITY_TYPES]; FmsInt n_main_parts; FmsComponentGetNumParts(main_comp, &n_main_parts); for (FmsInt et = FMS_VERTEX; et < FMS_NUM_ENTITY_TYPES; et++) { n_ents[et] = 0; for (FmsInt part_id = 0; part_id < n_main_parts; part_id++) { FmsInt num_ents; FmsComponentGetPart(main_comp, part_id, (FmsEntityType)et, NULL, NULL, NULL, NULL, &num_ents); n_ents[et] += num_ents; } } //-------------------------------------------------- // Interrogate the field. FmsFieldDescriptor f_fd; FmsLayoutType f_layout; FmsScalarType f_data_type; const void *f_data; FmsFieldGet(f, &f_fd, &space_dim, &f_layout, &f_data_type, &f_data); // FmsFieldGet(coords, NULL, &space_dim, NULL, NULL, NULL); FmsInt f_num_dofs; FmsFieldDescriptorGetNumDofs(f_fd, &f_num_dofs); // Access FMS data through this typed pointer. auto src_data = reinterpret_cast(f_data); FmsFieldDescriptorType f_fd_type; FmsFieldDescriptorGetType(f_fd, &f_fd_type); if (f_fd_type != FMS_FIXED_ORDER) { return 9; } FmsFieldType f_field_type; FmsBasisType f_basis_type; FmsInt f_order; FmsFieldDescriptorGetFixedOrder(f_fd, &f_field_type, &f_basis_type, &f_order); if (f_field_type != FMS_CONTINUOUS && f_field_type != FMS_DISCONTINUOUS && f_field_type != FMS_HDIV) { return 10; } int btype = FmsBasisTypeToMfemBasis(f_basis_type); if (btype < 0) { mfem::out << "\tInvalid BasisType: " << BasisType::Name(btype) << std::endl; return 11; } //------------------------------------------------------------------ if (setFE) { // We could assemble a name based on fe_coll.hpp rules and pass to // FiniteElementCollection::New() mfem::FiniteElementCollection *fec = nullptr; switch (f_field_type) { case FMS_DISCONTINUOUS: fec = new L2_FECollection(f_order, dim, btype); break; case FMS_CONTINUOUS: fec = new H1_FECollection(f_order, dim, btype); break; case FMS_HDIV: fec = new RT_FECollection(f_order, dim); break; case FMS_HCURL: case FMS_DISCONTINUOUS_WEIGHTED: MFEM_ABORT("Field types FMS_HCURL and FMS_DISCONTINUOUS_WEIGHTED" " are not supported yet."); break; } int ordering = (f_layout == FMS_BY_VDIM) ? Ordering::byVDIM : Ordering::byNODES; auto fes = new FiniteElementSpace(mesh, fec, space_dim, ordering); func.SetSpace(fes); } //------------------------------------------------------------------ const FmsInt nstride = (f_layout == FMS_BY_VDIM) ? space_dim : 1; const FmsInt vstride = (f_layout == FMS_BY_VDIM) ? 1 : f_num_dofs; // Data reordering to store the data into func. if ((FmsInt)(func.Size()) != f_num_dofs*space_dim) { return 12; } mfem::FiniteElementSpace *fes = func.FESpace(); const int vdim = fes->GetVDim(); const mfem::FiniteElementCollection *fec = fes->FEColl(); const int vert_dofs = fec->DofForGeometry(mfem::Geometry::POINT); const int edge_dofs = fec->DofForGeometry(mfem::Geometry::SEGMENT); const int tri_dofs = fec->DofForGeometry(mfem::Geometry::TRIANGLE); const int quad_dofs = fec->DofForGeometry(mfem::Geometry::SQUARE); const int tet_dofs = fec->DofForGeometry(mfem::Geometry::TETRAHEDRON); const int hex_dofs = fec->DofForGeometry(mfem::Geometry::CUBE); int ent_dofs[FMS_NUM_ENTITY_TYPES]; ent_dofs[FMS_VERTEX] = vert_dofs; ent_dofs[FMS_EDGE] = edge_dofs; ent_dofs[FMS_TRIANGLE] = tri_dofs; ent_dofs[FMS_QUADRILATERAL] = quad_dofs; ent_dofs[FMS_TETRAHEDRON] = tet_dofs; ent_dofs[FMS_HEXAHEDRON] = hex_dofs; FmsInt fms_dof_offset = 0; int mfem_ent_cnt[4] = {0,0,0,0}; // mfem entity counters, by dimension int mfem_last_vert_cnt = 0; mfem::HashTable mfem_edge; mfem::HashTable mfem_face; if (dim >= 2 && edge_dofs > 0) { mfem::Array ev; for (int i = 0; i < mesh->GetNEdges(); i++) { mesh->GetEdgeVertices(i, ev); int id = mfem_edge.GetId(ev[0], ev[1]); if (id != i) { return 13; } } } if (dim >= 3 && ((n_ents[FMS_TRIANGLE] > 0 && tri_dofs > 0) || (n_ents[FMS_QUADRILATERAL] > 0 && quad_dofs > 0))) { mfem::Array fv; for (int i = 0; i < mesh->GetNFaces(); i++) { mesh->GetFaceVertices(i, fv); if (fv.Size() == 3) { fv.Append(INT_MAX); } // HashTable uses the smallest 3 of the 4 indices to hash Hashed4 int id = mfem_face.GetId(fv[0], fv[1], fv[2], fv[3]); if (id != i) { return 14; } } } // Loop over all parts of the main component for (FmsInt part_id = 0; part_id < n_main_parts; part_id++) { // Loop over all entity types in the part for (FmsInt et = FMS_VERTEX; et < FMS_NUM_ENTITY_TYPES; et++) { FmsDomain domain; FmsIntType ent_id_type; const void *ents; const FmsOrientation *ents_ori; FmsInt num_ents; FmsComponentGetPart(main_comp, part_id, (FmsEntityType)et, &domain, &ent_id_type, &ents, &ents_ori, &num_ents); if (num_ents == 0) { continue; } if (ent_dofs[et] == 0) { if (et == FMS_VERTEX) { mfem_last_vert_cnt = mfem_ent_cnt[et]; } mfem_ent_cnt[FmsEntityDim[et]] += num_ents; continue; } if (ents != NULL && (ent_id_type != FMS_INT32 && ent_id_type != FMS_UINT32)) { return 15; } if (ents_ori != NULL) { return 16; } if (et == FMS_VERTEX) { const int mfem_dof_offset = mfem_ent_cnt[0]*vert_dofs; for (FmsInt i = 0; i < num_ents*vert_dofs; i++) { for (int j = 0; j < vdim; j++) { const int idx = i*nstride+j*vstride; func(mfem_dof_offset*nstride+idx) = static_cast(src_data[fms_dof_offset*nstride+idx]); } } fms_dof_offset += num_ents*vert_dofs; mfem_last_vert_cnt = mfem_ent_cnt[et]; mfem_ent_cnt[0] += num_ents; continue; } mfem::Array dofs; if (FmsEntityDim[et] == dim) { for (FmsInt e = 0; e < num_ents; e++) { fes->GetElementInteriorDofs(mfem_ent_cnt[dim]+e, dofs); for (int i = 0; i < ent_dofs[et]; i++, fms_dof_offset++) { for (int j = 0; j < vdim; j++) { func(fes->DofToVDof(dofs[i],j)) = static_cast(src_data[fms_dof_offset*nstride+j*vstride]); } } } mfem_ent_cnt[dim] += num_ents; continue; } const FmsInt nv = FmsEntityNumVerts[et]; mfem::Array ents_verts(num_ents*nv), m_ev; const int *ei = (const int *)ents; if (ents == NULL) { FmsDomainGetEntitiesVerts(domain, (FmsEntityType)et, NULL, FMS_INT32, 0, ents_verts.GetData(), num_ents); } else { for (FmsInt i = 0; i < num_ents; i++) { FmsDomainGetEntitiesVerts(domain, (FmsEntityType)et, NULL, FMS_INT32, ei[i], &ents_verts[i*nv], 1); } } for (int i = 0; i < ents_verts.Size(); i++) { ents_verts[i] += mfem_last_vert_cnt; } const int *perm; switch ((FmsEntityType)et) { case FMS_EDGE: { for (FmsInt part_ent_id = 0; part_ent_id < num_ents; part_ent_id++) { const int *ev = &ents_verts[2*part_ent_id]; int mfem_edge_id = mfem_edge.FindId(ev[0], ev[1]); if (mfem_edge_id < 0) { return 17; } mesh->GetEdgeVertices(mfem_edge_id, m_ev); int ori = (ev[0] == m_ev[0]) ? 0 : 1; perm = fec->DofOrderForOrientation(mfem::Geometry::SEGMENT, ori); fes->GetEdgeInteriorDofs(mfem_edge_id, dofs); for (int i = 0; i < edge_dofs; i++) { for (int j = 0; j < vdim; j++) { func(fes->DofToVDof(dofs[i],j)) = static_cast(src_data[(fms_dof_offset+perm[i])*nstride+j*vstride]); } } fms_dof_offset += edge_dofs; } break; } case FMS_TRIANGLE: { for (FmsInt part_ent_id = 0; part_ent_id < num_ents; part_ent_id++) { const int *tv = &ents_verts[3*part_ent_id]; int mfem_face_id = mfem_face.FindId(tv[0], tv[1], tv[2], INT_MAX); if (mfem_face_id < 0) { return 18; } mesh->GetFaceVertices(mfem_face_id, m_ev); int ori = 0; while (tv[ori] != m_ev[0]) { ori++; } ori = (tv[(ori+1)%3] == m_ev[1]) ? 2*ori : 2*ori+1; perm = fec->DofOrderForOrientation(mfem::Geometry::TRIANGLE, ori); fes->GetFaceInteriorDofs(mfem_face_id, dofs); for (int i = 0; i < tri_dofs; i++) { for (int j = 0; j < vdim; j++) { func(fes->DofToVDof(dofs[i],j)) = static_cast(src_data[(fms_dof_offset+perm[i])*nstride+j*vstride]); } } fms_dof_offset += tri_dofs; } break; } case FMS_QUADRILATERAL: { for (FmsInt part_ent_id = 0; part_ent_id < num_ents; part_ent_id++) { const int *qv = &ents_verts[4*part_ent_id]; int mfem_face_id = mfem_face.FindId(qv[0], qv[1], qv[2], qv[3]); if (mfem_face_id < 0) { return 19; } mesh->GetFaceVertices(mfem_face_id, m_ev); int ori = 0; while (qv[ori] != m_ev[0]) { ori++; } ori = (qv[(ori+1)%4] == m_ev[1]) ? 2*ori : 2*ori+1; perm = fec->DofOrderForOrientation(mfem::Geometry::SQUARE, ori); fes->GetFaceInteriorDofs(mfem_face_id, dofs); for (int i = 0; i < quad_dofs; i++) { for (int j = 0; j < vdim; j++) { func(fes->DofToVDof(dofs[i],j)) = static_cast(src_data[(fms_dof_offset+perm[i])*nstride+j*vstride]); } } fms_dof_offset += quad_dofs; } break; } default: break; } mfem_ent_cnt[FmsEntityDim[et]] += num_ents; } } return err; } int FmsMeshToMesh(FmsMesh fms_mesh, Mesh **mfem_mesh) { FmsInt dim, n_vert, n_elem, n_bdr_elem, space_dim; // Find the first component that has coordinates - that will be the new mfem // mesh. FmsInt num_comp; FmsMeshGetNumComponents(fms_mesh, &num_comp); FmsComponent main_comp = NULL; FmsField coords = NULL; for (FmsInt comp_id = 0; comp_id < num_comp; comp_id++) { FmsComponent comp; FmsMeshGetComponent(fms_mesh, comp_id, &comp); FmsComponentGetCoordinates(comp, &coords); if (coords) { main_comp = comp; break; } } if (!main_comp) { return 1; } FmsComponentGetDimension(main_comp, &dim); FmsComponentGetNumEntities(main_comp, &n_elem); FmsInt n_ents[FMS_NUM_ENTITY_TYPES]; FmsInt n_main_parts; FmsComponentGetNumParts(main_comp, &n_main_parts); #define RENUMBER_ENTITIES #ifdef RENUMBER_ENTITIES // I noticed that to get domains working right, since they appear to be // defined in a local vertex numbering scheme, we have to offset the vertex // ids that MFEM makes for shapes to move them to the coordinates in the // current domain. // However, parts would just be a set of element ids in the current domain // and it does not seem appropriate to offset the points in that case. // Should domains be treated specially? int *verts_per_part = new int[n_main_parts]; #endif // Sum the counts for each entity type across parts. for (FmsInt et = FMS_VERTEX; et < FMS_NUM_ENTITY_TYPES; et++) { n_ents[et] = 0; for (FmsInt part_id = 0; part_id < n_main_parts; part_id++) { FmsInt num_ents; FmsComponentGetPart(main_comp, part_id, (FmsEntityType)et, NULL, NULL, NULL, NULL, &num_ents); n_ents[et] += num_ents; #ifdef RENUMBER_ENTITIES if (et == FMS_VERTEX) { verts_per_part[part_id] = num_ents; } #endif } } n_vert = n_ents[FMS_VERTEX]; #ifdef RENUMBER_ENTITIES int *verts_start = new int[n_main_parts]; verts_start[0] = 0; for (FmsInt i = 1; i < n_main_parts; ++i) { verts_start[i] = verts_start[i-1] + verts_per_part[i-1]; } #endif // The first related component of dimension dim-1 will be the boundary of the // new mfem mesh. FmsComponent bdr_comp = NULL; FmsInt num_rel_comps; const FmsInt *rel_comp_ids; FmsComponentGetRelations(main_comp, &rel_comp_ids, &num_rel_comps); for (FmsInt i = 0; i < num_rel_comps; i++) { FmsComponent comp; FmsMeshGetComponent(fms_mesh, rel_comp_ids[i], &comp); FmsInt comp_dim; FmsComponentGetDimension(comp, &comp_dim); if (comp_dim == dim-1) { bdr_comp = comp; break; } } if (bdr_comp) { FmsComponentGetNumEntities(bdr_comp, &n_bdr_elem); } else { n_bdr_elem = 0; } FmsFieldGet(coords, NULL, &space_dim, NULL, NULL, NULL); int err = 0; Mesh *mesh = nullptr; mesh = new Mesh(dim, n_vert, n_elem, n_bdr_elem, space_dim); // Element tags FmsInt num_tags; FmsMeshGetNumTags(fms_mesh, &num_tags); FmsTag elem_tag = NULL, bdr_tag = NULL; for (FmsInt tag_id = 0; tag_id < num_tags; tag_id++) { FmsTag tag; FmsMeshGetTag(fms_mesh, tag_id, &tag); FmsComponent comp; FmsTagGetComponent(tag, &comp); if (!elem_tag && comp == main_comp) { #if DEBUG_FMS_MFEM const char *tn = NULL; FmsTagGetName(tag, &tn); mfem::out << "Found element tag " << tn << std::endl; #endif elem_tag = tag; } else if (!bdr_tag && comp == bdr_comp) { #if DEBUG_FMS_MFEM const char *tn = NULL; FmsTagGetName(tag, &tn); mfem::out << "Found boundary tag " << tn << std::endl; #endif bdr_tag = tag; } } FmsIntType attr_type; const void *v_attr, *v_bdr_attr; mfem::Array attr, bdr_attr; FmsInt num_attr; // Element attributes if (elem_tag) { FmsTagGet(elem_tag, &attr_type, &v_attr, &num_attr); if (attr_type == FMS_UINT8) { mfem::Array at((uint8_t*)v_attr, num_attr); attr = at; } else if (attr_type == FMS_INT32 || attr_type == FMS_UINT32) { attr.MakeRef((int*)v_attr, num_attr); } else { err = 1; // "attribute type not supported!" goto func_exit; } } // Boundary attributes if (bdr_tag) { FmsTagGet(bdr_tag, &attr_type, &v_bdr_attr, &num_attr); if (attr_type == FMS_UINT8) { mfem::Array at((uint8_t*)v_bdr_attr, num_attr); bdr_attr = at; } else if (attr_type == FMS_INT32 || attr_type == FMS_UINT32) { bdr_attr.MakeRef((int*)v_bdr_attr, num_attr); } else { err = 2; // "bdr attribute type not supported!" goto func_exit; } } // Add elements for (FmsInt part_id = 0; part_id < n_main_parts; part_id++) { for (int et = FMS_VERTEX; et < FMS_NUM_ENTITY_TYPES; et++) { if (FmsEntityDim[et] != dim) { continue; } FmsDomain domain; FmsIntType elem_id_type; const void *elem_ids; const FmsOrientation *elem_ori; FmsInt num_elems; FmsComponentGetPart(main_comp, part_id, (FmsEntityType)et, &domain, &elem_id_type, &elem_ids, &elem_ori, &num_elems); if (num_elems == 0) { continue; } if (elem_ids != NULL && (elem_id_type != FMS_INT32 && elem_id_type != FMS_UINT32)) { err = 3; goto func_exit; } if (elem_ori != NULL) { err = 4; goto func_exit; } const FmsInt nv = FmsEntityNumVerts[et]; mfem::Array ents_verts(num_elems*nv); if (elem_ids == NULL) { FmsDomainGetEntitiesVerts(domain, (FmsEntityType)et, NULL, FMS_INT32, 0, ents_verts.GetData(), num_elems); } else { const int *ei = (const int *)elem_ids; for (FmsInt i = 0; i < num_elems; i++) { FmsDomainGetEntitiesVerts(domain, (FmsEntityType)et, NULL, FMS_INT32, ei[i], &ents_verts[i*nv], 1); } } const int elem_offset = mesh->GetNE(); switch ((FmsEntityType)et) { case FMS_EDGE: err = 5; goto func_exit; break; case FMS_TRIANGLE: #ifdef RENUMBER_ENTITIES // The domain vertices/edges were defined in local ordering. We // now have a set of triangle vertices defined in terms of local // vertex numbers. Renumber them to a global numbering. for (FmsInt i = 0; i < num_elems*3; i++) { ents_verts[i] += verts_start[part_id]; } #endif for (FmsInt i = 0; i < num_elems; i++) { mesh->AddTriangle( &ents_verts[3*i], elem_tag ? attr[elem_offset+i] : 1); } break; case FMS_QUADRILATERAL: #ifdef RENUMBER_ENTITIES for (FmsInt i = 0; i < num_elems*4; i++) { ents_verts[i] += verts_start[part_id]; } #endif for (FmsInt i = 0; i < num_elems; i++) { mesh->AddQuad(&ents_verts[4*i], elem_tag ? attr[elem_offset+i] : 1); } break; case FMS_TETRAHEDRON: #ifdef RENUMBER_ENTITIES for (FmsInt i = 0; i < num_elems*4; i++) { ents_verts[i] += verts_start[part_id]; } #endif for (FmsInt i = 0; i < num_elems; i++) { mesh->AddTet(&ents_verts[4*i], elem_tag ? attr[elem_offset+i] : 1); } break; // TODO: What about wedges and pyramids? case FMS_HEXAHEDRON: #ifdef RENUMBER_ENTITIES for (FmsInt i = 0; i < num_elems*8; i++) { ents_verts[i] += verts_start[part_id]; } #endif for (FmsInt i = 0; i < num_elems; i++) { const int *hex_verts = &ents_verts[8*i]; #if 0 const int reorder[8] = {0, 1, 2, 3, 5, 4, 6, 7}; const int new_verts[8] = {hex_verts[reorder[0]], hex_verts[reorder[1]], hex_verts[reorder[2]], hex_verts[reorder[3]], hex_verts[reorder[4]], hex_verts[reorder[5]], hex_verts[reorder[6]], hex_verts[reorder[7]] }; hex_verts = new_verts; #endif mesh->AddHex(hex_verts, elem_tag ? attr[elem_offset+i] : 1); } break; default: break; } } } // Add boundary elements if (bdr_comp && n_bdr_elem > 0) { FmsInt n_bdr_parts; FmsComponentGetNumParts(bdr_comp, &n_bdr_parts); for (FmsInt part_id = 0; part_id < n_bdr_parts; part_id++) { for (int et = FMS_VERTEX; et < FMS_NUM_ENTITY_TYPES; et++) { if (FmsEntityDim[et] != dim-1) { continue; } FmsDomain domain; FmsIntType elem_id_type; const void *elem_ids; const FmsOrientation *elem_ori; FmsInt num_elems; FmsComponentGetPart(bdr_comp, part_id, (FmsEntityType)et, &domain, &elem_id_type, &elem_ids, &elem_ori, &num_elems); if (num_elems == 0) { continue; } if (elem_ids != NULL && (elem_id_type != FMS_INT32 && elem_id_type != FMS_UINT32)) { err = 6; goto func_exit; } if (elem_ori != NULL) { err = 7; goto func_exit; } const FmsInt nv = FmsEntityNumVerts[et]; mfem::Array ents_verts(num_elems*nv); if (elem_ids == NULL) { FmsDomainGetEntitiesVerts(domain, (FmsEntityType)et, NULL, FMS_INT32, 0, ents_verts.GetData(), num_elems); } else { const int *ei = (const int *)elem_ids; for (FmsInt i = 0; i < num_elems; i++) { FmsDomainGetEntitiesVerts(domain, (FmsEntityType)et, NULL, FMS_INT32, ei[i], &ents_verts[i*nv], 1); } } const int elem_offset = mesh->GetNBE(); switch ((FmsEntityType)et) { case FMS_EDGE: for (FmsInt i = 0; i < num_elems; i++) { mesh->AddBdrSegment( &ents_verts[2*i], bdr_tag ? bdr_attr[elem_offset+i] : 1); } break; case FMS_TRIANGLE: for (FmsInt i = 0; i < num_elems; i++) { mesh->AddBdrTriangle( &ents_verts[3*i], bdr_tag ? bdr_attr[elem_offset+i] : 1); } break; case FMS_QUADRILATERAL: for (FmsInt i = 0; i < num_elems; i++) { mesh->AddBdrQuad( &ents_verts[4*i], bdr_tag ? bdr_attr[elem_offset+i] : 1); } break; default: break; } } } } #ifdef RENUMBER_ENTITIES delete [] verts_per_part; delete [] verts_start; #endif // Transfer coordinates { // Set the vertex coordinates to zero const double origin[3] = {0.,0.,0.}; for (FmsInt vi = 0; vi < n_vert; vi++) { mesh->AddVertex(origin); } // Finalize the mesh topology mesh->FinalizeTopology(); FmsFieldDescriptor coords_fd = NULL; FmsLayoutType coords_layout; FmsFieldGet(coords, &coords_fd, NULL, &coords_layout, NULL, NULL); if (!coords_fd) { mfem::err << "Error reading the FMS mesh coords' FieldDescriptor." << std::endl; err = 8; goto func_exit; } FmsInt coords_order = 0; FmsBasisType coords_btype = FMS_NODAL_GAUSS_CLOSED; FmsFieldType coords_ftype = FMS_CONTINUOUS; FmsFieldDescriptorGetFixedOrder(coords_fd, &coords_ftype, &coords_btype, &coords_order); // Maybe this is extra but it seems mesh->SetCurvature assumes // btype=1. Maybe protects us against corrupt data. if (coords_btype != FMS_NODAL_GAUSS_CLOSED) { mfem::err << "Error reading FMS mesh coords." << std::endl; err = 9; goto func_exit; } // Switch to mfem::Mesh with nodes (interpolates the linear coordinates) const bool discont = (coords_ftype == FMS_DISCONTINUOUS); mesh->SetCurvature(coords_order, discont, space_dim, (coords_layout == FMS_BY_VDIM) ? mfem::Ordering::byVDIM : mfem::Ordering::byNODES); // Finalize mesh construction mesh->Finalize(); // Set the high-order mesh nodes mfem::GridFunction &nodes = *mesh->GetNodes(); FmsFieldToGridFunction(fms_mesh, coords, mesh, nodes, false); } func_exit: if (err) { delete mesh; } else { *mfem_mesh = mesh; } return err; } bool BasisTypeToFmsBasisType(int bt, FmsBasisType &btype) { bool retval = false; switch (bt) { case mfem::BasisType::GaussLegendre: // mfem::out << "mfem::BasisType::GaussLegendre -> FMS_NODAL_GAUSS_OPEN" << endl; btype = FMS_NODAL_GAUSS_OPEN; retval = true; break; case mfem::BasisType::GaussLobatto: // mfem::out << "mfem::BasisType::GaussLobato -> FMS_NODAL_GAUSS_CLOSED" << endl; btype = FMS_NODAL_GAUSS_CLOSED; retval = true; break; case mfem::BasisType::Positive: // mfem::out << "mfem::BasisType::Positive -> FMS_POSITIVE" << endl; btype = FMS_POSITIVE; retval = true; break; case mfem::BasisType::OpenUniform: // mfem::out << "mfem::BasisType::OpenUniform -> ?" << endl; btype = FMS_NODAL_UNIFORM_OPEN; retval = true; break; case mfem::BasisType::ClosedUniform: // mfem::out << "mfem::BasisType::ClosedUniform -> ?" << endl; btype = FMS_NODAL_UNIFORM_CLOSED; retval = true; break; case mfem::BasisType::OpenHalfUniform: // mfem::out << "mfem::BasisType::OpenHalfUniform -> ?" << endl; break; case mfem::BasisType::Serendipity: // mfem::out << "mfem::BasisType::Serendipity -> ?" << endl; break; case mfem::BasisType::ClosedGL: // mfem::out << "mfem::BasisType::ClosedGL -> ?" << endl; break; } /* Which MFEM types map to:? FMS_NODAL_CHEBYSHEV_OPEN, FMS_NODAL_CHEBYSHEV_CLOSED, */ return retval; } /** @note We add the FMS field descriptor and field in here so we can only do it after successfully validating the inputs (handling certain grid function types, etc.) */ int GridFunctionToFmsField(FmsDataCollection dc, FmsComponent comp, const std::string &fd_name, const std::string &field_name, const Mesh *mesh, const GridFunction *gf, FmsField *outfield) { if (!dc) { return 1; } if (!comp) { return 2; } if (!mesh) { return 3; } if (!gf) { return 4; } if (!outfield) { return 5; } double *c = gf->GetData(); const mfem::FiniteElementSpace *fespace = gf->FESpace(); const mfem::FiniteElementCollection *fecoll = fespace->FEColl(); #ifdef DEBUG_MFEM_FMS mfem::out << "Adding FMS field for " << field_name << "..." << endl; #endif /* Q: No getter for the basis, do different kinds of FECollection have implied basis? There are two subclasses that actually have the getter, maybe those aren't implied? */ FmsInt order = 1; int vdim = 1; FmsFieldType ftype = FMS_CONTINUOUS; FmsBasisType btype = FMS_NODAL_GAUSS_CLOSED; switch (fecoll->GetContType()) { case mfem::FiniteElementCollection::CONTINUOUS: { ftype = FMS_CONTINUOUS; order = static_cast(fespace->GetOrder(0)); vdim = gf->VectorDim(); auto fec = dynamic_cast(fecoll); if (fec != nullptr) { if (!BasisTypeToFmsBasisType(fec->GetBasisType(), btype)) { mfem::err << "Error converting MFEM basis type to FMS for" " FMS_CONTINUOUS." << std::endl; return 6; } } break; } case mfem::FiniteElementCollection::DISCONTINUOUS: { ftype = FMS_DISCONTINUOUS; order = static_cast(fespace->GetOrder(0)); vdim = gf->VectorDim(); auto fec = dynamic_cast(fecoll); if (fec != nullptr) { if (!BasisTypeToFmsBasisType(fec->GetBasisType(), btype)) { mfem::err << "Error converting MFEM basis type to FMS for" " FMS_DISCONTINUOUS." << std::endl; return 7; } } break; } case mfem::FiniteElementCollection::TANGENTIAL: { mfem::out << "Warning, unsupported ContType (TANGENTIAL) for " << field_name << ". Using FMS_CONTINUOUS." << std::endl; break; } case mfem::FiniteElementCollection::NORMAL: { ftype = FMS_HDIV; // This RT_FECollection type seems to arise from "RT" fields such as "RT_3D_P1". // Checking fe_coll.hpp, this contains verbiage about H_DIV so we assign it the // FMS type FMS_HDIV. // I've seen RT_3D_P1 return the wrong order so get it from the name. int idim, iorder; if (sscanf(fecoll->Name(), "RT_%dD_P%d", &idim, &iorder) == 2) { order = (FmsInt)iorder; } else { order = static_cast(fespace->GetOrder(0)); } // Get the vdim from the fespace since the grid function is returning // 3 but we need it to be what was read from the file so we can pass the // right vdim to the FMS field descriptor to compute the expected number of dofs. vdim = fespace->GetVDim(); break; } default: mfem::out << "Warning, unsupported ContType for field " << field_name << ". Using FMS_CONTINUOUS." << std::endl; ftype = FMS_CONTINUOUS; break; } // Now that we're not failing, create the fd and field. FmsFieldDescriptor fd = NULL; FmsField f = NULL; FmsDataCollectionAddFieldDescriptor(dc, fd_name.c_str(), &fd); FmsDataCollectionAddField(dc, field_name.c_str(), &f); *outfield = f; /* Q: Why is order defined on a per element basis? */ FmsFieldDescriptorSetComponent(fd, comp); FmsFieldDescriptorSetFixedOrder(fd, ftype, btype, order); FmsInt ndofs; FmsFieldDescriptorGetNumDofs(fd, &ndofs); const char *name = NULL; FmsFieldGetName(f, &name); FmsLayoutType layout = fespace->GetOrdering() == mfem::Ordering::byVDIM ? FMS_BY_VDIM : FMS_BY_NODES; #ifdef DEBUG_MFEM_FMS switch (ftype) { case FMS_CONTINUOUS: mfem::out << "\tFMS_CONTINUOUS" << std::endl; break; case FMS_DISCONTINUOUS: mfem::out << "\tFMS_DISCONTINUOUS" << std::endl; break; case FMS_HDIV: mfem::out << "\tFMS_HDIV" << std::endl; break; } mfem::out << "\tField is order " << order << " with vdim " << vdim << " and nDoFs " << ndofs << std::endl; mfem::out << "\tgf->size() " << gf->Size() << " ndofs * vdim " << ndofs * vdim << std::endl; mfem::out << "\tlayout " << layout << " (0 = BY_NODES, 1 = BY_VDIM)" << std::endl; #endif if (FmsFieldSet(f, fd, vdim, layout, FMS_DOUBLE, c)) { mfem::err << "Error setting field " << field_name << " in FMS." << std::endl; return 8; } return 0; } bool MfemMetaDataToFmsMetaData(DataCollection *mdc, FmsDataCollection fdc) { if (!mdc) { return false; } if (!fdc) { return false; } int *cycle = NULL; double *time = NULL, *timestep = NULL; FmsMetaData top_level = NULL; FmsMetaData *cycle_time_timestep = NULL; int mdata_err = 0; mdata_err = FmsDataCollectionAttachMetaData(fdc, &top_level); if (!top_level || mdata_err) { mfem::err << "Failed to attach metadata to the FmsDataCollection" << std::endl; return 40; } mdata_err = FmsMetaDataSetMetaData(top_level, "MetaData", 3, &cycle_time_timestep); if (!cycle_time_timestep || mdata_err) { mfem::err << "Failed to acquire FmsMetaData array" << std::endl; return false; } if (!cycle_time_timestep[0]) { mfem::err << "The MetaData pointer for cycle is NULL" << std::endl; return false; } mdata_err = FmsMetaDataSetIntegers(cycle_time_timestep[0], "cycle", FMS_INT32, 1, (void**)&cycle); if (!cycle || mdata_err) { mfem::err << "The data pointer for cycle is NULL" << std::endl; return false; } *cycle = mdc->GetCycle(); if (!cycle_time_timestep[1]) { mfem::err << "The FmsMetaData pointer for time is NULL" << std::endl; return false; } mdata_err = FmsMetaDataSetScalars(cycle_time_timestep[1], "time", FMS_DOUBLE, 1, (void**)&time); if (!time || mdata_err) { mfem::err << "The data pointer for time is NULL." << std::endl; return false; } *time = mdc->GetTime(); if (!cycle_time_timestep[2]) { mfem::err << "The FmsMetData pointer for timestep is NULL" << std::endl; return false; } mdata_err = FmsMetaDataSetScalars(cycle_time_timestep[2], "timestep", FMS_DOUBLE, 1, (void**)×tep); if (!timestep || mdata_err) { mfem::err << "The data pointer for timestep is NULL" << std::endl; return false; } *timestep = mdc->GetTimeStep(); return true; } //--------------------------------------------------------------------------- bool FmsMetaDataGetInteger(FmsMetaData mdata, const std::string &key, std::vector &values) { if (!mdata) { return false; } bool retval = false; FmsMetaDataType type; FmsIntType int_type; FmsInt i, size; FmsMetaData *children = nullptr; const void *data = nullptr; const char *mdata_name = nullptr; if (FmsMetaDataGetType(mdata, &type) == 0) { switch (type) { case FMS_INTEGER: if (FmsMetaDataGetIntegers(mdata, &mdata_name, &int_type, &size, &data) == 0) { if (strcasecmp(key.c_str(), mdata_name) == 0) { retval = true; // Interpret the integers and store them in the std::vector switch (int_type) { case FMS_INT8: for (i = 0; i < size; i++) { values.push_back(static_cast(reinterpret_cast(data)[i])); } break; case FMS_INT16: for (i = 0; i < size; i++) { values.push_back(static_cast(reinterpret_cast(data)[i])); } break; case FMS_INT32: for (i = 0; i < size; i++) { values.push_back(static_cast(reinterpret_cast(data)[i])); } break; case FMS_INT64: for (i = 0; i < size; i++) { values.push_back(static_cast(reinterpret_cast(data)[i])); } break; case FMS_UINT8: for (i = 0; i < size; i++) { values.push_back(static_cast(reinterpret_cast(data)[i])); } break; case FMS_UINT16: for (i = 0; i < size; i++) { values.push_back(static_cast(reinterpret_cast(data)[i])); } break; case FMS_UINT32: for (i = 0; i < size; i++) { values.push_back(static_cast(reinterpret_cast(data)[i])); } break; case FMS_UINT64: for (i = 0; i < size; i++) { values.push_back(static_cast(reinterpret_cast(data)[i])); } break; default: retval = false; break; } } } break; case FMS_META_DATA: if (FmsMetaDataGetMetaData(mdata, &mdata_name, &size, &children) == 0) { // Recurse to look for the key we want. for (i = 0; i < size && !retval; i++) { retval = FmsMetaDataGetInteger(children[i], key, values); } } break; default: break; } } return retval; } //--------------------------------------------------------------------------- bool FmsMetaDataGetScalar(FmsMetaData mdata, const std::string &key, std::vector &values) { if (!mdata) { return false; } bool retval = false; FmsMetaDataType type; FmsScalarType scal_type; FmsInt i, size; FmsMetaData *children = nullptr; const void *data = nullptr; const char *mdata_name = nullptr; if (FmsMetaDataGetType(mdata, &type) == 0) { switch (type) { case FMS_SCALAR: if (FmsMetaDataGetScalars(mdata, &mdata_name, &scal_type, &size, &data) == 0) { if (strcasecmp(key.c_str(), mdata_name) == 0) { retval = true; // Interpret the integers and store them in the std::vector switch (scal_type) { case FMS_FLOAT: for (i = 0; i < size; i++) { values.push_back(static_cast(reinterpret_cast(data)[i])); } break; case FMS_DOUBLE: for (i = 0; i < size; i++) { values.push_back(reinterpret_cast(data)[i]); } break; default: retval = false; break; } } } break; case FMS_META_DATA: if (FmsMetaDataGetMetaData(mdata, &mdata_name, &size, &children) == 0) { // Recurse to look for the key we want. for (i = 0; i < size && !retval; i++) { retval = FmsMetaDataGetScalar(children[i], key, values); } } break; default: break; } } return retval; } //--------------------------------------------------------------------------- bool FmsMetaDataGetString(FmsMetaData mdata, const std::string &key, std::string &value) { if (!mdata) { return false; } bool retval = false; FmsMetaDataType type; FmsInt i, size; FmsMetaData *children = nullptr; const char *mdata_name = nullptr; const char *str_value = nullptr; if (FmsMetaDataGetType(mdata, &type) == 0) { switch (type) { case FMS_STRING: if (FmsMetaDataGetString(mdata, &mdata_name, &str_value) == 0) { if (strcasecmp(key.c_str(), mdata_name) == 0) { retval = true; value = str_value; } } break; case FMS_META_DATA: if (FmsMetaDataGetMetaData(mdata, &mdata_name, &size, &children) == 0) { // Recurse to look for the key we want. for (i = 0; i < size && !retval; i++) { retval = FmsMetaDataGetString(children[i], key, value); } } break; default: break; } } return retval; } /* -------------------------------------------------------------------------- */ /* FMS to MFEM conversion function */ /* -------------------------------------------------------------------------- */ int FmsDataCollectionToDataCollection(FmsDataCollection dc, DataCollection **mfem_dc) { int retval = 0; FmsMesh fms_mesh; FmsDataCollectionGetMesh(dc, &fms_mesh); // NOTE: The MFEM data collection has a single Mesh. Mesh has a constructor // to take multiple Mesh objects but it appears to glue them together. Mesh *mesh = nullptr; int err = FmsMeshToMesh(fms_mesh, &mesh); if (err == 0) { std::string collection_name("collection"); const char *cn = nullptr; FmsDataCollectionGetName(dc, &cn); if (cn != nullptr) { collection_name = cn; } // Make a data collection that contains the mesh. DataCollection *mdc = new DataCollection(collection_name, mesh); mdc->SetOwnData(true); // Now do fields, etc. and add them to mdc. FmsField *fields = nullptr; FmsInt num_fields = 0; if (FmsDataCollectionGetFields(dc, &fields, &num_fields) == 0) { for (FmsInt i = 0; i < num_fields; ++i) { const char *name = nullptr; FmsFieldGetName(fields[i], &name); GridFunction *gf = new GridFunction; // Get the data type. FmsFieldDescriptor f_fd; FmsLayoutType f_layout; FmsScalarType f_data_type; const void *f_data; FmsFieldGet(fields[i], &f_fd, NULL, &f_layout, &f_data_type, &f_data); // Interpret the field according to its data type. int err = 1; switch (f_data_type) { case FMS_FLOAT: err = FmsFieldToGridFunction(fms_mesh, fields[i], mesh, *gf, true); break; case FMS_DOUBLE: err = FmsFieldToGridFunction(fms_mesh, fields[i], mesh, *gf, true); break; case FMS_COMPLEX_FLOAT: case FMS_COMPLEX_DOUBLE: // Does MFEM support complex? break; default: break; } if (err == 0) { mdc->RegisterField(name, gf); } else { mfem::out << "There was an error converting " << name << " code: " << err << std::endl; delete gf; } const char *fname = NULL; FmsFieldGetName(fields[i], &fname); } } // If we have metadata in FMS, pass what we can through to MFEM. FmsMetaData mdata = NULL; FmsDataCollectionGetMetaData(dc, &mdata); if (mdata) { std::vector ivalues; std::vector dvalues; std::string svalue; if (FmsMetaDataGetInteger(mdata, "cycle", ivalues)) { if (!ivalues.empty()) { mdc->SetCycle(ivalues[0]); } } if (FmsMetaDataGetScalar(mdata, "time", dvalues)) { if (!dvalues.empty()) { mdc->SetTime(dvalues[0]); } } if (FmsMetaDataGetScalar(mdata, "timestep", dvalues)) { if (!dvalues.empty()) { mdc->SetTimeStep(dvalues[0]); } } } *mfem_dc = mdc; } else { mfem::out << "FmsDataCollectionToDataCollection: mesh failed to convert. err=" << err << endl; retval = 1; } #if DEBUG_FMS_MFEM if (*mfem_dc) { VisItDataCollection visit_dc(std::string("DEBUG_DC"), mesh); visit_dc.SetOwnData(false); const auto &fields = (*mfem_dc)->GetFieldMap(); for (const auto &field : fields) { visit_dc.RegisterField(field.first, field.second); } visit_dc.Save(); } #endif return retval; } /* -------------------------------------------------------------------------- */ /* MFEM to FMS conversion function */ /* -------------------------------------------------------------------------- */ int MeshToFmsMesh(const Mesh *mmesh, FmsMesh *fmesh, FmsComponent *volume) { if (!mmesh) { return 1; } if (!fmesh) { return 2; } if (!volume) { return 3; } int err = 0; const int num_verticies = mmesh->GetNV(); #ifdef DEBUG_MFEM_FMS const int num_edges = mmesh->GetNEdges(); #endif const int num_faces = mmesh->GetNFaces(); const int num_elements = mmesh->GetNE(); #ifdef DEBUG_MFEM_FMS mfem::out << "nverts: " << num_verticies << std::endl; mfem::out << "nedges: " << num_edges << std::endl; mfem::out << "nfaces: " << num_faces << std::endl; mfem::out << "nele: " << num_elements << std::endl; #endif FmsMeshConstruct(fmesh); FmsMeshSetPartitionId(*fmesh, 0, 1); FmsDomain *domains = NULL; FmsMeshAddDomains(*fmesh, "Domain", 1, &domains); FmsDomainSetNumVertices(domains[0], num_verticies); const int edge_reorder[2] = {1, 0}; const int quad_reorder[4] = {0,1,2,3}; const int tet_reorder[4] = {3,2,1,0}; const int hex_reorder[6] = {0,5,1,3,4,2}; const int *reorder[8] = {NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL}; reorder[FMS_EDGE] = edge_reorder; reorder[FMS_QUADRILATERAL] = quad_reorder; reorder[FMS_TETRAHEDRON] = tet_reorder; reorder[FMS_HEXAHEDRON] = hex_reorder; const mfem::Table *edges = mmesh->GetEdgeVertexTable(); if (!edges) { mfem::err << "Error, mesh has no edges." << std::endl; return 1; } mfem::Table *faces = mmesh->GetFaceEdgeTable(); if (!faces && num_faces > 0) { mfem::err << "Error, mesh contains faces but the \"GetFaceEdgeTable\" returned NULL." << std::endl; return 2; } // Build edges std::vector edge_verts(edges->Size() * 2); for (int i = 0; i < edges->Size(); i++) { mfem::Array nids; edges->GetRow(i, nids); for (int j = 0; j < 2; j++) { edge_verts[i*2 + j] = nids[j]; } } // TODO: Move this code to after the for loop so edges can be added at top level entities FmsDomainSetNumEntities(domains[0], FMS_EDGE, FMS_INT32, edge_verts.size() / 2); FmsDomainAddEntities(domains[0], FMS_EDGE, reorder, FMS_INT32, edge_verts.data(), edge_verts.size() / 2); #ifdef DEBUG_MFEM_FMS mfem::out << "EDGES: "; for (int i = 0; i < edge_verts.size(); i++) { if (i % 2 == 0) { mfem::out << std::endl << "\t" << i/2 << ": "; } mfem::out << edge_verts[i] << " "; } mfem::out << std::endl; #endif // Build faces if (faces) { // TODO: Support Triangles and Quads, and move this code after the for // loop so these can be added as top level entities int rowsize = faces->RowSize(0); std::vector face_edges(faces->Size() * rowsize); for (int i = 0; i < faces->Size(); i++) { mfem::Array eids; faces->GetRow(i, eids); for (int j = 0; j < rowsize; j++) { face_edges[i*rowsize + j] = eids[j]; } } FmsEntityType ent_type = (rowsize == 3) ? FMS_TRIANGLE : FMS_QUADRILATERAL; FmsDomainSetNumEntities(domains[0], ent_type, FMS_INT32, face_edges.size() / rowsize); FmsDomainAddEntities(domains[0], ent_type, NULL, FMS_INT32, face_edges.data(), face_edges.size() / rowsize); #ifdef DEBUG_MFEM_FMS mfem::out << "FACES: "; for (int i = 0; i < face_edges.size(); i++) { if (i % rowsize == 0) { mfem::out << std::endl << "\t" << i/rowsize << ": "; } mfem::out << "(" << edge_verts[face_edges[i]*2] << ", " << edge_verts[face_edges[i]*2+1] << ") "; } mfem::out << std::endl; #endif } // Add top level elements std::vector tags; std::vector tris; std::vector quads; std::vector tets; std::vector hexes; for (int i = 0; i < num_elements; i++) { auto etype = mmesh->GetElementType(i); tags.push_back(mmesh->GetAttribute(i)); switch (etype) { case mfem::Element::POINT: { // TODO: ? break; } case mfem::Element::SEGMENT: { // TODO: ? break; } case mfem::Element::TRIANGLE: { mfem::Array eids, oris; mmesh->GetElementEdges(i, eids, oris); for (int e = 0; e < 3; e++) { tris.push_back(eids[e]); } break; } case mfem::Element::QUADRILATERAL: { mfem::Array eids, oris; mmesh->GetElementEdges(i, eids, oris); for (int e = 0; e < 4; e++) { quads.push_back(eids[e]); } break; } case mfem::Element::TETRAHEDRON: { mfem::Array fids, oris; mmesh->GetElementFaces(i, fids, oris); for (int f = 0; f < 4; f++) { tets.push_back(fids[f]); } break; } case mfem::Element::HEXAHEDRON: { mfem::Array fids, oris; mmesh->GetElementFaces(i, fids, oris); for (int f = 0; f < 6; f++) { hexes.push_back(fids[f]); } break; } default: mfem::err << "Error, element not implemented." << std::endl; return 3; } } if (tris.size()) { FmsDomainSetNumEntities(domains[0], FMS_TRIANGLE, FMS_INT32, tris.size() / 3); FmsDomainAddEntities(domains[0], FMS_TRIANGLE, reorder, FMS_INT32, tris.data(), tris.size() / 3); #ifdef DEBUG_MFEM_FMS mfem::out << "TRIS: "; for (int i = 0; i < tris.size(); i++) { if (i % 3 == 0) { mfem::out << std::endl << "\t" << i/3 << ": "; } mfem::out << tris[i] << " "; } mfem::out << std::endl; #endif } if (quads.size()) { // TODO: Not quite right either, if there are hexes and quads then this // will overwrite the faces that made up the hexes FmsDomainSetNumEntities(domains[0], FMS_QUADRILATERAL, FMS_INT32, quads.size() / 4); FmsDomainAddEntities(domains[0], FMS_QUADRILATERAL, reorder, FMS_INT32, quads.data(), quads.size() / 4); #ifdef DEBUG_MFEM_FMS mfem::out << "QUADS: "; for (int i = 0; i < quads.size(); i++) { if (i % 4 == 0) { mfem::out << std::endl << "\t" << i/4 << ": "; } mfem::out << quads[i] << " "; } mfem::out << std::endl; #endif } if (tets.size()) { FmsDomainSetNumEntities(domains[0], FMS_TETRAHEDRON, FMS_INT32, tets.size() / 4); FmsDomainAddEntities(domains[0], FMS_TETRAHEDRON, reorder, FMS_INT32, tets.data(), tets.size() / 4); #ifdef DEBUG_MFEM_FMS mfem::out << "TETS: "; for (int i = 0; i < tets.size(); i++) { if (i % 4 == 0) { mfem::out << std::endl << "\t" << i/4 << ": "; } mfem::out << tets[i] << " "; } mfem::out << std::endl; #endif } if (hexes.size()) { FmsDomainSetNumEntities(domains[0], FMS_HEXAHEDRON, FMS_INT32, hexes.size() / 6); FmsDomainAddEntities(domains[0], FMS_HEXAHEDRON, reorder, FMS_INT32, hexes.data(), hexes.size() / 6); #ifdef DEBUG_MFEM_FMS mfem::out << "HEXES: "; for (int i = 0; i < hexes.size(); i++) { if (i % 6 == 0) { mfem::out << std::endl << "\t" << i/6 << ": "; } mfem::out << hexes[i] << " "; } mfem::out << std::endl; #endif } err = FmsMeshFinalize(*fmesh); if (err) { mfem::err << "FmsMeshFinalize returned error code " << err << std::endl; return 4; } err = FmsMeshValidate(*fmesh); if (err) { mfem::err << "FmsMeshValidate returned error code " << err << std::endl; return 5; } FmsComponent v = NULL; FmsMeshAddComponent(*fmesh, "volume", &v); FmsComponentAddDomain(v, domains[0]); FmsTag tag; FmsMeshAddTag(*fmesh, "element_attribute", &tag); FmsTagSetComponent(tag, v); FmsTagSet(tag, FMS_INT32, FMS_INT32, tags.data(), tags.size()); // Add boundary component std::vector bdr_eles[FMS_NUM_ENTITY_TYPES]; std::vector bdr_attributes; const int NBE = mmesh->GetNBE(); for (int i = 0; i < NBE; i++) { const Element::Type betype = mmesh->GetBdrElementType(i); bdr_attributes.push_back(mmesh->GetBdrAttribute(i)); switch (betype) { case Element::POINT: bdr_eles[FMS_VERTEX].push_back(mmesh->GetBdrElementEdgeIndex(i)); break; case Element::SEGMENT: bdr_eles[FMS_EDGE].push_back(mmesh->GetBdrElementEdgeIndex(i)); break; case Element::TRIANGLE: bdr_eles[FMS_TRIANGLE].push_back(mmesh->GetBdrElementEdgeIndex(i)); break; case Element::QUADRILATERAL: bdr_eles[FMS_QUADRILATERAL].push_back(mmesh->GetBdrElementEdgeIndex(i)); break; case Element::TETRAHEDRON: bdr_eles[FMS_TETRAHEDRON].push_back(mmesh->GetBdrElementEdgeIndex(i)); break; case Element::HEXAHEDRON: bdr_eles[FMS_HEXAHEDRON].push_back(mmesh->GetBdrElementEdgeIndex(i)); break; default: MFEM_WARNING("Unsupported boundary element " << betype << " at boundary index " << i); break; } } if (NBE) { FmsComponent boundary = NULL; FmsMeshAddComponent(*fmesh, "boundary", &boundary); FmsInt part_id; FmsComponentAddPart(boundary, domains[0], &part_id); for (int i = FMS_NUM_ENTITY_TYPES - 1; i > 0; i--) { if (bdr_eles[i].size()) { FmsComponentAddPartEntities(boundary, part_id, (FmsEntityType)i, FMS_INT32, FMS_INT32, FMS_INT32, NULL, bdr_eles[i].data(), NULL, bdr_eles[i].size()); break; } } FmsComponentAddRelation(v, 1); FmsTag boundary_tag = NULL; FmsMeshAddTag(*fmesh, "boundary_attribute", &boundary_tag); FmsTagSetComponent(boundary_tag, boundary); FmsTagSet(boundary_tag, FMS_INT32, FMS_INT32, bdr_attributes.data(), bdr_attributes.size()); } *volume = v; return 0; } int DataCollectionToFmsDataCollection(DataCollection *mfem_dc, FmsDataCollection *dc) { // TODO: Write me int err = 0; const Mesh *mmesh = mfem_dc->GetMesh(); FmsMesh fmesh = NULL; FmsComponent volume = NULL; err = MeshToFmsMesh(mmesh, &fmesh, &volume); if (!fmesh || !volume || err) { mfem::err << "Error converting mesh topology from MFEM to FMS" << std::endl; if (fmesh) { FmsMeshDestroy(&fmesh); } return 1; } err = FmsDataCollectionCreate(fmesh, mfem_dc->GetCollectionName().c_str(), dc); if (!*dc || err) { mfem::err << "There was an error creating the FMS data collection." << std::endl; FmsMeshDestroy(&fmesh); if (*dc) { FmsDataCollectionDestroy(dc); } return 3; } // Add the coordinates field to the data collection const mfem::GridFunction *mcoords = mmesh->GetNodes(); if (mcoords) { FmsField fcoords = NULL; err = GridFunctionToFmsField(*dc, volume, "CoordsDescriptor", "Coords", mmesh, mcoords, &fcoords); err |= FmsComponentSetCoordinates(volume, fcoords); } else { // Sometimes the nodes are stored as just a vector of vertex coordinates mfem::Vector mverts; mmesh->GetVertices(mverts); FmsFieldDescriptor fdcoords = NULL; FmsField fcoords = NULL; err = FmsDataCollectionAddFieldDescriptor(*dc, "CoordsDescriptor", &fdcoords); err |= FmsFieldDescriptorSetComponent(fdcoords, volume); err |= FmsFieldDescriptorSetFixedOrder(fdcoords, FMS_CONTINUOUS, FMS_NODAL_GAUSS_CLOSED, 1); err |= FmsDataCollectionAddField(*dc, "Coords", &fcoords); err |= FmsFieldSet(fcoords, fdcoords, mmesh->SpaceDimension(), FMS_BY_NODES, FMS_DOUBLE, mverts.HostRead()); err |= FmsComponentSetCoordinates(volume, fcoords); } if (err) { mfem::err << "There was an error setting the mesh coordinates." << std::endl; FmsMeshDestroy(&fmesh); FmsDataCollectionDestroy(dc); return 4; } const auto &fields = mfem_dc->GetFieldMap(); for (const auto &pair : fields) { std::string fd_name(pair.first + "Descriptor"); FmsField field; err = GridFunctionToFmsField(*dc, volume, fd_name, pair.first.c_str(), mmesh, pair.second, &field); if (err) { mfem::err << "WARNING: There was an error adding the " << pair.first << " field. Continuing..." << std::endl; } } // /* TODO: // const auto &qfields = mfem_dc->GetQFieldMap(); // for(const auto &pair : qfields) { // FmsFieldDescriptor fd = NULL; // FmsField f = NULL; // std::string fd_name(pair.first + "Collection"); // FmsDataCollectionAddFieldDescriptor(*dc, fd_name.c_str(), &fd); // FmsDataCollectionAddField(*dc, pair.first.c_str(), &f); // GridFunctionToFmsField(*dc, fd, f, volume, pair.second); // TODO: Volume isn't always going to be correct // } */ MfemMetaDataToFmsMetaData(mfem_dc, *dc); return 0; } } // namespace mfem #endif