From 10aec367483ebb7f36331fe595133ef31b5a3783 Mon Sep 17 00:00:00 2001 From: John Peterson Date: Wed, 15 Oct 2025 14:19:44 -0500 Subject: [PATCH 01/46] Add ExodusII_IO API, boolean controlling whether elem_num_map and node_num_map are used to set unique_ids --- include/mesh/exodusII_io.h | 25 +++++++++++++++++++++++++ src/mesh/exodusII_io.C | 5 ++++- 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/include/mesh/exodusII_io.h b/include/mesh/exodusII_io.h index 5296c9bd393..2f22f9ee4b6 100644 --- a/include/mesh/exodusII_io.h +++ b/include/mesh/exodusII_io.h @@ -140,6 +140,23 @@ class ExodusII_IO : public MeshInput, */ void write_complex_magnitude (bool val); + /** + * If true this flag enforces the following behaviors: + * + * .) When reading an Exodus file, instead of setting the + * elem_num_map and node_num_map ids directly on Nodes and Elems + * read in from the Exodus file, set their unique_ids + * instead. Normally libmesh chooses the unique_ids automatically + * but in this case we override that choice. As a consequence + * the libMesh Elems/Nodes will be numbered based on their + * implied ordering in the exo file (sequentially by block and + * without any gaps in the numbering). + * .) When writing an Exodus file, populate the elem_num_map and + * node_num_map with the unique_ids of the Elems/Nodes being + * written. + */ + void set_unique_ids_from_maps (bool val); + /** * By default, we only write out the elements physically stored in * the mesh. If we have any SIDE_DISCONTINUOUS variables, however, @@ -670,6 +687,14 @@ class ExodusII_IO : public MeshInput, */ bool _write_complex_abs; + /** + * Set Elem/Node unique_ids based on the elem_num_map and node_num_map + * contents during reading, populate those maps with unique_ids during + * writing. + * See also: docs for set_unique_ids_from_maps(bool) function. + */ + bool _set_unique_ids_from_maps; + /** * Set to true (false is the default) to generate independent nodes * for every Bezier Extraction element. diff --git a/src/mesh/exodusII_io.C b/src/mesh/exodusII_io.C index b62db85cb87..e99ae251b53 100644 --- a/src/mesh/exodusII_io.C +++ b/src/mesh/exodusII_io.C @@ -971,7 +971,10 @@ void ExodusII_IO::write_complex_magnitude (bool val) _write_complex_abs = val; } - +void ExodusII_IO::set_unique_ids_from_maps (bool val) +{ + _set_unique_ids_from_maps = val; +} void ExodusII_IO::use_mesh_dimension_instead_of_spatial_dimension(bool val) { From e97c61b82df13d949e2b45cf51025f07ac6d27b5 Mon Sep 17 00:00:00 2001 From: John Peterson Date: Sun, 19 Oct 2025 16:32:20 -0500 Subject: [PATCH 02/46] Add code to set Node's unique_id manually based on node_num_map --- src/mesh/exodusII_io.C | 49 +++++++++++++++++++++++++++++++++++++----- 1 file changed, 44 insertions(+), 5 deletions(-) diff --git a/src/mesh/exodusII_io.C b/src/mesh/exodusII_io.C index e99ae251b53..b57bfef48c5 100644 --- a/src/mesh/exodusII_io.C +++ b/src/mesh/exodusII_io.C @@ -330,18 +330,57 @@ void ExodusII_IO::read (const std::string & fname) // Use the node_num_map to get the correct ID for Exodus int exodus_id = exio_helper->node_num_map[i]; + // Exodus ids are always 1-based while libmesh ids are always + // 0-based, so here we subtract 1 from the value given in the + // node_num_map to make it 0-based. + auto exodus_id_zero_based = + cast_int(exodus_id - 1); + + // Determine what id() the Node will have. If the user has set + // the _set_unique_ids_from_maps flag to true, then this Node + // will simply be assigned an id of "i" as it is added, + // otherwise we will immediately assign the Node a (zero-based) + // id() based on the value in the node_num_map. + dof_id_type libmesh_id = + _set_unique_ids_from_maps ? + cast_int(i) : + exodus_id_zero_based; + // Catch the node that was added to the mesh - Node * added_node = mesh.add_point (Point(exio_helper->x[i], exio_helper->y[i], exio_helper->z[i]), exodus_id-1); + Node * added_node = mesh.add_point (Point(exio_helper->x[i], exio_helper->y[i], exio_helper->z[i]), libmesh_id); - // If the Mesh assigned an ID different from what is in the - // Exodus file, we should probably error. - libmesh_error_msg_if(added_node->id() != static_cast(exodus_id-1), + // Sanity check: throw an error if the Mesh assigned an ID to + // the Node which does not match the libmesh_id we just determined. + libmesh_error_msg_if(added_node->id() != static_cast(libmesh_id), "Error! Mesh assigned node ID " << added_node->id() << " which is different from the (zero-based) Exodus ID " - << exodus_id-1 + << libmesh_id << "!"); + // If the _set_unique_ids_from_maps flag is true, then set the + // unique_id for "added_node" based on the (zero-based version of) + // node_num_map[i] value. + if (_set_unique_ids_from_maps) + { + added_node->set_unique_id(cast_int(exodus_id_zero_based)); + + // Normally the Mesh is responsible for setting the unique_ids + // of Nodes in a consistent manner, so when we set the value + // of a Node manually based on the node_num_map, we need to + // make sure that the "next" unique id assigned by the Mesh + // will still be valid. We do this by making sure that the + // next_unique_id is greater than the one we set manually. The + // APIs for doing this are only defined when unique ids are + // enabled. + // Note: it's not generally safe to call mesh.parallel_max_unique_id() + // here because the Exodus reader might only be called on one processor. +#ifdef LIBMESH_ENABLE_UNIQUE_ID + unique_id_type next_unique_id = mesh.next_unique_id(); + mesh.set_next_unique_id(std::max(next_unique_id, exodus_id_zero_based + 1)); +#endif + } + // If we have a set of spline weights, these nodes are going to // be used as control points for Bezier elements, and we need // to attach a NodeElem to each to make sure it doesn't get From f952f7e21d326fd982f166fc02aba5778bf3b116 Mon Sep 17 00:00:00 2001 From: John Peterson Date: Mon, 20 Oct 2025 11:24:09 -0500 Subject: [PATCH 03/46] Handle _set_unique_ids_from_maps flag while reading in elements --- src/mesh/exodusII_io.C | 29 +++++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/src/mesh/exodusII_io.C b/src/mesh/exodusII_io.C index b57bfef48c5..e92b09eb530 100644 --- a/src/mesh/exodusII_io.C +++ b/src/mesh/exodusII_io.C @@ -568,16 +568,29 @@ void ExodusII_IO::read (const std::string & fname) for (int k=0; knum_nodes_per_elem; k++) { // global index - int gi = (elem_num)*exio_helper->num_nodes_per_elem + conv.get_node_map(k); + int gi = elem_num * exio_helper->num_nodes_per_elem + conv.get_node_map(k); + + // Look up the value in the connectivity array at this global index + auto conn_gi = exio_helper->connect[gi]; // The entries in 'connect' are actually (1-based) - // indices into the node_num_map, so to get the right - // node ID we: - // 1.) Subtract 1 from connect[gi] - // 2.) Pass it through node_num_map to get the corresponding Exodus ID - // 3.) Subtract 1 from that, since libmesh node numbering is "zero"-based, - // even when the Exodus node numbering doesn't start with 1. - int libmesh_node_id = exio_helper->node_num_map[exio_helper->connect[gi] - 1] - 1; + // indices into the node_num_map, so in order to use + // conn_gi as an index in C++, we need to first make + // it zero-based. + auto conn_gi_zero_based = + cast_int(conn_gi - 1); + + // If the user set the flag which stores Exodus node + // ids as unique_ids instead of regular ids, then + // the libmesh node id we are looking for is + // actually just "conn_gi_zero_based". Otherwise, we + // need to look up the Node's id in the node_num_map, + // *and* then subtract 1 from that because the entries + // in the node_num_map are also 1-based. + dof_id_type libmesh_node_id = + _set_unique_ids_from_maps ? + cast_int(conn_gi_zero_based) : + cast_int(exio_helper->node_num_map[conn_gi_zero_based] - 1); // Set the node pointer in the Elem elem->set_node(k, mesh.node_ptr(libmesh_node_id)); From a98c32730a385d2fe2318efc3f597a4d67da757e Mon Sep 17 00:00:00 2001 From: John Peterson Date: Mon, 20 Oct 2025 11:45:14 -0500 Subject: [PATCH 04/46] And do the same node indexing steps for Bezier extraction data --- src/mesh/exodusII_io.C | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/src/mesh/exodusII_io.C b/src/mesh/exodusII_io.C index e92b09eb530..88048f30ee0 100644 --- a/src/mesh/exodusII_io.C +++ b/src/mesh/exodusII_io.C @@ -661,10 +661,29 @@ void ExodusII_IO::read (const std::string & fname) libmesh_vector_at(coef_vec, elem_node_index); // Get the libMesh node corresponding to that row - const int gi = (elem_num)*exio_helper->bex_num_elem_cvs + - spline_node_index; + const int gi = elem_num * exio_helper->bex_num_elem_cvs + spline_node_index; + + // Look up the value in the connectivity array at this global index + auto conn_gi = exio_helper->connect[gi]; + + // The entries in 'connect' are actually (1-based) + // indices into the node_num_map, so in order to use + // conn_gi as an index in C++, we need to first make + // it zero-based. + auto conn_gi_zero_based = + cast_int(conn_gi - 1); + + // If the user set the flag which stores Exodus node + // ids as unique_ids instead of regular ids, then + // the libmesh node id we are looking for is + // actually just "conn_gi_zero_based". Otherwise, we + // need to look up the Node's id in the node_num_map, + // *and* then subtract 1 from that because the entries + // in the node_num_map are also 1-based. const dof_id_type libmesh_node_id = - exio_helper->node_num_map[exio_helper->connect[gi] - 1] - 1; + _set_unique_ids_from_maps ? + cast_int(conn_gi_zero_based) : + cast_int(exio_helper->node_num_map[conn_gi_zero_based] - 1); if (coef != 0) // Ignore irrelevant spline nodes key.emplace_back(libmesh_node_id, coef); From 12c5b6d139c7797123e1483ca7415ab96be95613 Mon Sep 17 00:00:00 2001 From: John Peterson Date: Mon, 20 Oct 2025 14:40:20 -0500 Subject: [PATCH 05/46] Add lambda which determines the libmesh_node id for the current Elem and local Node --- src/mesh/exodusII_io.C | 54 +++++++++++++++++++++++++----------------- 1 file changed, 32 insertions(+), 22 deletions(-) diff --git a/src/mesh/exodusII_io.C b/src/mesh/exodusII_io.C index 88048f30ee0..798bd8b3915 100644 --- a/src/mesh/exodusII_io.C +++ b/src/mesh/exodusII_io.C @@ -567,30 +567,40 @@ void ExodusII_IO::read (const std::string & fname) { for (int k=0; knum_nodes_per_elem; k++) { - // global index + // Get index into this block's connectivity array int gi = elem_num * exio_helper->num_nodes_per_elem + conv.get_node_map(k); - // Look up the value in the connectivity array at this global index - auto conn_gi = exio_helper->connect[gi]; - - // The entries in 'connect' are actually (1-based) - // indices into the node_num_map, so in order to use - // conn_gi as an index in C++, we need to first make - // it zero-based. - auto conn_gi_zero_based = - cast_int(conn_gi - 1); - - // If the user set the flag which stores Exodus node - // ids as unique_ids instead of regular ids, then - // the libmesh node id we are looking for is - // actually just "conn_gi_zero_based". Otherwise, we - // need to look up the Node's id in the node_num_map, - // *and* then subtract 1 from that because the entries - // in the node_num_map are also 1-based. - dof_id_type libmesh_node_id = - _set_unique_ids_from_maps ? - cast_int(conn_gi_zero_based) : - cast_int(exio_helper->node_num_map[conn_gi_zero_based] - 1); + // Helper function that takes an index into the + // nodal connectivity array for the current block + // and determines the corresponding libMesh Node id. + auto get_libmesh_node_id = [this](int gi) -> dof_id_type + { + // Look up the value in the connectivity array at this global index + auto conn_gi = exio_helper->connect[gi]; + + // The entries in 'connect' are actually (1-based) + // indices into the node_num_map, so in order to use + // conn_gi as an index in C++, we need to first make + // it zero-based. + auto conn_gi_zero_based = + cast_int(conn_gi - 1); + + // If the user set the flag which stores Exodus node + // ids as unique_ids instead of regular ids, then + // the libmesh node id we are looking for is + // actually just "conn_gi_zero_based". Otherwise, we + // need to look up the Node's id in the node_num_map, + // *and* then subtract 1 from that because the entries + // in the node_num_map are also 1-based. + dof_id_type libmesh_node_id = + _set_unique_ids_from_maps ? + cast_int(conn_gi_zero_based) : + cast_int(exio_helper->node_num_map[conn_gi_zero_based] - 1); + + return libmesh_node_id; + }; + + auto libmesh_node_id = get_libmesh_node_id(gi); // Set the node pointer in the Elem elem->set_node(k, mesh.node_ptr(libmesh_node_id)); From f4af06bd3f1e50da729ad2e7904cc7c095588449 Mon Sep 17 00:00:00 2001 From: John Peterson Date: Mon, 20 Oct 2025 14:44:26 -0500 Subject: [PATCH 06/46] Move lambda outside of elem block loop --- src/mesh/exodusII_io.C | 63 ++++++++++++++++++++++-------------------- 1 file changed, 33 insertions(+), 30 deletions(-) diff --git a/src/mesh/exodusII_io.C b/src/mesh/exodusII_io.C index 798bd8b3915..b50a2128be4 100644 --- a/src/mesh/exodusII_io.C +++ b/src/mesh/exodusII_io.C @@ -448,6 +448,38 @@ void ExodusII_IO::read (const std::string & fname) // ExodusII_IO::write()!) that don't support sparse ids. dof_id_type n_nodes = mesh.n_nodes(); + // Helper function that takes an index into the nodal connectivity + // array for the current block and determines the corresponding + // libMesh Node id. Takes into account whether the user has chosen + // to set the Node unique ids based on the node_num_map or to let + // libMesh set them. + auto get_libmesh_node_id = [this](int gi) -> dof_id_type + { + // Look up the value in the connectivity array at this global index + auto conn_gi = exio_helper->connect[gi]; + + // The entries in 'connect' are actually (1-based) + // indices into the node_num_map, so in order to use + // conn_gi as an index in C++, we need to first make + // it zero-based. + auto conn_gi_zero_based = + cast_int(conn_gi - 1); + + // If the user set the flag which stores Exodus node + // ids as unique_ids instead of regular ids, then + // the libmesh node id we are looking for is + // actually just "conn_gi_zero_based". Otherwise, we + // need to look up the Node's id in the node_num_map, + // *and* then subtract 1 from that because the entries + // in the node_num_map are also 1-based. + dof_id_type libmesh_node_id = + _set_unique_ids_from_maps ? + cast_int(conn_gi_zero_based) : + cast_int(exio_helper->node_num_map[conn_gi_zero_based] - 1); + + return libmesh_node_id; + }; + // Loop over all the element blocks for (int i=0; inum_elem_blk; i++) { @@ -570,36 +602,7 @@ void ExodusII_IO::read (const std::string & fname) // Get index into this block's connectivity array int gi = elem_num * exio_helper->num_nodes_per_elem + conv.get_node_map(k); - // Helper function that takes an index into the - // nodal connectivity array for the current block - // and determines the corresponding libMesh Node id. - auto get_libmesh_node_id = [this](int gi) -> dof_id_type - { - // Look up the value in the connectivity array at this global index - auto conn_gi = exio_helper->connect[gi]; - - // The entries in 'connect' are actually (1-based) - // indices into the node_num_map, so in order to use - // conn_gi as an index in C++, we need to first make - // it zero-based. - auto conn_gi_zero_based = - cast_int(conn_gi - 1); - - // If the user set the flag which stores Exodus node - // ids as unique_ids instead of regular ids, then - // the libmesh node id we are looking for is - // actually just "conn_gi_zero_based". Otherwise, we - // need to look up the Node's id in the node_num_map, - // *and* then subtract 1 from that because the entries - // in the node_num_map are also 1-based. - dof_id_type libmesh_node_id = - _set_unique_ids_from_maps ? - cast_int(conn_gi_zero_based) : - cast_int(exio_helper->node_num_map[conn_gi_zero_based] - 1); - - return libmesh_node_id; - }; - + // Convert this index a libMesh Node id auto libmesh_node_id = get_libmesh_node_id(gi); // Set the node pointer in the Elem From e9b7a0af070d38c0ba35787a8fc5b77a0aa33f8f Mon Sep 17 00:00:00 2001 From: John Peterson Date: Mon, 20 Oct 2025 14:46:11 -0500 Subject: [PATCH 07/46] Use helper lambda for Bezier extraction data as well --- src/mesh/exodusII_io.C | 25 +++---------------------- 1 file changed, 3 insertions(+), 22 deletions(-) diff --git a/src/mesh/exodusII_io.C b/src/mesh/exodusII_io.C index b50a2128be4..0caaa677090 100644 --- a/src/mesh/exodusII_io.C +++ b/src/mesh/exodusII_io.C @@ -602,7 +602,7 @@ void ExodusII_IO::read (const std::string & fname) // Get index into this block's connectivity array int gi = elem_num * exio_helper->num_nodes_per_elem + conv.get_node_map(k); - // Convert this index a libMesh Node id + // Convert this index to a libMesh Node id auto libmesh_node_id = get_libmesh_node_id(gi); // Set the node pointer in the Elem @@ -676,27 +676,8 @@ void ExodusII_IO::read (const std::string & fname) // Get the libMesh node corresponding to that row const int gi = elem_num * exio_helper->bex_num_elem_cvs + spline_node_index; - // Look up the value in the connectivity array at this global index - auto conn_gi = exio_helper->connect[gi]; - - // The entries in 'connect' are actually (1-based) - // indices into the node_num_map, so in order to use - // conn_gi as an index in C++, we need to first make - // it zero-based. - auto conn_gi_zero_based = - cast_int(conn_gi - 1); - - // If the user set the flag which stores Exodus node - // ids as unique_ids instead of regular ids, then - // the libmesh node id we are looking for is - // actually just "conn_gi_zero_based". Otherwise, we - // need to look up the Node's id in the node_num_map, - // *and* then subtract 1 from that because the entries - // in the node_num_map are also 1-based. - const dof_id_type libmesh_node_id = - _set_unique_ids_from_maps ? - cast_int(conn_gi_zero_based) : - cast_int(exio_helper->node_num_map[conn_gi_zero_based] - 1); + // Convert this index to a libMesh Node id + auto libmesh_node_id = get_libmesh_node_id(gi); if (coef != 0) // Ignore irrelevant spline nodes key.emplace_back(libmesh_node_id, coef); From 98cfd03fcd01cfb4753e868a44a94ad211697238 Mon Sep 17 00:00:00 2001 From: John Peterson Date: Mon, 20 Oct 2025 15:50:31 -0500 Subject: [PATCH 08/46] Also store/set _set_unique_ids_from_maps flag on the ExodusII_IO_Helper Fix missing initialization of this flag in ExodusII_IO. --- include/mesh/exodusII_io_helper.h | 4 ++++ src/mesh/exodusII_io.C | 7 +++++++ src/mesh/exodusII_io_helper.C | 1 + 3 files changed, 12 insertions(+) diff --git a/include/mesh/exodusII_io_helper.h b/include/mesh/exodusII_io_helper.h index 8a74d34506a..97ba2d915e8 100644 --- a/include/mesh/exodusII_io_helper.h +++ b/include/mesh/exodusII_io_helper.h @@ -850,6 +850,10 @@ class ExodusII_IO_Helper : public ParallelObject // On/Off message flag bool verbose; + // Same as the ExodusII_IO flag by the same name. This flag is + // also set whenever ExodusII_IO::set_unique_ids_from_maps() is called. + bool set_unique_ids_from_maps; + // This flag gets set after the Exodus file has been successfully opened for writing. // Both the create() and open() (if called with EX_WRITE) functions may set this flag. bool opened_for_writing; diff --git a/src/mesh/exodusII_io.C b/src/mesh/exodusII_io.C index 0caaa677090..87fe66ac630 100644 --- a/src/mesh/exodusII_io.C +++ b/src/mesh/exodusII_io.C @@ -134,6 +134,7 @@ ExodusII_IO::ExodusII_IO (MeshBase & mesh, #endif _allow_empty_variables(false), _write_complex_abs(true), + _set_unique_ids_from_maps(false), _disc_bex(false) { // if !LIBMESH_HAVE_EXODUS_API, we didn't use this @@ -157,6 +158,7 @@ ExodusII_IO::ExodusII_IO (const MeshBase & mesh, #endif _allow_empty_variables(false), _write_complex_abs(true), + _set_unique_ids_from_maps(false), _disc_bex(false) { // if !LIBMESH_HAVE_EXODUS_API, we didn't use this @@ -1039,6 +1041,11 @@ void ExodusII_IO::write_complex_magnitude (bool val) void ExodusII_IO::set_unique_ids_from_maps (bool val) { _set_unique_ids_from_maps = val; + + // Set this flag on the helper object as well. The helper needs to know about this + // flag, since it sometimes needs to construct libmesh Node ids from nodal connectivity + // arrays (see e.g. ExodusII_IO_Helper::read_edge_blocks()). + exio_helper->set_unique_ids_from_maps = val; } void ExodusII_IO::use_mesh_dimension_instead_of_spatial_dimension(bool val) diff --git a/src/mesh/exodusII_io_helper.C b/src/mesh/exodusII_io_helper.C index 9f3b6078c82..b42736aa860 100644 --- a/src/mesh/exodusII_io_helper.C +++ b/src/mesh/exodusII_io_helper.C @@ -286,6 +286,7 @@ ExodusII_IO_Helper::ExodusII_IO_Helper(const ParallelObject & parent, num_nodal_vars(0), num_elem_vars(0), verbose(v), + set_unique_ids_from_maps(false), opened_for_writing(false), opened_for_reading(false), _run_only_on_proc0(run_only_on_proc0), From dfed7de9cf01ad62b64f4197f2d576bb6c793aef Mon Sep 17 00:00:00 2001 From: John Peterson Date: Mon, 20 Oct 2025 16:18:27 -0500 Subject: [PATCH 09/46] Update libmesh_node_id lookup in ExodusII_IO_Helper::read_edge_blocks() to support _set_unique_ids_from_maps flag --- src/mesh/exodusII_io_helper.C | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/mesh/exodusII_io_helper.C b/src/mesh/exodusII_io_helper.C index b42736aa860..8f3d20835c8 100644 --- a/src/mesh/exodusII_io_helper.C +++ b/src/mesh/exodusII_io_helper.C @@ -1416,7 +1416,18 @@ void ExodusII_IO_Helper::read_edge_blocks(MeshBase & mesh) { int exodus_node_id = connect[i+n]; int exodus_node_id_zero_based = exodus_node_id - 1; - int libmesh_node_id = node_num_map[exodus_node_id_zero_based] - 1; + + // If the user set the _set_unique_ids_from_maps + // flag, then the libmesh node id we are looking for + // is actually just "exodus_node_id_zero_based". + // Otherwise, we need to look up the Node's id in + // the node_num_map, *and* then subtract 1 from that + // because the entries in the node_num_map are also + // 1-based. + dof_id_type libmesh_node_id = + set_unique_ids_from_maps ? + exodus_node_id_zero_based : + cast_int(node_num_map[exodus_node_id_zero_based] - 1); edge->set_node(n, mesh.node_ptr(libmesh_node_id)); } From 4e68688509785d53659b8dde710f993a9c398b55 Mon Sep 17 00:00:00 2001 From: John Peterson Date: Mon, 20 Oct 2025 16:23:31 -0500 Subject: [PATCH 10/46] Use same variable naming, casts when determining libmesh_node_id --- src/mesh/exodusII_io.C | 14 +++++++------- src/mesh/exodusII_io_helper.C | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/mesh/exodusII_io.C b/src/mesh/exodusII_io.C index 87fe66ac630..a2110b62a4c 100644 --- a/src/mesh/exodusII_io.C +++ b/src/mesh/exodusII_io.C @@ -458,26 +458,26 @@ void ExodusII_IO::read (const std::string & fname) auto get_libmesh_node_id = [this](int gi) -> dof_id_type { // Look up the value in the connectivity array at this global index - auto conn_gi = exio_helper->connect[gi]; + auto exodus_node_id = exio_helper->connect[gi]; // The entries in 'connect' are actually (1-based) // indices into the node_num_map, so in order to use - // conn_gi as an index in C++, we need to first make + // exodus_node_id as an index in C++, we need to first make // it zero-based. - auto conn_gi_zero_based = - cast_int(conn_gi - 1); + auto exodus_node_id_zero_based = + cast_int(exodus_node_id - 1); // If the user set the flag which stores Exodus node // ids as unique_ids instead of regular ids, then // the libmesh node id we are looking for is - // actually just "conn_gi_zero_based". Otherwise, we + // actually just "exodus_node_id_zero_based". Otherwise, we // need to look up the Node's id in the node_num_map, // *and* then subtract 1 from that because the entries // in the node_num_map are also 1-based. dof_id_type libmesh_node_id = _set_unique_ids_from_maps ? - cast_int(conn_gi_zero_based) : - cast_int(exio_helper->node_num_map[conn_gi_zero_based] - 1); + cast_int(exodus_node_id_zero_based) : + cast_int(exio_helper->node_num_map[exodus_node_id_zero_based] - 1); return libmesh_node_id; }; diff --git a/src/mesh/exodusII_io_helper.C b/src/mesh/exodusII_io_helper.C index 8f3d20835c8..48bb7fe3436 100644 --- a/src/mesh/exodusII_io_helper.C +++ b/src/mesh/exodusII_io_helper.C @@ -1426,7 +1426,7 @@ void ExodusII_IO_Helper::read_edge_blocks(MeshBase & mesh) // 1-based. dof_id_type libmesh_node_id = set_unique_ids_from_maps ? - exodus_node_id_zero_based : + cast_int(exodus_node_id_zero_based) : cast_int(node_num_map[exodus_node_id_zero_based] - 1); edge->set_node(n, mesh.node_ptr(libmesh_node_id)); From 4a501dfc0d3f74954610f5329c5f5328337a1e8c Mon Sep 17 00:00:00 2001 From: John Peterson Date: Mon, 20 Oct 2025 16:37:23 -0500 Subject: [PATCH 11/46] Add ExodusII_IO_Helper::get_libmesh_node_id() helper function --- include/mesh/exodusII_io_helper.h | 12 ++++++++++++ src/mesh/exodusII_io_helper.C | 30 ++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+) diff --git a/include/mesh/exodusII_io_helper.h b/include/mesh/exodusII_io_helper.h index 97ba2d915e8..351ad67a35f 100644 --- a/include/mesh/exodusII_io_helper.h +++ b/include/mesh/exodusII_io_helper.h @@ -305,6 +305,18 @@ class ExodusII_IO_Helper : public ParallelObject int time_step, std::map & elem_var_value_map); + /** + * Helper function that takes an index into the nodal connectivity + * array stored on this class (i.e. the "connect" member variable) + * and determines the corresponding libMesh Node id. Takes into + * account whether the user has chosen to set the Node unique ids + * based on the node_num_map or to let libMesh set them. The input + * is the index into the current "connect" array, the contents of + * which will depend on what exII function was most recently called, + * see e.g. exII::ex_get_conn(). + */ + dof_id_type get_libmesh_node_id(int connect_index); + /** * Opens an \p ExodusII mesh file named \p filename for writing. */ diff --git a/src/mesh/exodusII_io_helper.C b/src/mesh/exodusII_io_helper.C index 48bb7fe3436..2965096b05a 100644 --- a/src/mesh/exodusII_io_helper.C +++ b/src/mesh/exodusII_io_helper.C @@ -2153,6 +2153,36 @@ void ExodusII_IO_Helper::read_elemental_var_values(std::string elemental_var_nam } + +dof_id_type ExodusII_IO_Helper::get_libmesh_node_id(int connect_index) +{ + // Look up the value in the connectivity array at the provided index + auto exodus_node_id = this->connect[connect_index]; + + // The entries in 'connect' are actually (1-based) + // indices into the node_num_map, so in order to use + // exodus_node_id as an index in C++, we need to first make + // it zero-based. + auto exodus_node_id_zero_based = + cast_int(exodus_node_id - 1); + + // If the user set the flag which stores Exodus node + // ids as unique_ids instead of regular ids, then + // the libmesh node id we are looking for is + // actually just "exodus_node_id_zero_based". Otherwise, we + // need to look up the Node's id in the node_num_map, + // *and* then subtract 1 from that because the entries + // in the node_num_map are also 1-based. + dof_id_type libmesh_node_id = + this->set_unique_ids_from_maps ? + cast_int(exodus_node_id_zero_based) : + cast_int(this->node_num_map[exodus_node_id_zero_based] - 1); + + return libmesh_node_id; +} + + + // For Writing Solutions void ExodusII_IO_Helper::create(std::string filename) From c214402c4735dc4b50ebb363cb8e0a27b3018bd2 Mon Sep 17 00:00:00 2001 From: John Peterson Date: Mon, 20 Oct 2025 16:39:42 -0500 Subject: [PATCH 12/46] Drop get_libmesh_node_id() lambda --- src/mesh/exodusII_io.C | 36 ++--------------------------------- src/mesh/exodusII_io_helper.C | 16 +--------------- 2 files changed, 3 insertions(+), 49 deletions(-) diff --git a/src/mesh/exodusII_io.C b/src/mesh/exodusII_io.C index a2110b62a4c..a30a6fc2d95 100644 --- a/src/mesh/exodusII_io.C +++ b/src/mesh/exodusII_io.C @@ -450,38 +450,6 @@ void ExodusII_IO::read (const std::string & fname) // ExodusII_IO::write()!) that don't support sparse ids. dof_id_type n_nodes = mesh.n_nodes(); - // Helper function that takes an index into the nodal connectivity - // array for the current block and determines the corresponding - // libMesh Node id. Takes into account whether the user has chosen - // to set the Node unique ids based on the node_num_map or to let - // libMesh set them. - auto get_libmesh_node_id = [this](int gi) -> dof_id_type - { - // Look up the value in the connectivity array at this global index - auto exodus_node_id = exio_helper->connect[gi]; - - // The entries in 'connect' are actually (1-based) - // indices into the node_num_map, so in order to use - // exodus_node_id as an index in C++, we need to first make - // it zero-based. - auto exodus_node_id_zero_based = - cast_int(exodus_node_id - 1); - - // If the user set the flag which stores Exodus node - // ids as unique_ids instead of regular ids, then - // the libmesh node id we are looking for is - // actually just "exodus_node_id_zero_based". Otherwise, we - // need to look up the Node's id in the node_num_map, - // *and* then subtract 1 from that because the entries - // in the node_num_map are also 1-based. - dof_id_type libmesh_node_id = - _set_unique_ids_from_maps ? - cast_int(exodus_node_id_zero_based) : - cast_int(exio_helper->node_num_map[exodus_node_id_zero_based] - 1); - - return libmesh_node_id; - }; - // Loop over all the element blocks for (int i=0; inum_elem_blk; i++) { @@ -605,7 +573,7 @@ void ExodusII_IO::read (const std::string & fname) int gi = elem_num * exio_helper->num_nodes_per_elem + conv.get_node_map(k); // Convert this index to a libMesh Node id - auto libmesh_node_id = get_libmesh_node_id(gi); + auto libmesh_node_id = exio_helper->get_libmesh_node_id(gi); // Set the node pointer in the Elem elem->set_node(k, mesh.node_ptr(libmesh_node_id)); @@ -679,7 +647,7 @@ void ExodusII_IO::read (const std::string & fname) const int gi = elem_num * exio_helper->bex_num_elem_cvs + spline_node_index; // Convert this index to a libMesh Node id - auto libmesh_node_id = get_libmesh_node_id(gi); + auto libmesh_node_id = exio_helper->get_libmesh_node_id(gi); if (coef != 0) // Ignore irrelevant spline nodes key.emplace_back(libmesh_node_id, coef); diff --git a/src/mesh/exodusII_io_helper.C b/src/mesh/exodusII_io_helper.C index 2965096b05a..c234e78b634 100644 --- a/src/mesh/exodusII_io_helper.C +++ b/src/mesh/exodusII_io_helper.C @@ -1414,21 +1414,7 @@ void ExodusII_IO_Helper::read_edge_blocks(MeshBase & mesh) auto edge = Elem::build(conv.libmesh_elem_type()); for (int n=0; n(exodus_node_id_zero_based) : - cast_int(node_num_map[exodus_node_id_zero_based] - 1); - + dof_id_type libmesh_node_id = this->get_libmesh_node_id(i+n); edge->set_node(n, mesh.node_ptr(libmesh_node_id)); } From 0ef5997d314ab1750da88708f85e1f1e82d399c4 Mon Sep 17 00:00:00 2001 From: John Peterson Date: Mon, 20 Oct 2025 16:49:42 -0500 Subject: [PATCH 13/46] Use slightly more general std::make_tuple in loop declaration Although in this case both types in the tuple are the same, this syntax allows you to define local variables with different types directly in the for-loop clause. --- src/mesh/exodusII_io_helper.C | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mesh/exodusII_io_helper.C b/src/mesh/exodusII_io_helper.C index c234e78b634..44ebe746e50 100644 --- a/src/mesh/exodusII_io_helper.C +++ b/src/mesh/exodusII_io_helper.C @@ -1409,7 +1409,7 @@ void ExodusII_IO_Helper::read_edge_blocks(MeshBase & mesh) // Loop over indices in connectivity array, build edge elements, // look them up in the edge_map. - for (unsigned int i=0, sz=connect.size(); i Date: Tue, 21 Oct 2025 10:29:23 -0500 Subject: [PATCH 14/46] Change ExodusII_IO_Helper::get_libmesh_node_id() to take a 1-based Exodus node id --- include/mesh/exodusII_io_helper.h | 16 ++++++---------- src/mesh/exodusII_io.C | 15 +++++++++++---- src/mesh/exodusII_io_helper.C | 15 ++++++--------- 3 files changed, 23 insertions(+), 23 deletions(-) diff --git a/include/mesh/exodusII_io_helper.h b/include/mesh/exodusII_io_helper.h index 351ad67a35f..7ed2441b797 100644 --- a/include/mesh/exodusII_io_helper.h +++ b/include/mesh/exodusII_io_helper.h @@ -306,16 +306,12 @@ class ExodusII_IO_Helper : public ParallelObject std::map & elem_var_value_map); /** - * Helper function that takes an index into the nodal connectivity - * array stored on this class (i.e. the "connect" member variable) - * and determines the corresponding libMesh Node id. Takes into - * account whether the user has chosen to set the Node unique ids - * based on the node_num_map or to let libMesh set them. The input - * is the index into the current "connect" array, the contents of - * which will depend on what exII function was most recently called, - * see e.g. exII::ex_get_conn(). - */ - dof_id_type get_libmesh_node_id(int connect_index); + * Helper function that takes a (1-based) Exodus node id and + * determines the corresponding libMesh Node id. Takes into account + * whether the user has chosen to set the Node unique ids based on + * the node_num_map or to let libMesh set them. + */ + dof_id_type get_libmesh_node_id(int exodus_node_id); /** * Opens an \p ExodusII mesh file named \p filename for writing. diff --git a/src/mesh/exodusII_io.C b/src/mesh/exodusII_io.C index a30a6fc2d95..dd440eeaa3f 100644 --- a/src/mesh/exodusII_io.C +++ b/src/mesh/exodusII_io.C @@ -572,8 +572,11 @@ void ExodusII_IO::read (const std::string & fname) // Get index into this block's connectivity array int gi = elem_num * exio_helper->num_nodes_per_elem + conv.get_node_map(k); + // Get the 1-based Exodus node id from the "connect" array + auto exodus_node_id = exio_helper->connect[gi]; + // Convert this index to a libMesh Node id - auto libmesh_node_id = exio_helper->get_libmesh_node_id(gi); + auto libmesh_node_id = exio_helper->get_libmesh_node_id(exodus_node_id); // Set the node pointer in the Elem elem->set_node(k, mesh.node_ptr(libmesh_node_id)); @@ -646,8 +649,11 @@ void ExodusII_IO::read (const std::string & fname) // Get the libMesh node corresponding to that row const int gi = elem_num * exio_helper->bex_num_elem_cvs + spline_node_index; + // Get the 1-based Exodus node id from the "connect" array + auto exodus_node_id = exio_helper->connect[gi]; + // Convert this index to a libMesh Node id - auto libmesh_node_id = exio_helper->get_libmesh_node_id(gi); + auto libmesh_node_id = exio_helper->get_libmesh_node_id(exodus_node_id); if (coef != 0) // Ignore irrelevant spline nodes key.emplace_back(libmesh_node_id, coef); @@ -923,19 +929,20 @@ void ExodusII_IO::read (const std::string & fname) for (int i=0; inum_nodes_per_set[nodeset]; ++i) { int exodus_id = exio_helper->node_sets_node_list[i + offset]; + auto exodus_id_zero_based = cast_int(exodus_id - 1); // It's possible for nodesets to have invalid ids in them // by accident. Instead of possibly accessing past the // end of node_num_map, let's make sure we have that many // entries. - libmesh_error_msg_if(static_cast(exodus_id - 1) >= exio_helper->node_num_map.size(), + libmesh_error_msg_if(exodus_id_zero_based >= exio_helper->node_num_map.size(), "Invalid Exodus node id " << exodus_id << " found in nodeset " << nodeset_id); // As before, the entries in 'node_list' are 1-based // indices into the node_num_map array, so we have to map // them. See comment above. - int libmesh_node_id = exio_helper->node_num_map[exodus_id - 1] - 1; + auto libmesh_node_id = cast_int(exio_helper->node_num_map[exodus_id_zero_based] - 1); mesh.get_boundary_info().add_node(cast_int(libmesh_node_id), nodeset_id); } diff --git a/src/mesh/exodusII_io_helper.C b/src/mesh/exodusII_io_helper.C index 44ebe746e50..7c3ca1b364a 100644 --- a/src/mesh/exodusII_io_helper.C +++ b/src/mesh/exodusII_io_helper.C @@ -1414,7 +1414,8 @@ void ExodusII_IO_Helper::read_edge_blocks(MeshBase & mesh) auto edge = Elem::build(conv.libmesh_elem_type()); for (int n=0; nget_libmesh_node_id(i+n); + auto exodus_node_id = this->connect[i+n]; + dof_id_type libmesh_node_id = this->get_libmesh_node_id(exodus_node_id); edge->set_node(n, mesh.node_ptr(libmesh_node_id)); } @@ -2140,15 +2141,11 @@ void ExodusII_IO_Helper::read_elemental_var_values(std::string elemental_var_nam -dof_id_type ExodusII_IO_Helper::get_libmesh_node_id(int connect_index) +dof_id_type ExodusII_IO_Helper::get_libmesh_node_id(int exodus_node_id) { - // Look up the value in the connectivity array at the provided index - auto exodus_node_id = this->connect[connect_index]; - - // The entries in 'connect' are actually (1-based) - // indices into the node_num_map, so in order to use - // exodus_node_id as an index in C++, we need to first make - // it zero-based. + // The input exodus_node_id is a (1-based) index into the + // node_num_map, so in order to use exodus_node_id as an index in + // C++, we need to first make it zero-based. auto exodus_node_id_zero_based = cast_int(exodus_node_id - 1); From 8dc9d411c58878f7df6b3105b388aa41f0a8aa96 Mon Sep 17 00:00:00 2001 From: John Peterson Date: Tue, 21 Oct 2025 10:39:08 -0500 Subject: [PATCH 15/46] Call get_libmesh_node_id() when reading nodeset info Also, add an error check to the get_libmesh_node_id() function so that we get a nice error message rather than accessing past the end of the node_num_map vector. --- src/mesh/exodusII_io.C | 20 +++----------------- src/mesh/exodusII_io_helper.C | 7 +++++++ 2 files changed, 10 insertions(+), 17 deletions(-) diff --git a/src/mesh/exodusII_io.C b/src/mesh/exodusII_io.C index dd440eeaa3f..495a6754a2a 100644 --- a/src/mesh/exodusII_io.C +++ b/src/mesh/exodusII_io.C @@ -928,23 +928,9 @@ void ExodusII_IO::read (const std::string & fname) for (int i=0; inum_nodes_per_set[nodeset]; ++i) { - int exodus_id = exio_helper->node_sets_node_list[i + offset]; - auto exodus_id_zero_based = cast_int(exodus_id - 1); - - // It's possible for nodesets to have invalid ids in them - // by accident. Instead of possibly accessing past the - // end of node_num_map, let's make sure we have that many - // entries. - libmesh_error_msg_if(exodus_id_zero_based >= exio_helper->node_num_map.size(), - "Invalid Exodus node id " << exodus_id - << " found in nodeset " << nodeset_id); - - // As before, the entries in 'node_list' are 1-based - // indices into the node_num_map array, so we have to map - // them. See comment above. - auto libmesh_node_id = cast_int(exio_helper->node_num_map[exodus_id_zero_based] - 1); - mesh.get_boundary_info().add_node(cast_int(libmesh_node_id), - nodeset_id); + int exodus_node_id = exio_helper->node_sets_node_list[i + offset]; + auto libmesh_node_id = exio_helper->get_libmesh_node_id(exodus_node_id); + mesh.get_boundary_info().add_node(libmesh_node_id, nodeset_id); } } } diff --git a/src/mesh/exodusII_io_helper.C b/src/mesh/exodusII_io_helper.C index 7c3ca1b364a..7978d3a1028 100644 --- a/src/mesh/exodusII_io_helper.C +++ b/src/mesh/exodusII_io_helper.C @@ -2149,6 +2149,13 @@ dof_id_type ExodusII_IO_Helper::get_libmesh_node_id(int exodus_node_id) auto exodus_node_id_zero_based = cast_int(exodus_node_id - 1); + // Throw an informative error message rather than accessing past the + // end of the node_num_map. If we are setting Node unique_ids from + // the node_num_map, we don't need to do this check. + if (!this->set_unique_ids_from_maps) + libmesh_error_msg_if(exodus_node_id_zero_based >= this->node_num_map.size(), + "Cannot get LibMesh node id for Exodus node id: " << exodus_node_id); + // If the user set the flag which stores Exodus node // ids as unique_ids instead of regular ids, then // the libmesh node id we are looking for is From c7d0fdca7464c61484cf839bdafc5113f026253f Mon Sep 17 00:00:00 2001 From: John Peterson Date: Tue, 21 Oct 2025 14:22:43 -0500 Subject: [PATCH 16/46] Call get_libmesh_node_id() in ExodusII_IO_Helper::read_nodal_var_values() --- src/mesh/exodusII_io_helper.C | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/mesh/exodusII_io_helper.C b/src/mesh/exodusII_io_helper.C index 7978d3a1028..39fac2244ab 100644 --- a/src/mesh/exodusII_io_helper.C +++ b/src/mesh/exodusII_io_helper.C @@ -1862,7 +1862,7 @@ void ExodusII_IO_Helper::read_nodal_var_values(std::string nodal_var_name, int t } // Clear out any previously read nodal variable values - nodal_var_values.clear(); + this->nodal_var_values.clear(); std::vector unmapped_nodal_var_values(num_nodes); @@ -1877,18 +1877,21 @@ void ExodusII_IO_Helper::read_nodal_var_values(std::string nodal_var_name, int t MappedInputVector(unmapped_nodal_var_values, _single_precision).data()); EX_CHECK_ERR(ex_err, "Error reading nodal variable values!"); - for (unsigned i=0; i(num_nodes); i++) + for (auto i : make_range(num_nodes)) { - libmesh_assert_less(i, this->node_num_map.size()); - - // Use the node_num_map to obtain the ID of this node in the Exodus file, - // and remember to subtract 1 since libmesh is zero-based and Exodus is 1-based. - const unsigned mapped_node_id = this->node_num_map[i] - 1; - - libmesh_assert_less(i, unmapped_nodal_var_values.size()); + // Determine the libmesh node id implied by "i". The + // get_libmesh_node_id() helper function expects a 1-based + // Exodus node id, so we construct the "implied" Exodus node id + // from "i" by adding 1. + // + // If the user has set the "set_unique_ids_from_maps" flag to + // true, then calling get_libmesh_node_id(i+1) will just return + // i, otherwise it will determine the value (with error + // checking) using this->node_num_map. + auto libmesh_node_id = this->get_libmesh_node_id(/*exodus_node_id=*/i+1); // Store the nodal value in the map. - nodal_var_values[mapped_node_id] = unmapped_nodal_var_values[i]; + this->nodal_var_values[libmesh_node_id] = unmapped_nodal_var_values[i]; } } From 6914226cac28d067b6d188422fe8f568b75d7ffa Mon Sep 17 00:00:00 2001 From: John Peterson Date: Tue, 21 Oct 2025 14:58:14 -0500 Subject: [PATCH 17/46] Call ExodusII_IO_Helper::get_libmesh_node_id() from ExodusII_IO::read() The helper function effectively does nothing in the case where set_unique_ids_from_maps == true, since in that case the libmesh id is just a (zero-based) version of the input. We then determine the unique_id after the new Node has already been added, since that is when it is most convenient to set the unique_id value. --- src/mesh/exodusII_io.C | 47 ++++++++++++++++++++---------------------- 1 file changed, 22 insertions(+), 25 deletions(-) diff --git a/src/mesh/exodusII_io.C b/src/mesh/exodusII_io.C index 495a6754a2a..1ef73dc8152 100644 --- a/src/mesh/exodusII_io.C +++ b/src/mesh/exodusII_io.C @@ -327,37 +327,24 @@ void ExodusII_IO::read (const std::string & fname) std::unordered_map spline_nodeelem_ptrs; // Loop over the nodes, create Nodes with local processor_id 0. - for (int i=0; inum_nodes; i++) + for (auto i : make_range(exio_helper->num_nodes)) { - // Use the node_num_map to get the correct ID for Exodus - int exodus_id = exio_helper->node_num_map[i]; - - // Exodus ids are always 1-based while libmesh ids are always - // 0-based, so here we subtract 1 from the value given in the - // node_num_map to make it 0-based. - auto exodus_id_zero_based = - cast_int(exodus_id - 1); - - // Determine what id() the Node will have. If the user has set - // the _set_unique_ids_from_maps flag to true, then this Node - // will simply be assigned an id of "i" as it is added, - // otherwise we will immediately assign the Node a (zero-based) - // id() based on the value in the node_num_map. - dof_id_type libmesh_id = - _set_unique_ids_from_maps ? - cast_int(i) : - exodus_id_zero_based; + // Determine the libmesh node id implied by "i". The + // get_libmesh_node_id() helper function expects a 1-based + // Exodus node id, so we construct the "implied" Exodus node id + // from "i" by adding 1. + auto libmesh_node_id = exio_helper->get_libmesh_node_id(/*exodus_node_id=*/i+1); // Catch the node that was added to the mesh - Node * added_node = mesh.add_point (Point(exio_helper->x[i], exio_helper->y[i], exio_helper->z[i]), libmesh_id); + Node * added_node = mesh.add_point (Point(exio_helper->x[i], exio_helper->y[i], exio_helper->z[i]), libmesh_node_id); // Sanity check: throw an error if the Mesh assigned an ID to // the Node which does not match the libmesh_id we just determined. - libmesh_error_msg_if(added_node->id() != static_cast(libmesh_id), + libmesh_error_msg_if(added_node->id() != static_cast(libmesh_node_id), "Error! Mesh assigned node ID " << added_node->id() << " which is different from the (zero-based) Exodus ID " - << libmesh_id + << libmesh_node_id << "!"); // If the _set_unique_ids_from_maps flag is true, then set the @@ -365,10 +352,20 @@ void ExodusII_IO::read (const std::string & fname) // node_num_map[i] value. if (_set_unique_ids_from_maps) { - added_node->set_unique_id(cast_int(exodus_id_zero_based)); + // Use the node_num_map to get a 1-based Exodus Node ID + int exodus_mapped_id = exio_helper->node_num_map[i]; + + // Exodus ids are always 1-based while libmesh ids are always + // 0-based, so to make a libmesh unique_id here, we subtract 1 + // from the exodus_mapped_id to make it 0-based. + auto exodus_mapped_id_zero_based = + cast_int(exodus_mapped_id - 1); + + // Set added_node's unique_id to "exodus_mapped_id_zero_based". + added_node->set_unique_id(cast_int(exodus_mapped_id_zero_based)); // Normally the Mesh is responsible for setting the unique_ids - // of Nodes in a consistent manner, so when we set the value + // of Nodes in a consistent manner, so when we set the unique_id // of a Node manually based on the node_num_map, we need to // make sure that the "next" unique id assigned by the Mesh // will still be valid. We do this by making sure that the @@ -379,7 +376,7 @@ void ExodusII_IO::read (const std::string & fname) // here because the Exodus reader might only be called on one processor. #ifdef LIBMESH_ENABLE_UNIQUE_ID unique_id_type next_unique_id = mesh.next_unique_id(); - mesh.set_next_unique_id(std::max(next_unique_id, exodus_id_zero_based + 1)); + mesh.set_next_unique_id(std::max(next_unique_id, exodus_mapped_id_zero_based + 1)); #endif } From 6396a4d2467c9377d5dc713c70ad5f1fa5a86e40 Mon Sep 17 00:00:00 2001 From: John Peterson Date: Tue, 21 Oct 2025 15:52:41 -0500 Subject: [PATCH 18/46] Add comment about _set_unique_ids_from_maps flag behavior when writing Nodes --- src/mesh/exodusII_io_helper.C | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/mesh/exodusII_io_helper.C b/src/mesh/exodusII_io_helper.C index 39fac2244ab..15b5c39298b 100644 --- a/src/mesh/exodusII_io_helper.C +++ b/src/mesh/exodusII_io_helper.C @@ -2468,12 +2468,22 @@ void ExodusII_IO_Helper::write_nodal_coordinates(const MeshBase & mesh, bool use #endif }; - // And in the node_num_map - since the nodes aren't organized in - // blocks, libmesh will always write out the identity map - // here... unless there has been some refinement and coarsening, or - // node deletion without a corresponding call to contract(). You - // need to write this any time there could be 'holes' in the node - // numbering, so we write it every time. + // And in the node_num_map. If the user has set the + // _set_unique_ids_from_maps flag, then we will write the Node + // unique_ids to the node_num_map, otherwise we will just write a + // trivial node_num_map, since in that we don't write the unique_id + // information to the Exodus file. In other words, set the + // _set_unique_ids_from_maps flag to true on both the reading and + // writing ExodusII_IO objects if you want to preserve the + // node_num_map information without actually renumbering the Nodes + // in libmesh according to the node_num_map. + // + // One reason why you might not want to actually renumber the Nodes + // in libmesh according to the node_num_map is that it can introduce + // undesirable large "gaps" in the numbering, e.g. Nodes numbered + // [0, 1, 1000, 10001] which is not ideal for the ReplicatedMesh + // _nodes data structure, which stores the Nodes in a contiguous + // array based on Node id. // Let's skip the node_num_map in the discontinuous and add_sides // cases, since we're effectively duplicating nodes for the sake of From 7393af0159416faaa106b6e4f33143f6c7a0990c Mon Sep 17 00:00:00 2001 From: John Peterson Date: Tue, 21 Oct 2025 16:12:27 -0500 Subject: [PATCH 19/46] Store unique_ids in the node_num_map when writing Nodes Previously, we just wrote an identity vector for the node_num_map, but now we populate it with the Node unique_ids if the user sets the _set_unique_ids_from_maps boolean. --- src/mesh/exodusII_io_helper.C | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/src/mesh/exodusII_io_helper.C b/src/mesh/exodusII_io_helper.C index 15b5c39298b..e59833e508c 100644 --- a/src/mesh/exodusII_io_helper.C +++ b/src/mesh/exodusII_io_helper.C @@ -2507,17 +2507,27 @@ void ExodusII_IO_Helper::write_nodal_coordinates(const MeshBase & mesh, bool use // Fill in node_num_map entry with the proper (1-based) node // id, unless we're not going to be able to keep the map up - // later. + // later. If the user has chosen to _set_unique_ids_from_maps, + // then we fill up the node_num_map with (1-based) unique + // ids rather than node ids. if (!_add_sides) - node_num_map.push_back(node.id() + 1); + { + if (this->set_unique_ids_from_maps) + node_num_map.push_back(node.unique_id() + 1); + else + node_num_map.push_back(node.id() + 1); + } - // Also map the zero-based libmesh node id to the 1-based - // Exodus ID it will be assigned (this is equivalent to the - // current size of the x vector). + // Also map the zero-based libmesh node id to the (1-based) + // index in the node_num_map it corresponds to + // (this is equivalent to the current size of the "x" vector, + // so we just use x.size()). This map is used to look up + // an Exodus Node id given a libMesh Node id, so it does + // involve unique_ids. libmesh_node_num_to_exodus[ cast_int(node.id()) ] = cast_int(x.size()); - } + } // end for (node_ptr) } - else + else // use_discontinuous { for (const auto & elem : mesh.active_element_ptr_range()) for (const Node & node : elem->node_ref_range()) From 3be119a60f72f691622093b82767f9f1b81db2c0 Mon Sep 17 00:00:00 2001 From: John Peterson Date: Fri, 31 Oct 2025 11:18:01 -0500 Subject: [PATCH 20/46] Add exo file with non-trivial node_num_map for unit testing --- tests/Makefile.am | 1 + tests/meshes/nontrivial_node_num_map.exo | Bin 0 -> 2832 bytes 2 files changed, 1 insertion(+) create mode 100644 tests/meshes/nontrivial_node_num_map.exo diff --git a/tests/Makefile.am b/tests/Makefile.am index acf0ad8239d..ce689e24f19 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -217,6 +217,7 @@ data = matrices/geom_1_extraction_op.h5 \ meshes/non_manifold_junction1.exo \ meshes/non_manifold_junction2.exo \ meshes/non_manifold_junction3.exo \ + meshes/nontrivial_node_num_map.exo \ meshes/Cluster_34.stl \ meshes/engraving.stl \ meshes/quad4_tri3_smoothed.xda.gz \ diff --git a/tests/meshes/nontrivial_node_num_map.exo b/tests/meshes/nontrivial_node_num_map.exo new file mode 100644 index 0000000000000000000000000000000000000000..cfec214e9c7e98f2ef4edebcfa7f0679d8fbb070 GIT binary patch literal 2832 zcmb_dy>1gh5YE5vE>6Nv3M85!rAV5@PJ%!dQUZw(DI<{%>E!s%?Q9Fb|*uln7DekuYAZl|<|1S4&5`>usxL^)t zVJjqvemuSRk6N!3Xk28k>wX$Gda^mOpy_4`2yvrjo%4V)I%v$KG08N%+@=R|0YbcD2Zu9 z+4#=q7pvf<0{NY0E2-x=LBEqkaqgcaX@Hu&X?%|XD=L%LZ zRkzkE1u8A>KhOoR!B_hVkWT3eg@|+>vbzpvEiMoBK0u1`?vO4 z#Gz$A%t7T Date: Fri, 31 Oct 2025 11:20:24 -0500 Subject: [PATCH 21/46] Run bootstrap --- tests/Makefile.in | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/Makefile.in b/tests/Makefile.in index d2ab60962dd..cdc44ec38d0 100644 --- a/tests/Makefile.in +++ b/tests/Makefile.in @@ -2386,6 +2386,7 @@ data = matrices/geom_1_extraction_op.h5 \ meshes/non_manifold_junction1.exo \ meshes/non_manifold_junction2.exo \ meshes/non_manifold_junction3.exo \ + meshes/nontrivial_node_num_map.exo \ meshes/Cluster_34.stl \ meshes/engraving.stl \ meshes/quad4_tri3_smoothed.xda.gz \ From 4e605fee004c6b625d960bcd5b9cec1c251ff6a7 Mon Sep 17 00:00:00 2001 From: John Peterson Date: Fri, 31 Oct 2025 11:25:58 -0500 Subject: [PATCH 22/46] Add ExodusII_IO unit test that sets node unique ids from the node_num_map --- tests/mesh/mesh_input.C | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/tests/mesh/mesh_input.C b/tests/mesh/mesh_input.C index 4dd3a2ff749..8be66beb97d 100644 --- a/tests/mesh/mesh_input.C +++ b/tests/mesh/mesh_input.C @@ -112,6 +112,7 @@ public: CPPUNIT_TEST( testExodusCopyNodalSolutionReplicated ); CPPUNIT_TEST( testExodusCopyElementSolutionReplicated ); CPPUNIT_TEST( testExodusReadHeader ); + CPPUNIT_TEST( testExodusSetUniqueIdsFromMaps ); #if LIBMESH_DIM > 2 CPPUNIT_TEST( testExodusIGASidesets ); CPPUNIT_TEST( testLowOrderEdgeBlocks ); @@ -333,6 +334,15 @@ public: #ifdef LIBMESH_HAVE_EXODUS_API + void testExodusSetUniqueIdsFromMaps() + { + LOG_UNIT_TEST; + + ReplicatedMesh mesh(*TestCommWorld); + ExodusII_IO exii(mesh); + exii.read("meshes/nontrivial_node_num_map.exo"); + } + void testExodusReadHeader () { LOG_UNIT_TEST; From ff34a931ce1f865ffaa81c4540fa82dd477f0f99 Mon Sep 17 00:00:00 2001 From: John Peterson Date: Fri, 31 Oct 2025 11:50:40 -0500 Subject: [PATCH 23/46] Add test verifying that the unique_ids have been set --- tests/mesh/mesh_input.C | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/tests/mesh/mesh_input.C b/tests/mesh/mesh_input.C index 8be66beb97d..bb06ad214f0 100644 --- a/tests/mesh/mesh_input.C +++ b/tests/mesh/mesh_input.C @@ -338,9 +338,33 @@ public: { LOG_UNIT_TEST; + // This test requires that libmesh is compiled with unique_ids enabled +#ifdef LIBMESH_ENABLE_UNIQUE_ID ReplicatedMesh mesh(*TestCommWorld); ExodusII_IO exii(mesh); + + // Set Node/Elem unique ids based on the node/elem_num_map + exii.set_unique_ids_from_maps(true); + + // Read the mesh, which contains the following non-trivial node_num_map: + // node_num_map = 1, 3, 9, 8, 2, 5, 7, 6, 4 + // coordx = 0.5, 0.5, 0, 0, 0.5, 0, -0.5, -0.5, -0.5 + // coordy = -0.5, 0, 0, -0.5, 0.5, 0.5, 0, -0.5, 0.5 exii.read("meshes/nontrivial_node_num_map.exo"); + + // Verify the results. The following is a (zero-based) version of + // the node_num_map as it exists in the file. + std::vector expected_unique_ids = {0, 2, 8, 7, 1, 4, 6, 5, 3}; + for (auto i : index_range(expected_unique_ids)) + { + // Debugging: + // libMesh::out << "unique_id for node " << i + // << " = " << mesh.node_ptr(i)->unique_id() + // << std::endl; + + CPPUNIT_ASSERT_EQUAL(mesh.node_ptr(i)->unique_id(), expected_unique_ids[i]); + } +#endif } void testExodusReadHeader () From 59b43b7a5944ee0cca98f576850e7af006a689a4 Mon Sep 17 00:00:00 2001 From: John Peterson Date: Fri, 31 Oct 2025 13:29:05 -0500 Subject: [PATCH 24/46] Add test of same mesh without setting the flag --- tests/mesh/mesh_input.C | 76 ++++++++++++++++++++++++++++++----------- 1 file changed, 56 insertions(+), 20 deletions(-) diff --git a/tests/mesh/mesh_input.C b/tests/mesh/mesh_input.C index bb06ad214f0..b096f29bbc8 100644 --- a/tests/mesh/mesh_input.C +++ b/tests/mesh/mesh_input.C @@ -340,30 +340,66 @@ public: // This test requires that libmesh is compiled with unique_ids enabled #ifdef LIBMESH_ENABLE_UNIQUE_ID - ReplicatedMesh mesh(*TestCommWorld); - ExodusII_IO exii(mesh); + { + ReplicatedMesh mesh(*TestCommWorld); + ExodusII_IO exii(mesh); - // Set Node/Elem unique ids based on the node/elem_num_map - exii.set_unique_ids_from_maps(true); + // Set Node/Elem unique ids based on the node/elem_num_map + exii.set_unique_ids_from_maps(true); - // Read the mesh, which contains the following non-trivial node_num_map: - // node_num_map = 1, 3, 9, 8, 2, 5, 7, 6, 4 - // coordx = 0.5, 0.5, 0, 0, 0.5, 0, -0.5, -0.5, -0.5 - // coordy = -0.5, 0, 0, -0.5, 0.5, 0.5, 0, -0.5, 0.5 - exii.read("meshes/nontrivial_node_num_map.exo"); + // Read the mesh, which contains the following non-trivial node_num_map: + // node_num_map = 1, 3, 9, 8, 2, 5, 7, 6, 4 + // coordx = 0.5, 0.5, 0, 0, 0.5, 0, -0.5, -0.5, -0.5 + // coordy = -0.5, 0, 0, -0.5, 0.5, 0.5, 0, -0.5, 0.5 + exii.read("meshes/nontrivial_node_num_map.exo"); - // Verify the results. The following is a (zero-based) version of - // the node_num_map as it exists in the file. - std::vector expected_unique_ids = {0, 2, 8, 7, 1, 4, 6, 5, 3}; - for (auto i : index_range(expected_unique_ids)) - { - // Debugging: - // libMesh::out << "unique_id for node " << i - // << " = " << mesh.node_ptr(i)->unique_id() - // << std::endl; + // Verify the results. The following is a (zero-based) version of + // the node_num_map as it exists in the file. + std::vector expected_unique_ids = {0, 2, 8, 7, 1, 4, 6, 5, 3}; + for (auto i : index_range(expected_unique_ids)) + { + // Debugging: + // libMesh::out << "unique_id for node " << i + // << " = " << mesh.node_ptr(i)->unique_id() + // << std::endl; - CPPUNIT_ASSERT_EQUAL(mesh.node_ptr(i)->unique_id(), expected_unique_ids[i]); - } + CPPUNIT_ASSERT_EQUAL(mesh.node_ptr(i)->unique_id(), expected_unique_ids[i]); + } + } + + { + // Same test but of the standard libmesh behavior (set Node ids + // based on node_num_map, rather than unique_ids) + ReplicatedMesh mesh(*TestCommWorld); + ExodusII_IO exii(mesh); + + // Set Node/Elem unique ids based on the node/elem_num_map + exii.set_unique_ids_from_maps(false); + + // Read the mesh, which contains the following non-trivial node_num_map: + // node_num_map = 1, 3, 9, 8, 2, 5, 7, 6, 4 + // coordx = 0.5, 0.5, 0, 0, 0.5, 0, -0.5, -0.5, -0.5 + // coordy = -0.5, 0, 0, -0.5, 0.5, 0.5, 0, -0.5, 0.5 + exii.read("meshes/nontrivial_node_num_map.exo"); + + // Verify the results. The following is the (zero-based) + // _position_ of the (one-based) id "i" in the node_num_map, as + // it exists in the file. For example, zero-based Node id 3 + // corresponds to one-based Node id 4, which appears in + // (zero-based) position 8 in the node_num_map, hence: 3 -> 8, + // etc. + // i = 0, 1, 2, 3, 4, 5, 6, 7, 8 + std::vector expected_unique_ids = {0, 4, 1, 8, 5, 7, 6, 3, 2}; + for (auto i : index_range(expected_unique_ids)) + { + // Debugging: + // libMesh::out << "unique_id for node " << i + // << " = " << mesh.node_ptr(i)->unique_id() + // << std::endl; + + CPPUNIT_ASSERT_EQUAL(mesh.node_ptr(i)->unique_id(), expected_unique_ids[i]); + } + } #endif } From aa07bff53137874b03ab003a67c58997a1810302 Mon Sep 17 00:00:00 2001 From: John Peterson Date: Fri, 31 Oct 2025 13:44:16 -0500 Subject: [PATCH 25/46] Add helper function that the test calls twice --- tests/mesh/mesh_input.C | 73 +++++++++++++++++------------------------ 1 file changed, 31 insertions(+), 42 deletions(-) diff --git a/tests/mesh/mesh_input.C b/tests/mesh/mesh_input.C index b096f29bbc8..6121ae27b1a 100644 --- a/tests/mesh/mesh_input.C +++ b/tests/mesh/mesh_input.C @@ -334,10 +334,11 @@ public: #ifdef LIBMESH_HAVE_EXODUS_API - void testExodusSetUniqueIdsFromMaps() - { - LOG_UNIT_TEST; + void testExodusSetUniqueIdsFromMaps_implementation( + bool set_unique_ids, + const std::vector & expected_unique_ids) + { // This test requires that libmesh is compiled with unique_ids enabled #ifdef LIBMESH_ENABLE_UNIQUE_ID { @@ -345,17 +346,12 @@ public: ExodusII_IO exii(mesh); // Set Node/Elem unique ids based on the node/elem_num_map - exii.set_unique_ids_from_maps(true); + exii.set_unique_ids_from_maps(set_unique_ids); - // Read the mesh, which contains the following non-trivial node_num_map: - // node_num_map = 1, 3, 9, 8, 2, 5, 7, 6, 4 - // coordx = 0.5, 0.5, 0, 0, 0.5, 0, -0.5, -0.5, -0.5 - // coordy = -0.5, 0, 0, -0.5, 0.5, 0.5, 0, -0.5, 0.5 + // Read the mesh exii.read("meshes/nontrivial_node_num_map.exo"); - // Verify the results. The following is a (zero-based) version of - // the node_num_map as it exists in the file. - std::vector expected_unique_ids = {0, 2, 8, 7, 1, 4, 6, 5, 3}; + // Verify the results. for (auto i : index_range(expected_unique_ids)) { // Debugging: @@ -366,41 +362,34 @@ public: CPPUNIT_ASSERT_EQUAL(mesh.node_ptr(i)->unique_id(), expected_unique_ids[i]); } } +#else + // Prevent compiler warnings about unused variables when + // unique_ids are not enabled. + libmesh_ignore(set_unique_ids, expected_unique_ids); +#endif // LIBMESH_ENABLE_UNIQUE_ID + } // end testExodusSetUniqueIdsFromMaps_implementation() - { - // Same test but of the standard libmesh behavior (set Node ids - // based on node_num_map, rather than unique_ids) - ReplicatedMesh mesh(*TestCommWorld); - ExodusII_IO exii(mesh); - - // Set Node/Elem unique ids based on the node/elem_num_map - exii.set_unique_ids_from_maps(false); - // Read the mesh, which contains the following non-trivial node_num_map: - // node_num_map = 1, 3, 9, 8, 2, 5, 7, 6, 4 - // coordx = 0.5, 0.5, 0, 0, 0.5, 0, -0.5, -0.5, -0.5 - // coordy = -0.5, 0, 0, -0.5, 0.5, 0.5, 0, -0.5, 0.5 - exii.read("meshes/nontrivial_node_num_map.exo"); - // Verify the results. The following is the (zero-based) - // _position_ of the (one-based) id "i" in the node_num_map, as - // it exists in the file. For example, zero-based Node id 3 - // corresponds to one-based Node id 4, which appears in - // (zero-based) position 8 in the node_num_map, hence: 3 -> 8, - // etc. - // i = 0, 1, 2, 3, 4, 5, 6, 7, 8 - std::vector expected_unique_ids = {0, 4, 1, 8, 5, 7, 6, 3, 2}; - for (auto i : index_range(expected_unique_ids)) - { - // Debugging: - // libMesh::out << "unique_id for node " << i - // << " = " << mesh.node_ptr(i)->unique_id() - // << std::endl; + void testExodusSetUniqueIdsFromMaps() + { + LOG_UNIT_TEST; - CPPUNIT_ASSERT_EQUAL(mesh.node_ptr(i)->unique_id(), expected_unique_ids[i]); - } - } -#endif + // node_num_map = 1, 3, 9, 8, 2, 5, 7, 6, 4 + // The input is a (zero-based) version of the node_num_map as it + // exists in the file. + this->testExodusSetUniqueIdsFromMaps_implementation( + /*set_unique_ids=*/true, + /*expected_unique_ids=*/{0, 2, 8, 7, 1, 4, 6, 5, 3}); + + // The input is the (zero-based) _position_ of the (one-based) id + // "i" in the node_num_map, as it exists in the file. For example, + // zero-based Node id 3 corresponds to one-based Node id 4, which + // appears in (zero-based) position 8 in the node_num_map, hence: + // 3 -> 8, etc. + this->testExodusSetUniqueIdsFromMaps_implementation( + /*set_unique_ids=*/false, + /*expected_unique_ids=*/{0, 4, 1, 8, 5, 7, 6, 3, 2}); } void testExodusReadHeader () From 36066fe58a38d6b8ffc2ec8068714053a90dd307 Mon Sep 17 00:00:00 2001 From: John Peterson Date: Mon, 3 Nov 2025 11:19:44 -0600 Subject: [PATCH 26/46] Add comments about setting _end_elem_id, and that we don't set the unique_ids of spline NodeElems --- src/mesh/exodusII_io.C | 7 +++++++ src/mesh/exodusII_io_helper.C | 6 ++++++ 2 files changed, 13 insertions(+) diff --git a/src/mesh/exodusII_io.C b/src/mesh/exodusII_io.C index 1ef73dc8152..56f8309f9b1 100644 --- a/src/mesh/exodusII_io.C +++ b/src/mesh/exodusII_io.C @@ -399,6 +399,13 @@ void ExodusII_IO::read (const std::string & fname) // Give the NodeElem ids at the end, so we can match any // existing ids in the file for other elements + // + // We don't set the unique_id for this NodeElem here even if + // the user has set the _set_unique_ids_from_maps flag + // because these NodeElems don't have entries in the + // elem_num_map. Therefore, we just let the Mesh assign + // whatever unique_id is "next" as the Elem is added to the + // Mesh. elem->set_id() = exio_helper->end_elem_id() + i; elem->set_node(0, added_node); diff --git a/src/mesh/exodusII_io_helper.C b/src/mesh/exodusII_io_helper.C index e59833e508c..2798f44d2cf 100644 --- a/src/mesh/exodusII_io_helper.C +++ b/src/mesh/exodusII_io_helper.C @@ -1486,6 +1486,12 @@ void ExodusII_IO_Helper::read_elem_num_map () if (num_elem) { + // The elem_num_map may contain ids larger than num_elem. In + // other words, the elem_num_map is not necessarily just a + // permutation of the "trivial" 1,2,3,... mapping, it can + // contain effectively "any" numbers. Therefore, to get + // "_end_elem_id", we need to check what the max entry in the + // elem_num_map is. auto it = std::max_element(elem_num_map.begin(), elem_num_map.end()); _end_elem_id = *it; } From dd06e91c112368f0c927753f0a44aed18a66e171 Mon Sep 17 00:00:00 2001 From: John Peterson Date: Mon, 3 Nov 2025 11:44:43 -0600 Subject: [PATCH 27/46] Add ExodusII_IO_Helper::get_libmesh_elem_id() --- include/mesh/exodusII_io_helper.h | 9 +++++---- src/mesh/exodusII_io.C | 6 ++++++ src/mesh/exodusII_io_helper.C | 30 ++++++++++++++++++++++++++++++ 3 files changed, 41 insertions(+), 4 deletions(-) diff --git a/include/mesh/exodusII_io_helper.h b/include/mesh/exodusII_io_helper.h index 7ed2441b797..f69a584a427 100644 --- a/include/mesh/exodusII_io_helper.h +++ b/include/mesh/exodusII_io_helper.h @@ -306,12 +306,13 @@ class ExodusII_IO_Helper : public ParallelObject std::map & elem_var_value_map); /** - * Helper function that takes a (1-based) Exodus node id and - * determines the corresponding libMesh Node id. Takes into account - * whether the user has chosen to set the Node unique ids based on - * the node_num_map or to let libMesh set them. + * Helper function that takes a (1-based) Exodus node/elem id and + * determines the corresponding libMesh Node/Elem id. Takes into account + * whether the user has chosen to set the Node/Elem unique ids based on + * the {node,elem}_num_map or to let libMesh set them. */ dof_id_type get_libmesh_node_id(int exodus_node_id); + dof_id_type get_libmesh_elem_id(int exodus_elem_id); /** * Opens an \p ExodusII mesh file named \p filename for writing. diff --git a/src/mesh/exodusII_io.C b/src/mesh/exodusII_io.C index 56f8309f9b1..d6e1e62b156 100644 --- a/src/mesh/exodusII_io.C +++ b/src/mesh/exodusII_io.C @@ -494,6 +494,12 @@ void ExodusII_IO::read (const std::string & fname) // Assign the current subdomain to this Elem uelem->subdomain_id() = static_cast(subdomain_id); + // Determine the libmesh elem id implied by "j". The + // get_libmesh_elem_id() helper function expects a 1-based + // Exodus elem id, so we construct the "implied" Exodus elem + // id from "j" by adding 1. + // auto libmesh_elem_id = exio_helper->get_libmesh_elem_id(/*exodus_elem_id=*/j+1); + // Use the elem_num_map to obtain the ID of this element in // the Exodus file. Make sure we aren't reading garbage if // the file is corrupt. diff --git a/src/mesh/exodusII_io_helper.C b/src/mesh/exodusII_io_helper.C index 2798f44d2cf..21377fc8187 100644 --- a/src/mesh/exodusII_io_helper.C +++ b/src/mesh/exodusII_io_helper.C @@ -2182,6 +2182,36 @@ dof_id_type ExodusII_IO_Helper::get_libmesh_node_id(int exodus_node_id) +dof_id_type ExodusII_IO_Helper::get_libmesh_elem_id(int exodus_elem_id) +{ + // The input exodus_elem_id is assumed to be a (1-based) index into + // the elem_num_map, so in order to use exodus_elem_id as an index + // in C++, we need to first make it zero-based. + auto exodus_elem_id_zero_based = + cast_int(exodus_elem_id - 1); + + // Throw an informative error message rather than accessing past the + // end of the elem_num_map. If we are setting Elem unique_ids from + // the elem_num_map, we don't need to do this check. + if (!this->set_unique_ids_from_maps) + libmesh_error_msg_if(exodus_elem_id_zero_based >= this->elem_num_map.size(), + "Cannot get LibMesh elem id for Exodus elem id: " << exodus_elem_id); + + // If the user set the flag which stores Exodus elem + // ids as unique_ids instead of regular ids, then + // the libmesh elem id we are looking for is + // actually just "exodus_elem_id_zero_based". Otherwise, we + // need to look up the Elem's id in the elem_num_map, + // *and* then subtract 1 from that because the entries + // in the elem_num_map are also 1-based. + dof_id_type libmesh_elem_id = + this->set_unique_ids_from_maps ? + cast_int(exodus_elem_id_zero_based) : + cast_int(this->elem_num_map[exodus_elem_id_zero_based] - 1); + + return libmesh_elem_id; +} + // For Writing Solutions void ExodusII_IO_Helper::create(std::string filename) From 4619ba47a1a1a1ca86730840c9fd379073964e51 Mon Sep 17 00:00:00 2001 From: John Peterson Date: Mon, 3 Nov 2025 11:57:50 -0600 Subject: [PATCH 28/46] Call ExodusII_IO_Helper::get_libmesh_elem_id() while adding Elems to the Mesh --- src/mesh/exodusII_io.C | 63 +++++++++++++++++++++++++++--------------- 1 file changed, 41 insertions(+), 22 deletions(-) diff --git a/src/mesh/exodusII_io.C b/src/mesh/exodusII_io.C index d6e1e62b156..33ab3b248a3 100644 --- a/src/mesh/exodusII_io.C +++ b/src/mesh/exodusII_io.C @@ -495,24 +495,12 @@ void ExodusII_IO::read (const std::string & fname) uelem->subdomain_id() = static_cast(subdomain_id); // Determine the libmesh elem id implied by "j". The - // get_libmesh_elem_id() helper function expects a 1-based - // Exodus elem id, so we construct the "implied" Exodus elem - // id from "j" by adding 1. - // auto libmesh_elem_id = exio_helper->get_libmesh_elem_id(/*exodus_elem_id=*/j+1); - - // Use the elem_num_map to obtain the ID of this element in - // the Exodus file. Make sure we aren't reading garbage if - // the file is corrupt. - libmesh_error_msg_if(std::size_t(j) >= exio_helper->elem_num_map.size(), - "Error: Trying to read Exodus file with more elements than elem_num_map entries.\n"); - int exodus_id = exio_helper->elem_num_map[j]; - - // Assign this element the same ID it had in the Exodus - // file, but make it zero-based by subtracting 1. Note: - // some day we could use 1-based numbering in libmesh and - // thus match the Exodus numbering exactly, but at the - // moment libmesh is zero-based. - uelem->set_id(exodus_id-1); + // ExodusII_IO_Helper::get_libmesh_elem_id() helper function + // expects a 1-based Exodus elem id, so we construct the + // "implied" Exodus elem id from "j" by adding 1. + auto libmesh_elem_id = exio_helper->get_libmesh_elem_id(/*exodus_elem_id=*/j+1); + + uelem->set_id(libmesh_elem_id); // Record that we have seen an element of dimension uelem->dim() elems_of_dimension[uelem->dim()] = true; @@ -520,13 +508,44 @@ void ExodusII_IO::read (const std::string & fname) // Catch the Elem pointer that the Mesh throws back Elem * elem = mesh.add_elem(std::move(uelem)); - // If the Mesh assigned an ID different from what is in the - // Exodus file, we should probably error. - libmesh_error_msg_if(elem->id() != static_cast(exodus_id-1), + // If the _set_unique_ids_from_maps flag is true, then set the + // unique_id for "elem" based on the (zero-based version of) + // elem_num_map[j] value. + if (_set_unique_ids_from_maps) + { + // Use the elem_num_map to get a 1-based Exodus Node ID + int exodus_mapped_id = exio_helper->elem_num_map[j]; + + // Exodus ids are always 1-based while libmesh ids are always + // 0-based, so to make a libmesh unique_id here, we subtract 1 + // from the exodus_mapped_id to make it 0-based. + auto exodus_mapped_id_zero_based = + cast_int(exodus_mapped_id - 1); + + // Set elem's unique_id to "exodus_mapped_id_zero_based". + elem->set_unique_id(cast_int(exodus_mapped_id_zero_based)); + + // Normally the Mesh is responsible for setting the unique_ids + // of Nodes/Elems in a consistent manner, so when we set the unique_id + // of a Node/Elem manually based on the {node,elem}_num_map, we need to + // make sure that the "next" unique id assigned by the Mesh + // will still be valid. We do this by making sure that the + // next_unique_id is greater than the one we set manually. The + // APIs for doing this are only defined when unique ids are + // enabled. +#ifdef LIBMESH_ENABLE_UNIQUE_ID + unique_id_type next_unique_id = mesh.next_unique_id(); + mesh.set_next_unique_id(std::max(next_unique_id, exodus_mapped_id_zero_based + 1)); +#endif + } + + // If the Mesh assigned an ID different from the one we + // tried to give it, we should probably error. + libmesh_error_msg_if(elem->id() != static_cast(libmesh_elem_id), "Error! Mesh assigned ID " << elem->id() << " which is different from the (zero-based) Exodus ID " - << exodus_id-1 + << libmesh_elem_id << "!"); // Assign extra integer IDs From d1be057868dc04faa826add077d424349d83eddb Mon Sep 17 00:00:00 2001 From: John Peterson Date: Mon, 3 Nov 2025 14:03:37 -0600 Subject: [PATCH 29/46] Call ExodusII_IO_Helper::get_libmesh_elem_id() while reading sidesets --- src/mesh/exodusII_io.C | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/mesh/exodusII_io.C b/src/mesh/exodusII_io.C index 33ab3b248a3..bfde40247a0 100644 --- a/src/mesh/exodusII_io.C +++ b/src/mesh/exodusII_io.C @@ -773,15 +773,10 @@ void ExodusII_IO::read (const std::string & fname) for (auto e : index_range(exio_helper->elem_list)) { - // The numbers in the Exodus file sidesets should be thought - // of as (1-based) indices into the elem_num_map array. So, - // to get the right element ID we have to: - // 1.) Subtract 1 from elem_list[e] (to get a zero-based index) - // 2.) Pass it through elem_num_map (to get the corresponding Exodus ID) - // 3.) Subtract 1 from that, since libmesh is "zero"-based, - // even when the Exodus numbering doesn't start with 1. + // Call helper function to get the libmesh Elem id for the + // e'th entry in the current elem_list. dof_id_type libmesh_elem_id = - cast_int(exio_helper->elem_num_map[exio_helper->elem_list[e] - 1] - 1); + exio_helper->get_libmesh_elem_id(exio_helper->elem_list[e]); // Set any relevant node/edge maps for this element Elem & elem = mesh.elem_ref(libmesh_elem_id); From 2df723774aadf62b88e628b8c88ff11bf9566212 Mon Sep 17 00:00:00 2001 From: John Peterson Date: Mon, 3 Nov 2025 14:06:39 -0600 Subject: [PATCH 30/46] Call ExodusII_IO_Helper::get_libmesh_elem_id() while reading elemsets --- src/mesh/exodusII_io.C | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/src/mesh/exodusII_io.C b/src/mesh/exodusII_io.C index bfde40247a0..61934cd8dd3 100644 --- a/src/mesh/exodusII_io.C +++ b/src/mesh/exodusII_io.C @@ -869,14 +869,10 @@ void ExodusII_IO::read (const std::string & fname) std::map elem_to_elemsets; for (auto e : index_range(exio_helper->elemset_list)) { - // Follow standard (see sideset case above) approach for - // converting the ids stored in the elemset_list to - // libmesh Elem ids. - // - // TODO: this should be moved to a helper function so we - // don't duplicate the code. - dof_id_type libmesh_elem_id = - cast_int(exio_helper->elem_num_map[exio_helper->elemset_list[e] - 1] - 1); + // Call helper function to get the libmesh Elem id for the + // e'th entry in the current elemset_list. + dof_id_type libmesh_elem_id = + exio_helper->get_libmesh_elem_id(exio_helper->elemset_list[e]); // Get a pointer to this Elem Elem * elem = mesh.elem_ptr(libmesh_elem_id); From 7bc10819688e7ed848f95b35bdd31e3a5aec99ff Mon Sep 17 00:00:00 2001 From: John Peterson Date: Mon, 3 Nov 2025 14:25:19 -0600 Subject: [PATCH 31/46] Move code that sets Elem unique_ids to separate function --- include/mesh/exodusII_io_helper.h | 10 +++++++++ src/mesh/exodusII_io.C | 33 +++--------------------------- src/mesh/exodusII_io_helper.C | 34 +++++++++++++++++++++++++++++++ 3 files changed, 47 insertions(+), 30 deletions(-) diff --git a/include/mesh/exodusII_io_helper.h b/include/mesh/exodusII_io_helper.h index f69a584a427..bc406cd9fce 100644 --- a/include/mesh/exodusII_io_helper.h +++ b/include/mesh/exodusII_io_helper.h @@ -314,6 +314,16 @@ class ExodusII_IO_Helper : public ParallelObject dof_id_type get_libmesh_node_id(int exodus_node_id); dof_id_type get_libmesh_elem_id(int exodus_elem_id); + /** + * Helper function that sets the unique_id of the passed-in Elem if + * _set_unique_ids_from_maps == true, does nothing if that flag is + * false. The input index is assumed to be a zero-based index into + * the elem_num_map array. + */ + void set_elem_unique_id(MeshBase & mesh, + Elem * elem, + int zero_based_elem_num_map_index); + /** * Opens an \p ExodusII mesh file named \p filename for writing. */ diff --git a/src/mesh/exodusII_io.C b/src/mesh/exodusII_io.C index 61934cd8dd3..b9de5b61dd1 100644 --- a/src/mesh/exodusII_io.C +++ b/src/mesh/exodusII_io.C @@ -508,36 +508,9 @@ void ExodusII_IO::read (const std::string & fname) // Catch the Elem pointer that the Mesh throws back Elem * elem = mesh.add_elem(std::move(uelem)); - // If the _set_unique_ids_from_maps flag is true, then set the - // unique_id for "elem" based on the (zero-based version of) - // elem_num_map[j] value. - if (_set_unique_ids_from_maps) - { - // Use the elem_num_map to get a 1-based Exodus Node ID - int exodus_mapped_id = exio_helper->elem_num_map[j]; - - // Exodus ids are always 1-based while libmesh ids are always - // 0-based, so to make a libmesh unique_id here, we subtract 1 - // from the exodus_mapped_id to make it 0-based. - auto exodus_mapped_id_zero_based = - cast_int(exodus_mapped_id - 1); - - // Set elem's unique_id to "exodus_mapped_id_zero_based". - elem->set_unique_id(cast_int(exodus_mapped_id_zero_based)); - - // Normally the Mesh is responsible for setting the unique_ids - // of Nodes/Elems in a consistent manner, so when we set the unique_id - // of a Node/Elem manually based on the {node,elem}_num_map, we need to - // make sure that the "next" unique id assigned by the Mesh - // will still be valid. We do this by making sure that the - // next_unique_id is greater than the one we set manually. The - // APIs for doing this are only defined when unique ids are - // enabled. -#ifdef LIBMESH_ENABLE_UNIQUE_ID - unique_id_type next_unique_id = mesh.next_unique_id(); - mesh.set_next_unique_id(std::max(next_unique_id, exodus_mapped_id_zero_based + 1)); -#endif - } + // If the _set_unique_ids_from_maps flag is true, set the + // unique_id for "elem", otherwise do nothing. + exio_helper->set_elem_unique_id(mesh, elem, j); // If the Mesh assigned an ID different from the one we // tried to give it, we should probably error. diff --git a/src/mesh/exodusII_io_helper.C b/src/mesh/exodusII_io_helper.C index 21377fc8187..1083ac247a4 100644 --- a/src/mesh/exodusII_io_helper.C +++ b/src/mesh/exodusII_io_helper.C @@ -2212,6 +2212,40 @@ dof_id_type ExodusII_IO_Helper::get_libmesh_elem_id(int exodus_elem_id) return libmesh_elem_id; } +void +ExodusII_IO_Helper::set_elem_unique_id(MeshBase & mesh, + Elem * elem, + int zero_based_elem_num_map_index) +{ + if (this->set_unique_ids_from_maps) + { + // Use the elem_num_map to get a 1-based Exodus Node ID + int exodus_mapped_id = this->elem_num_map[zero_based_elem_num_map_index]; + + // Exodus ids are always 1-based while libmesh ids are always + // 0-based, so to make a libmesh unique_id here, we subtract 1 + // from the exodus_mapped_id to make it 0-based. + auto exodus_mapped_id_zero_based = + cast_int(exodus_mapped_id - 1); + + // Set elem's unique_id to "exodus_mapped_id_zero_based". + elem->set_unique_id(cast_int(exodus_mapped_id_zero_based)); + + // Normally the Mesh is responsible for setting the unique_ids + // of Nodes/Elems in a consistent manner, so when we set the unique_id + // of a Node/Elem manually based on the {node,elem}_num_map, we need to + // make sure that the "next" unique id assigned by the Mesh + // will still be valid. We do this by making sure that the + // next_unique_id is greater than the one we set manually. The + // APIs for doing this are only defined when unique ids are + // enabled. +#ifdef LIBMESH_ENABLE_UNIQUE_ID + unique_id_type next_unique_id = mesh.next_unique_id(); + mesh.set_next_unique_id(std::max(next_unique_id, exodus_mapped_id_zero_based + 1)); +#endif + } +} + // For Writing Solutions void ExodusII_IO_Helper::create(std::string filename) From ee0c7801883f3f86e0681ae76d74f2b077e7a346 Mon Sep 17 00:00:00 2001 From: John Peterson Date: Mon, 3 Nov 2025 15:06:19 -0600 Subject: [PATCH 32/46] Add/use ExodusII_IO_Helper::set_node_unique_id() --- include/mesh/exodusII_io_helper.h | 17 +++++++------- src/mesh/exodusII_io.C | 35 +++------------------------- src/mesh/exodusII_io_helper.C | 38 ++++++++++++++++++++++++++++--- 3 files changed, 47 insertions(+), 43 deletions(-) diff --git a/include/mesh/exodusII_io_helper.h b/include/mesh/exodusII_io_helper.h index bc406cd9fce..ed2fc0bcf13 100644 --- a/include/mesh/exodusII_io_helper.h +++ b/include/mesh/exodusII_io_helper.h @@ -315,14 +315,15 @@ class ExodusII_IO_Helper : public ParallelObject dof_id_type get_libmesh_elem_id(int exodus_elem_id); /** - * Helper function that sets the unique_id of the passed-in Elem if - * _set_unique_ids_from_maps == true, does nothing if that flag is - * false. The input index is assumed to be a zero-based index into - * the elem_num_map array. - */ - void set_elem_unique_id(MeshBase & mesh, - Elem * elem, - int zero_based_elem_num_map_index); + * Helper function that sets the unique_id of the passed-in + * Node/Elem if _set_unique_ids_from_maps == true, does nothing if + * that flag is false. The input index is assumed to be a zero-based + * index into the {node,elem}_num_map array. + */ + void set_node_unique_id( + MeshBase & mesh, Node * node, int zero_based_node_num_map_index); + void set_elem_unique_id( + MeshBase & mesh, Elem * elem, int zero_based_elem_num_map_index); /** * Opens an \p ExodusII mesh file named \p filename for writing. diff --git a/src/mesh/exodusII_io.C b/src/mesh/exodusII_io.C index b9de5b61dd1..e4f6351a8d5 100644 --- a/src/mesh/exodusII_io.C +++ b/src/mesh/exodusII_io.C @@ -347,38 +347,9 @@ void ExodusII_IO::read (const std::string & fname) << libmesh_node_id << "!"); - // If the _set_unique_ids_from_maps flag is true, then set the - // unique_id for "added_node" based on the (zero-based version of) - // node_num_map[i] value. - if (_set_unique_ids_from_maps) - { - // Use the node_num_map to get a 1-based Exodus Node ID - int exodus_mapped_id = exio_helper->node_num_map[i]; - - // Exodus ids are always 1-based while libmesh ids are always - // 0-based, so to make a libmesh unique_id here, we subtract 1 - // from the exodus_mapped_id to make it 0-based. - auto exodus_mapped_id_zero_based = - cast_int(exodus_mapped_id - 1); - - // Set added_node's unique_id to "exodus_mapped_id_zero_based". - added_node->set_unique_id(cast_int(exodus_mapped_id_zero_based)); - - // Normally the Mesh is responsible for setting the unique_ids - // of Nodes in a consistent manner, so when we set the unique_id - // of a Node manually based on the node_num_map, we need to - // make sure that the "next" unique id assigned by the Mesh - // will still be valid. We do this by making sure that the - // next_unique_id is greater than the one we set manually. The - // APIs for doing this are only defined when unique ids are - // enabled. - // Note: it's not generally safe to call mesh.parallel_max_unique_id() - // here because the Exodus reader might only be called on one processor. -#ifdef LIBMESH_ENABLE_UNIQUE_ID - unique_id_type next_unique_id = mesh.next_unique_id(); - mesh.set_next_unique_id(std::max(next_unique_id, exodus_mapped_id_zero_based + 1)); -#endif - } + // If the _set_unique_ids_from_maps flag is true, set the + // unique_id for "node", otherwise do nothing. + exio_helper->set_node_unique_id(mesh, added_node, i); // If we have a set of spline weights, these nodes are going to // be used as control points for Bezier elements, and we need diff --git a/src/mesh/exodusII_io_helper.C b/src/mesh/exodusII_io_helper.C index 1083ac247a4..91b8ed0a29d 100644 --- a/src/mesh/exodusII_io_helper.C +++ b/src/mesh/exodusII_io_helper.C @@ -2213,9 +2213,41 @@ dof_id_type ExodusII_IO_Helper::get_libmesh_elem_id(int exodus_elem_id) } void -ExodusII_IO_Helper::set_elem_unique_id(MeshBase & mesh, - Elem * elem, - int zero_based_elem_num_map_index) +ExodusII_IO_Helper::set_node_unique_id( + MeshBase & mesh, Node * node, int zero_based_node_num_map_index) +{ + if (this->set_unique_ids_from_maps) + { + // Use the node_num_map to get a 1-based Exodus Node ID + int exodus_mapped_id = this->node_num_map[zero_based_node_num_map_index]; + + // Exodus ids are always 1-based while libmesh ids are always + // 0-based, so to make a libmesh unique_id here, we subtract 1 + // from the exodus_mapped_id to make it 0-based. + auto exodus_mapped_id_zero_based = + cast_int(exodus_mapped_id - 1); + + // Set added_node's unique_id to "exodus_mapped_id_zero_based". + node->set_unique_id(cast_int(exodus_mapped_id_zero_based)); + + // Normally the Mesh is responsible for setting the unique_ids + // of Nodes/Elems in a consistent manner, so when we set the unique_id + // of a Node/Elem manually based on the {node,elem}_num_map, we need to + // make sure that the "next" unique id assigned by the Mesh + // will still be valid. We do this by making sure that the + // next_unique_id is greater than the one we set manually. The + // APIs for doing this are only defined when unique ids are + // enabled. +#ifdef LIBMESH_ENABLE_UNIQUE_ID + unique_id_type next_unique_id = mesh.next_unique_id(); + mesh.set_next_unique_id(std::max(next_unique_id, exodus_mapped_id_zero_based + 1)); +#endif + } +} + +void +ExodusII_IO_Helper::set_elem_unique_id( + MeshBase & mesh, Elem * elem, int zero_based_elem_num_map_index) { if (this->set_unique_ids_from_maps) { From 44c6974ed1f61ec7fc939b4f44f6715136c4531b Mon Sep 17 00:00:00 2001 From: John Peterson Date: Mon, 3 Nov 2025 15:22:31 -0600 Subject: [PATCH 33/46] Add private ExodusII_IO_Helper::set_dof_object_unique_id() implementation function --- include/mesh/exodusII_io_helper.h | 13 +++++++++ src/mesh/exodusII_io_helper.C | 45 +++++++++---------------------- 2 files changed, 26 insertions(+), 32 deletions(-) diff --git a/include/mesh/exodusII_io_helper.h b/include/mesh/exodusII_io_helper.h index ed2fc0bcf13..0cfbe7e4ee7 100644 --- a/include/mesh/exodusII_io_helper.h +++ b/include/mesh/exodusII_io_helper.h @@ -61,6 +61,7 @@ namespace libMesh // Forward declarations class MeshBase; +class DofObject; /** * This is the \p ExodusII_IO_Helper class. This class hides the @@ -325,6 +326,18 @@ class ExodusII_IO_Helper : public ParallelObject void set_elem_unique_id( MeshBase & mesh, Elem * elem, int zero_based_elem_num_map_index); +private: + + /** + * Internal implementation for the two functions above. + */ + void set_dof_object_unique_id( + MeshBase & mesh, + DofObject * dof_object, + int exodus_mapped_id); + +public: + /** * Opens an \p ExodusII mesh file named \p filename for writing. */ diff --git a/src/mesh/exodusII_io_helper.C b/src/mesh/exodusII_io_helper.C index 91b8ed0a29d..5bf6c9c709a 100644 --- a/src/mesh/exodusII_io_helper.C +++ b/src/mesh/exodusII_io_helper.C @@ -2216,52 +2216,32 @@ void ExodusII_IO_Helper::set_node_unique_id( MeshBase & mesh, Node * node, int zero_based_node_num_map_index) { - if (this->set_unique_ids_from_maps) - { - // Use the node_num_map to get a 1-based Exodus Node ID - int exodus_mapped_id = this->node_num_map[zero_based_node_num_map_index]; - - // Exodus ids are always 1-based while libmesh ids are always - // 0-based, so to make a libmesh unique_id here, we subtract 1 - // from the exodus_mapped_id to make it 0-based. - auto exodus_mapped_id_zero_based = - cast_int(exodus_mapped_id - 1); - - // Set added_node's unique_id to "exodus_mapped_id_zero_based". - node->set_unique_id(cast_int(exodus_mapped_id_zero_based)); - - // Normally the Mesh is responsible for setting the unique_ids - // of Nodes/Elems in a consistent manner, so when we set the unique_id - // of a Node/Elem manually based on the {node,elem}_num_map, we need to - // make sure that the "next" unique id assigned by the Mesh - // will still be valid. We do this by making sure that the - // next_unique_id is greater than the one we set manually. The - // APIs for doing this are only defined when unique ids are - // enabled. -#ifdef LIBMESH_ENABLE_UNIQUE_ID - unique_id_type next_unique_id = mesh.next_unique_id(); - mesh.set_next_unique_id(std::max(next_unique_id, exodus_mapped_id_zero_based + 1)); -#endif - } + this->set_dof_object_unique_id(mesh, node, this->node_num_map[zero_based_node_num_map_index]); } void ExodusII_IO_Helper::set_elem_unique_id( MeshBase & mesh, Elem * elem, int zero_based_elem_num_map_index) +{ + this->set_dof_object_unique_id(mesh, elem, this->elem_num_map[zero_based_elem_num_map_index]); +} + +void +ExodusII_IO_Helper::set_dof_object_unique_id( + MeshBase & mesh, + DofObject * dof_object, + int exodus_mapped_id) { if (this->set_unique_ids_from_maps) { - // Use the elem_num_map to get a 1-based Exodus Node ID - int exodus_mapped_id = this->elem_num_map[zero_based_elem_num_map_index]; - // Exodus ids are always 1-based while libmesh ids are always // 0-based, so to make a libmesh unique_id here, we subtract 1 // from the exodus_mapped_id to make it 0-based. auto exodus_mapped_id_zero_based = cast_int(exodus_mapped_id - 1); - // Set elem's unique_id to "exodus_mapped_id_zero_based". - elem->set_unique_id(cast_int(exodus_mapped_id_zero_based)); + // Set added_node's unique_id to "exodus_mapped_id_zero_based". + dof_object->set_unique_id(cast_int(exodus_mapped_id_zero_based)); // Normally the Mesh is responsible for setting the unique_ids // of Nodes/Elems in a consistent manner, so when we set the unique_id @@ -2278,6 +2258,7 @@ ExodusII_IO_Helper::set_elem_unique_id( } } + // For Writing Solutions void ExodusII_IO_Helper::create(std::string filename) From a31cc2e0eec17c2bc943f22c4860321d8bbcee68 Mon Sep 17 00:00:00 2001 From: John Peterson Date: Mon, 3 Nov 2025 15:32:41 -0600 Subject: [PATCH 34/46] Use libmesh_vector_at macro for bounds checking --- src/mesh/exodusII_io_helper.C | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/mesh/exodusII_io_helper.C b/src/mesh/exodusII_io_helper.C index 5bf6c9c709a..e21cb78e9fb 100644 --- a/src/mesh/exodusII_io_helper.C +++ b/src/mesh/exodusII_io_helper.C @@ -2216,14 +2216,14 @@ void ExodusII_IO_Helper::set_node_unique_id( MeshBase & mesh, Node * node, int zero_based_node_num_map_index) { - this->set_dof_object_unique_id(mesh, node, this->node_num_map[zero_based_node_num_map_index]); + this->set_dof_object_unique_id(mesh, node, libmesh_vector_at(this->node_num_map, zero_based_node_num_map_index)); } void ExodusII_IO_Helper::set_elem_unique_id( MeshBase & mesh, Elem * elem, int zero_based_elem_num_map_index) { - this->set_dof_object_unique_id(mesh, elem, this->elem_num_map[zero_based_elem_num_map_index]); + this->set_dof_object_unique_id(mesh, elem, libmesh_vector_at(this->elem_num_map, zero_based_elem_num_map_index)); } void From c4a1ae9136800919e90d86c60b765e461fedc779 Mon Sep 17 00:00:00 2001 From: John Peterson Date: Mon, 3 Nov 2025 15:53:07 -0600 Subject: [PATCH 35/46] Fix indentation level --- src/mesh/exodusII_io_helper.C | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/mesh/exodusII_io_helper.C b/src/mesh/exodusII_io_helper.C index e21cb78e9fb..5871e689a8b 100644 --- a/src/mesh/exodusII_io_helper.C +++ b/src/mesh/exodusII_io_helper.C @@ -2979,9 +2979,9 @@ void ExodusII_IO_Helper::write_elements(const MeshBase & mesh, bool use_disconti // element ids to add. if (subdomain_id < subdomain_id_end) { - connect.resize(element_id_vec.size()*num_nodes_per_elem); + connect.resize(element_id_vec.size()*num_nodes_per_elem); - for (auto i : index_range(element_id_vec)) + for (auto i : index_range(element_id_vec)) { unsigned int elem_id = element_id_vec[i]; libmesh_elem_num_to_exodus[elem_id] = ++libmesh_elem_num_to_exodus_counter; // 1-based indexing for Exodus @@ -3029,8 +3029,8 @@ void ExodusII_IO_Helper::write_elements(const MeshBase & mesh, bool use_disconti libmesh_map_find(discontinuous_node_indices, std::make_pair(elem_id, elem_node_index)); } - } - } + } // end for(j) + } // end for(i) // This transform command stores its result in a range that // begins at the third argument, so this command is adding @@ -3043,10 +3043,10 @@ void ExodusII_IO_Helper::write_elements(const MeshBase & mesh, bool use_disconti curr_elem_map_end, [](dof_id_type id){return id+1;}); } - // If this is a "fake" block of added sides, we build those as - // we go. - else + else // subdomain_id >= subdomain_id_end { + // If this is a "fake" block of added sides, we build those as + // we go. libmesh_assert(_add_sides); libmesh_assert(num_elem_this_blk_it != num_elem_this_blk_vec.end()); From 9f59bea09dc0b98b36fb6ceb32ecf5a695394591 Mon Sep 17 00:00:00 2001 From: John Peterson Date: Mon, 3 Nov 2025 16:23:41 -0600 Subject: [PATCH 36/46] Support set_unique_ids_from_maps flag when writing elem_num_map --- include/mesh/mesh_base.h | 2 +- src/mesh/exodusII_io_helper.C | 47 ++++++++++++++++++++--------------- 2 files changed, 28 insertions(+), 21 deletions(-) diff --git a/include/mesh/mesh_base.h b/include/mesh/mesh_base.h index c539616a510..7e9c8a8edab 100644 --- a/include/mesh/mesh_base.h +++ b/include/mesh/mesh_base.h @@ -458,7 +458,7 @@ class MeshBase : public ParallelObject /** * \returns The next unique id to be used. */ - unique_id_type next_unique_id() { return _next_unique_id; } + unique_id_type next_unique_id() const { return _next_unique_id; } /** * Sets the next available unique id to be used. On a diff --git a/src/mesh/exodusII_io_helper.C b/src/mesh/exodusII_io_helper.C index 5871e689a8b..6cf460d3d6e 100644 --- a/src/mesh/exodusII_io_helper.C +++ b/src/mesh/exodusII_io_helper.C @@ -2754,8 +2754,9 @@ void ExodusII_IO_Helper::write_elements(const MeshBase & mesh, bool use_disconti ++counter; } - elem_num_map.resize(num_elem); - std::vector::iterator curr_elem_map_end = elem_num_map.begin(); + // Here we reserve() space so that we can push_back() onto the + // elem_num_map in the loops below. + this->elem_num_map.reserve(num_elem); // In the case of discontinuous plotting we initialize a map from // (element, node) pairs to the corresponding discontinuous node index. @@ -2958,7 +2959,16 @@ void ExodusII_IO_Helper::write_elements(const MeshBase & mesh, bool use_disconti // We need these later if we're adding fake sides, but we don't need // to recalculate it. auto num_elem_this_blk_it = num_elem_this_blk_vec.begin(); + + // We write "fake" ids to the elem_num_map when adding fake sides. + // I don't think it's too important exactly what fake ids are used, + // as long as they don't conflict with any other ids that are + // already in the elem_num_map. auto next_fake_id = mesh.max_elem_id() + 1; // 1-based numbering in Exodus +#ifdef LIBMESH_ENABLE_UNIQUE_ID + if (this->set_unique_ids_from_maps) + next_fake_id = mesh.next_unique_id(); +#endif for (auto & [subdomain_id, element_id_vec] : subdomain_map) { @@ -3030,18 +3040,16 @@ void ExodusII_IO_Helper::write_elements(const MeshBase & mesh, bool use_disconti std::make_pair(elem_id, elem_node_index)); } } // end for(j) - } // end for(i) - // This transform command stores its result in a range that - // begins at the third argument, so this command is adding - // values to the elem_num_map vector starting from - // curr_elem_map_end. Here we add 1 to each id to make a - // 1-based exodus file. - curr_elem_map_end = std::transform - (element_id_vec.begin(), - element_id_vec.end(), - curr_elem_map_end, - [](dof_id_type id){return id+1;}); + // push_back() either elem_id+1 or the current Elem's + // unique_id+1 into the elem_num_map, depending on the value + // of the set_unique_ids_from_maps flag. + if (this->set_unique_ids_from_maps) + this->elem_num_map.push_back(elem.unique_id() + 1); + else + this->elem_num_map.push_back(elem_id + 1); + + } // end for(i) } else // subdomain_id >= subdomain_id_end { @@ -3082,12 +3090,11 @@ void ExodusII_IO_Helper::write_elements(const MeshBase & mesh, bool use_disconti } } - auto old_curr_map_end = curr_elem_map_end; - curr_elem_map_end += num_elem_this_blk; - - std::generate - (old_curr_map_end, curr_elem_map_end, - [&next_fake_id](){return next_fake_id++;}); + // Store num_elem_this_blk "fake" ids into the + // elem_num_map. Use a traditional for-loop to avoid unused + // variable warnings about the loop counter. + for (unsigned int i=0; ielem_num_map.push_back(next_fake_id++); } ++num_elem_this_blk_it; @@ -3100,7 +3107,7 @@ void ExodusII_IO_Helper::write_elements(const MeshBase & mesh, bool use_disconti nullptr, // elem_edge_conn (unused) nullptr); // elem_face_conn (unused) EX_CHECK_ERR(ex_err, "Error writing element connectivities"); - } + } // end for (auto & [subdomain_id, element_id_vec] : subdomain_map) // write out the element number map that we created ex_err = exII::ex_put_elem_num_map(ex_id, elem_num_map.data()); From 5d7c4425a3239008b2687cafd6607fe83aeabc3a Mon Sep 17 00:00:00 2001 From: John Peterson Date: Mon, 3 Nov 2025 16:52:22 -0600 Subject: [PATCH 37/46] Use ExodusII_IO_Helper::get_libmesh_elem_id() in read_elemental_var_values() --- src/mesh/exodusII_io_helper.C | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/mesh/exodusII_io_helper.C b/src/mesh/exodusII_io_helper.C index 6cf460d3d6e..9291a5a4fcc 100644 --- a/src/mesh/exodusII_io_helper.C +++ b/src/mesh/exodusII_io_helper.C @@ -2135,12 +2135,14 @@ void ExodusII_IO_Helper::read_elemental_var_values(std::string elemental_var_nam for (unsigned j=0; j(num_elem_this_blk); j++) { - // Use the elem_num_map to obtain the ID of this element in the Exodus file, - // and remember to subtract 1 since libmesh is zero-based and Exodus is 1-based. - unsigned mapped_elem_id = this->elem_num_map[ex_el_num] - 1; + // Determine the libmesh id of the element with zero-based + // index "ex_el_num". This function expects a one-based + // index, so we add 1 to ex_el_num when we pass it in. + auto libmesh_elem_id = + this->get_libmesh_elem_id(ex_el_num + 1); // Store the elemental value in the map. - elem_var_value_map[mapped_elem_id] = block_elem_var_values[j]; + elem_var_value_map[libmesh_elem_id] = block_elem_var_values[j]; // Go to the next sequential element ID. ex_el_num++; From 3a4bfe41d92c94e9e6b24fd6202bdc1e12ad26a6 Mon Sep 17 00:00:00 2001 From: John Peterson Date: Tue, 4 Nov 2025 09:46:18 -0600 Subject: [PATCH 38/46] Add ExodusII_IO_Helper::get_libmesh_id() --- include/mesh/exodusII_io_helper.h | 6 +++++- src/mesh/exodusII_io_helper.C | 31 +++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/include/mesh/exodusII_io_helper.h b/include/mesh/exodusII_io_helper.h index 0cfbe7e4ee7..ac095a74626 100644 --- a/include/mesh/exodusII_io_helper.h +++ b/include/mesh/exodusII_io_helper.h @@ -329,8 +329,12 @@ class ExodusII_IO_Helper : public ParallelObject private: /** - * Internal implementation for the two functions above. + * Internal implementation for the two sets of functions above. */ + dof_id_type get_libmesh_id( + int exodus_id, + const std::vector & num_map); + void set_dof_object_unique_id( MeshBase & mesh, DofObject * dof_object, diff --git a/src/mesh/exodusII_io_helper.C b/src/mesh/exodusII_io_helper.C index 9291a5a4fcc..43a0eec7654 100644 --- a/src/mesh/exodusII_io_helper.C +++ b/src/mesh/exodusII_io_helper.C @@ -2214,6 +2214,37 @@ dof_id_type ExodusII_IO_Helper::get_libmesh_elem_id(int exodus_elem_id) return libmesh_elem_id; } +dof_id_type +ExodusII_IO_Helper::get_libmesh_id(int exodus_id, + const std::vector & num_map) +{ + // The input exodus_id is assumed to be a (1-based) index into + // the {node,elem}_num_map, so in order to use exodus_id as an index + // in C++, we need to first make it zero-based. + auto exodus_id_zero_based = + cast_int(exodus_id - 1); + + // Throw an informative error message rather than accessing past the + // end of the provided num_map. If we are setting Elem unique_ids + // based on the num_map, we don't need to do this check. + if (!this->set_unique_ids_from_maps) + libmesh_error_msg_if(exodus_id_zero_based >= num_map.size(), + "Cannot get LibMesh id for Exodus id: " << exodus_id); + + // If the user set the flag which stores Exodus node/elem ids as + // unique_ids instead of regular ids, then the libmesh id we are + // looking for is actually just "exodus_id_zero_based". Otherwise, + // we need to look up the Node/Elem's id in the provided num_map, + // *and* then subtract 1 from that because the entries in the + // num_map are also 1-based. + dof_id_type libmesh_id = + this->set_unique_ids_from_maps ? + cast_int(exodus_id_zero_based) : + cast_int(num_map[exodus_id_zero_based] - 1); + + return libmesh_id; +} + void ExodusII_IO_Helper::set_node_unique_id( MeshBase & mesh, Node * node, int zero_based_node_num_map_index) From 84fb1a8651022c4ed84db74a1732dd4681052d7a Mon Sep 17 00:00:00 2001 From: John Peterson Date: Tue, 4 Nov 2025 09:51:10 -0600 Subject: [PATCH 39/46] Call ExodusII_IO_Helper::get_libmesh_id() from ExodusII_IO_Helper::get_libmesh_{node,elem}_id() --- src/mesh/exodusII_io_helper.C | 58 +++-------------------------------- 1 file changed, 4 insertions(+), 54 deletions(-) diff --git a/src/mesh/exodusII_io_helper.C b/src/mesh/exodusII_io_helper.C index 43a0eec7654..698d221936b 100644 --- a/src/mesh/exodusII_io_helper.C +++ b/src/mesh/exodusII_io_helper.C @@ -2154,64 +2154,12 @@ void ExodusII_IO_Helper::read_elemental_var_values(std::string elemental_var_nam dof_id_type ExodusII_IO_Helper::get_libmesh_node_id(int exodus_node_id) { - // The input exodus_node_id is a (1-based) index into the - // node_num_map, so in order to use exodus_node_id as an index in - // C++, we need to first make it zero-based. - auto exodus_node_id_zero_based = - cast_int(exodus_node_id - 1); - - // Throw an informative error message rather than accessing past the - // end of the node_num_map. If we are setting Node unique_ids from - // the node_num_map, we don't need to do this check. - if (!this->set_unique_ids_from_maps) - libmesh_error_msg_if(exodus_node_id_zero_based >= this->node_num_map.size(), - "Cannot get LibMesh node id for Exodus node id: " << exodus_node_id); - - // If the user set the flag which stores Exodus node - // ids as unique_ids instead of regular ids, then - // the libmesh node id we are looking for is - // actually just "exodus_node_id_zero_based". Otherwise, we - // need to look up the Node's id in the node_num_map, - // *and* then subtract 1 from that because the entries - // in the node_num_map are also 1-based. - dof_id_type libmesh_node_id = - this->set_unique_ids_from_maps ? - cast_int(exodus_node_id_zero_based) : - cast_int(this->node_num_map[exodus_node_id_zero_based] - 1); - - return libmesh_node_id; + return this->get_libmesh_id(exodus_node_id, this->node_num_map); } - - dof_id_type ExodusII_IO_Helper::get_libmesh_elem_id(int exodus_elem_id) { - // The input exodus_elem_id is assumed to be a (1-based) index into - // the elem_num_map, so in order to use exodus_elem_id as an index - // in C++, we need to first make it zero-based. - auto exodus_elem_id_zero_based = - cast_int(exodus_elem_id - 1); - - // Throw an informative error message rather than accessing past the - // end of the elem_num_map. If we are setting Elem unique_ids from - // the elem_num_map, we don't need to do this check. - if (!this->set_unique_ids_from_maps) - libmesh_error_msg_if(exodus_elem_id_zero_based >= this->elem_num_map.size(), - "Cannot get LibMesh elem id for Exodus elem id: " << exodus_elem_id); - - // If the user set the flag which stores Exodus elem - // ids as unique_ids instead of regular ids, then - // the libmesh elem id we are looking for is - // actually just "exodus_elem_id_zero_based". Otherwise, we - // need to look up the Elem's id in the elem_num_map, - // *and* then subtract 1 from that because the entries - // in the elem_num_map are also 1-based. - dof_id_type libmesh_elem_id = - this->set_unique_ids_from_maps ? - cast_int(exodus_elem_id_zero_based) : - cast_int(this->elem_num_map[exodus_elem_id_zero_based] - 1); - - return libmesh_elem_id; + return this->get_libmesh_id(exodus_elem_id, this->elem_num_map); } dof_id_type @@ -2245,6 +2193,8 @@ ExodusII_IO_Helper::get_libmesh_id(int exodus_id, return libmesh_id; } + + void ExodusII_IO_Helper::set_node_unique_id( MeshBase & mesh, Node * node, int zero_based_node_num_map_index) From b3d98f4d12aa1984be28a6db5f8dc3cbedabc846 Mon Sep 17 00:00:00 2001 From: John Peterson Date: Tue, 4 Nov 2025 10:21:50 -0600 Subject: [PATCH 40/46] Rename unit test --- tests/mesh/mesh_input.C | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/mesh/mesh_input.C b/tests/mesh/mesh_input.C index 6121ae27b1a..9b41f1192cb 100644 --- a/tests/mesh/mesh_input.C +++ b/tests/mesh/mesh_input.C @@ -112,7 +112,7 @@ public: CPPUNIT_TEST( testExodusCopyNodalSolutionReplicated ); CPPUNIT_TEST( testExodusCopyElementSolutionReplicated ); CPPUNIT_TEST( testExodusReadHeader ); - CPPUNIT_TEST( testExodusSetUniqueIdsFromMaps ); + CPPUNIT_TEST( testExodusSetNodeUniqueIdsFromMaps ); #if LIBMESH_DIM > 2 CPPUNIT_TEST( testExodusIGASidesets ); CPPUNIT_TEST( testLowOrderEdgeBlocks ); @@ -335,7 +335,7 @@ public: #ifdef LIBMESH_HAVE_EXODUS_API - void testExodusSetUniqueIdsFromMaps_implementation( + void testExodusSetNodeUniqueIdsFromMaps_implementation( bool set_unique_ids, const std::vector & expected_unique_ids) { @@ -367,18 +367,18 @@ public: // unique_ids are not enabled. libmesh_ignore(set_unique_ids, expected_unique_ids); #endif // LIBMESH_ENABLE_UNIQUE_ID - } // end testExodusSetUniqueIdsFromMaps_implementation() + } // end testExodusSetNodeUniqueIdsFromMaps_implementation() - void testExodusSetUniqueIdsFromMaps() + void testExodusSetNodeUniqueIdsFromMaps() { LOG_UNIT_TEST; // node_num_map = 1, 3, 9, 8, 2, 5, 7, 6, 4 // The input is a (zero-based) version of the node_num_map as it // exists in the file. - this->testExodusSetUniqueIdsFromMaps_implementation( + this->testExodusSetNodeUniqueIdsFromMaps_implementation( /*set_unique_ids=*/true, /*expected_unique_ids=*/{0, 2, 8, 7, 1, 4, 6, 5, 3}); @@ -387,7 +387,7 @@ public: // zero-based Node id 3 corresponds to one-based Node id 4, which // appears in (zero-based) position 8 in the node_num_map, hence: // 3 -> 8, etc. - this->testExodusSetUniqueIdsFromMaps_implementation( + this->testExodusSetNodeUniqueIdsFromMaps_implementation( /*set_unique_ids=*/false, /*expected_unique_ids=*/{0, 4, 1, 8, 5, 7, 6, 3, 2}); } From 22ff09ebe8d1b7cf70c2ab68bd19fa3d62e086e7 Mon Sep 17 00:00:00 2001 From: John Peterson Date: Tue, 4 Nov 2025 10:25:09 -0600 Subject: [PATCH 41/46] Add exo file with non-trivial elem_num_map_for unit testing --- tests/Makefile.am | 1 + tests/meshes/nontrivial_elem_num_map.exo | Bin 0 -> 4680 bytes 2 files changed, 1 insertion(+) create mode 100644 tests/meshes/nontrivial_elem_num_map.exo diff --git a/tests/Makefile.am b/tests/Makefile.am index ce689e24f19..b7c9f423971 100644 --- a/tests/Makefile.am +++ b/tests/Makefile.am @@ -217,6 +217,7 @@ data = matrices/geom_1_extraction_op.h5 \ meshes/non_manifold_junction1.exo \ meshes/non_manifold_junction2.exo \ meshes/non_manifold_junction3.exo \ + meshes/nontrivial_elem_num_map.exo \ meshes/nontrivial_node_num_map.exo \ meshes/Cluster_34.stl \ meshes/engraving.stl \ diff --git a/tests/meshes/nontrivial_elem_num_map.exo b/tests/meshes/nontrivial_elem_num_map.exo new file mode 100644 index 0000000000000000000000000000000000000000..39b7778c398b65619eaa4e2522e7046fb7494ae5 GIT binary patch literal 4680 zcmeH~J#!pI5QZho((UQPJ_#Gdhe5^$%$KqGh))zM%7k_m@zzTr`xg)p z5K;UD0)7AiRZa;A2nYxW6e=VnpbFk+X8JVRE8i7lRC%i2?Vj#;dU|^1X7@hW`|!+y zZ>tg)h1Gsmq~mIo7oRxCWjzyY{k+Je-xkjH%7YP7Xjb_k(|~NKPH2tdV32n60o^5` zNxvw&+1L}_mPoK={S49-Vb%B3yZuicX;IG&Y~#GE_*oUkF3Wx{aPtECO~)t3dF+}e z9cClNb`Ek}KZ!HO*c9A+-CjC&7QdF5M~!@ zvzPZ(Z|{hF_?N4^>N7x%b91RWEYr^pI^A)qklCo{^xbx8x2^};!kT!`39Ig$9NsHK z0OD~rtgT34ezmPzdNi}UI#r$OAnZH*2Hw!f8K^aW)*}&rI4XziuW{Fdezxcg$cLPl zKHAgMkG1i?sNWoqr{)X!O!ya^w$S)hG7&$X5AMAAZ2YmfEBmE(;0qVg)z)eLp;|0{ z?bXI#lAe3tS!QsXUro3V=5g2Do{&RIaeR@fPpF9-&jVZ6_tw9cq|P=1U%V&Kuw6yb z&$p!qb&C0MO(DVm4n=%^5y^g86xn{oKH_t~K}oA^lRG1=?hm7LxN`gB_xBjAru@&` z@ao3zlIxDo1u*Cg)u(=(HEXPwx~a98BXN9xE5POaT7w(b*7{a@;_oJRgUsUoDDFl# zA6IFwvtLcF9G44oS|hI=-)S95S}xo?#NQCCMY(KZvs~tJ$^TR?{#tYK&slKED3{G! z#1D1hb|aVbf;Dp4@tw_kl9tOn?w8`8%7p=H#OLf-i*wVB(xUD`OC?@|8tk!MaxTW8%L?DOKz^Z)1htnPIg^7N~D{Cr%2 z|7``h|62m@N8VxRe6R72G8>Y23g21i_!0pd+>64K0(OADEx`ePmy4PuPzg!D>t`thUsQwNVS!$=Yo#tb;Wy3gk}xcLd_tS++dj z%!4R18*$04A+T=N%si}_^>L2LqYln*40UHV&Ia|hx)PU~aZace=V2D)p4q4a;}e0o U;@r^ZHTIP~wcl5Ly)(!E1C?Wl3;+NC literal 0 HcmV?d00001 From d9007dd05a7ec9d7fd9f1be50fe9cd674c8f193f Mon Sep 17 00:00:00 2001 From: John Peterson Date: Tue, 4 Nov 2025 10:33:13 -0600 Subject: [PATCH 42/46] Run bootstrap --- tests/Makefile.in | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/Makefile.in b/tests/Makefile.in index cdc44ec38d0..37f2b705cb6 100644 --- a/tests/Makefile.in +++ b/tests/Makefile.in @@ -2386,6 +2386,7 @@ data = matrices/geom_1_extraction_op.h5 \ meshes/non_manifold_junction1.exo \ meshes/non_manifold_junction2.exo \ meshes/non_manifold_junction3.exo \ + meshes/nontrivial_elem_num_map.exo \ meshes/nontrivial_node_num_map.exo \ meshes/Cluster_34.stl \ meshes/engraving.stl \ From 205c8be6b6c09e8781d67a0120de59f8d7d39707 Mon Sep 17 00:00:00 2001 From: John Peterson Date: Tue, 4 Nov 2025 10:41:16 -0600 Subject: [PATCH 43/46] Add testExodusSetElemUniqueIdsFromMaps() unit test --- tests/mesh/mesh_input.C | 67 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/tests/mesh/mesh_input.C b/tests/mesh/mesh_input.C index 9b41f1192cb..c08c37e6663 100644 --- a/tests/mesh/mesh_input.C +++ b/tests/mesh/mesh_input.C @@ -113,6 +113,7 @@ public: CPPUNIT_TEST( testExodusCopyElementSolutionReplicated ); CPPUNIT_TEST( testExodusReadHeader ); CPPUNIT_TEST( testExodusSetNodeUniqueIdsFromMaps ); + CPPUNIT_TEST( testExodusSetElemUniqueIdsFromMaps ); #if LIBMESH_DIM > 2 CPPUNIT_TEST( testExodusIGASidesets ); CPPUNIT_TEST( testLowOrderEdgeBlocks ); @@ -392,6 +393,72 @@ public: /*expected_unique_ids=*/{0, 4, 1, 8, 5, 7, 6, 3, 2}); } + void testExodusSetElemUniqueIdsFromMaps_implementation( + bool set_unique_ids, + const std::vector & expected_unique_ids) + { + // This test requires that libmesh is compiled with unique_ids enabled +#ifdef LIBMESH_ENABLE_UNIQUE_ID + { + ReplicatedMesh mesh(*TestCommWorld); + ExodusII_IO exii(mesh); + + // Set Node/Elem unique ids based on the node/elem_num_map + exii.set_unique_ids_from_maps(set_unique_ids); + + // Read the mesh + exii.read("meshes/nontrivial_elem_num_map.exo"); + + // Verify the results. + auto expected_it = expected_unique_ids.begin(); + for (const auto & elem : mesh.element_ptr_range()) + { + // Debugging: + // libMesh::out << "unique_id for Elem " << elem->id() + // << " = " << elem->unique_id() + // << std::endl; + + CPPUNIT_ASSERT_EQUAL(elem->unique_id(), *expected_it++); + } + } +#else + // Prevent compiler warnings about unused variables when + // unique_ids are not enabled. + libmesh_ignore(set_unique_ids, expected_unique_ids); +#endif // LIBMESH_ENABLE_UNIQUE_ID + } + + void testExodusSetElemUniqueIdsFromMaps() + { + LOG_UNIT_TEST; + + // The mesh used in this test has a non-trivial elem_num_map with + // a non-contiguous numbering that has a "gap" at the beginning + // and is also missing "16": + // elem_num_map = 11, 12, 13, 14, 15, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, + // 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, + // 45, 46 ; + { + // When we assign (zero-based) elem_num_map entries as unique_ids, the + // unique_ids are just a zero-based version of the entries above. + std::vector expected_unique_ids = { + 10, 11, 12, 13, 14, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, + 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45}; + this->testExodusSetElemUniqueIdsFromMaps_implementation( + /*set_unique_ids=*/true, expected_unique_ids); + } + + { + // When libmesh assigns unique_ids, all the Nodes (50) are + // numbered first, so the first Elem is assigned a unique_id of + // 50 and the rest are sequential from there. + std::vector expected_unique_ids(/*size=*/35); + std::iota(expected_unique_ids.begin(), expected_unique_ids.end(), /*start=*/50); + this->testExodusSetElemUniqueIdsFromMaps_implementation( + /*set_unique_ids=*/false, expected_unique_ids); + } + } + void testExodusReadHeader () { LOG_UNIT_TEST; From fc305357a5381ed55f2e56900c2c70559076306a Mon Sep 17 00:00:00 2001 From: John Peterson Date: Tue, 4 Nov 2025 12:58:25 -0600 Subject: [PATCH 44/46] Fix compilation issues identified by CI * Fix signed vs. unsiged warning * Fix ambiguous std::max() overload * Fix unused variable warning --- src/mesh/exodusII_io_helper.C | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/mesh/exodusII_io_helper.C b/src/mesh/exodusII_io_helper.C index 698d221936b..abb75a1c1f9 100644 --- a/src/mesh/exodusII_io_helper.C +++ b/src/mesh/exodusII_io_helper.C @@ -2236,7 +2236,10 @@ ExodusII_IO_Helper::set_dof_object_unique_id( // enabled. #ifdef LIBMESH_ENABLE_UNIQUE_ID unique_id_type next_unique_id = mesh.next_unique_id(); - mesh.set_next_unique_id(std::max(next_unique_id, exodus_mapped_id_zero_based + 1)); + mesh.set_next_unique_id(std::max(next_unique_id, static_cast(exodus_mapped_id_zero_based + 1))); +#else + // Avoid compiler warnings about the unused variable + libmesh_ignore(mesh); #endif } } @@ -3076,7 +3079,7 @@ void ExodusII_IO_Helper::write_elements(const MeshBase & mesh, bool use_disconti // Store num_elem_this_blk "fake" ids into the // elem_num_map. Use a traditional for-loop to avoid unused // variable warnings about the loop counter. - for (unsigned int i=0; ielem_num_map.push_back(next_fake_id++); } From 39f77705aba4232514bdd6c12b1ed4765f2f64fd Mon Sep 17 00:00:00 2001 From: John Peterson Date: Fri, 7 Nov 2025 09:44:24 -0600 Subject: [PATCH 45/46] Improve comments on the _set_unique_ids_from_maps flag --- include/mesh/exodusII_io.h | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/include/mesh/exodusII_io.h b/include/mesh/exodusII_io.h index 2f22f9ee4b6..e2e6c409703 100644 --- a/include/mesh/exodusII_io.h +++ b/include/mesh/exodusII_io.h @@ -141,19 +141,41 @@ class ExodusII_IO : public MeshInput, void write_complex_magnitude (bool val); /** - * If true this flag enforces the following behaviors: + * If true, this flag enforces the following behaviors: * * .) When reading an Exodus file, instead of setting the * elem_num_map and node_num_map ids directly on Nodes and Elems * read in from the Exodus file, set their unique_ids - * instead. Normally libmesh chooses the unique_ids automatically - * but in this case we override that choice. As a consequence + * instead. Normally libmesh chooses the unique_ids automatically, + * but in this case we override that choice. As a consequence, * the libMesh Elems/Nodes will be numbered based on their * implied ordering in the exo file (sequentially by block and * without any gaps in the numbering). * .) When writing an Exodus file, populate the elem_num_map and * node_num_map with the unique_ids of the Elems/Nodes being * written. + * + * There are a few reasons why one might want to employ this + * behavior. One case is if you are using a ReplicatedMesh and the + * {node,elem}_num_map contains "large" (much larger than num_nodes) + * ids. This could happen either because the {node,elem}_num_map + * starts with a large id, or because there is a large gap in the + * {node,elem}_num_map numbering. Because the ReplicatedMesh always + * constructs std::vectors of size "largest_id" rather than size + * "num_nodes" or "num_elems", this mesh will be stored + * inefficiently, allocating nullptrs in the std::vector for the + * "missing" Nodes/Elems. Setting this flag to true will ensure that + * only vectors of size "num_nodes" and "num_elems" are allocated. + * + * Another case is when you delete Nodes/Elems during a simulation + * and/or renumber them, but don't want the original Nodes/Elems to + * be renumbered when the Mesh is written back out to an Exodus + * file. Setting this flag to true causes the original Node/Elem + * ids to be "saved" in the unique_id fields, so that they can later + * be written back to disk with their original numbering. + * + * This capability is relatively new, and should be considered + * experimental. */ void set_unique_ids_from_maps (bool val); From b969c4863b5d0c21b334bd2e7980f50b697b5234 Mon Sep 17 00:00:00 2001 From: John Peterson Date: Fri, 7 Nov 2025 09:59:10 -0600 Subject: [PATCH 46/46] Change name set_{node,elem}_unique_id() -> conditionally_set_{node,elem}_unique_id() --- include/mesh/exodusII_io_helper.h | 16 +++++++++------- src/mesh/exodusII_io.C | 4 ++-- src/mesh/exodusII_io_helper.C | 8 ++++---- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/include/mesh/exodusII_io_helper.h b/include/mesh/exodusII_io_helper.h index ac095a74626..79fc1d40809 100644 --- a/include/mesh/exodusII_io_helper.h +++ b/include/mesh/exodusII_io_helper.h @@ -316,14 +316,16 @@ class ExodusII_IO_Helper : public ParallelObject dof_id_type get_libmesh_elem_id(int exodus_elem_id); /** - * Helper function that sets the unique_id of the passed-in - * Node/Elem if _set_unique_ids_from_maps == true, does nothing if - * that flag is false. The input index is assumed to be a zero-based - * index into the {node,elem}_num_map array. - */ - void set_node_unique_id( + * Helper function that conditionally sets the unique_id of the + * passed-in Node/Elem. Calling this function does nothing if + * _set_unique_ids_from_maps == false, otherwise it sets the + * unique_id based on the entries of the {node,elem_num_map}. The + * input index is assumed to be a zero-based index into the + * {node,elem}_num_map array. + */ + void conditionally_set_node_unique_id( MeshBase & mesh, Node * node, int zero_based_node_num_map_index); - void set_elem_unique_id( + void conditionally_set_elem_unique_id( MeshBase & mesh, Elem * elem, int zero_based_elem_num_map_index); private: diff --git a/src/mesh/exodusII_io.C b/src/mesh/exodusII_io.C index e4f6351a8d5..3cb657da79f 100644 --- a/src/mesh/exodusII_io.C +++ b/src/mesh/exodusII_io.C @@ -349,7 +349,7 @@ void ExodusII_IO::read (const std::string & fname) // If the _set_unique_ids_from_maps flag is true, set the // unique_id for "node", otherwise do nothing. - exio_helper->set_node_unique_id(mesh, added_node, i); + exio_helper->conditionally_set_node_unique_id(mesh, added_node, i); // If we have a set of spline weights, these nodes are going to // be used as control points for Bezier elements, and we need @@ -481,7 +481,7 @@ void ExodusII_IO::read (const std::string & fname) // If the _set_unique_ids_from_maps flag is true, set the // unique_id for "elem", otherwise do nothing. - exio_helper->set_elem_unique_id(mesh, elem, j); + exio_helper->conditionally_set_elem_unique_id(mesh, elem, j); // If the Mesh assigned an ID different from the one we // tried to give it, we should probably error. diff --git a/src/mesh/exodusII_io_helper.C b/src/mesh/exodusII_io_helper.C index abb75a1c1f9..bc49f9f462d 100644 --- a/src/mesh/exodusII_io_helper.C +++ b/src/mesh/exodusII_io_helper.C @@ -2196,15 +2196,15 @@ ExodusII_IO_Helper::get_libmesh_id(int exodus_id, void -ExodusII_IO_Helper::set_node_unique_id( - MeshBase & mesh, Node * node, int zero_based_node_num_map_index) +ExodusII_IO_Helper:: +conditionally_set_node_unique_id(MeshBase & mesh, Node * node, int zero_based_node_num_map_index) { this->set_dof_object_unique_id(mesh, node, libmesh_vector_at(this->node_num_map, zero_based_node_num_map_index)); } void -ExodusII_IO_Helper::set_elem_unique_id( - MeshBase & mesh, Elem * elem, int zero_based_elem_num_map_index) +ExodusII_IO_Helper:: +conditionally_set_elem_unique_id(MeshBase & mesh, Elem * elem, int zero_based_elem_num_map_index) { this->set_dof_object_unique_id(mesh, elem, libmesh_vector_at(this->elem_num_map, zero_based_elem_num_map_index)); }