Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
10aec36
Add ExodusII_IO API, boolean controlling whether elem_num_map and nod…
jwpeterson Oct 15, 2025
e97c61b
Add code to set Node's unique_id manually based on node_num_map
jwpeterson Oct 19, 2025
f952f7e
Handle _set_unique_ids_from_maps flag while reading in elements
jwpeterson Oct 20, 2025
a98c327
And do the same node indexing steps for Bezier extraction data
jwpeterson Oct 20, 2025
12c5b6d
Add lambda which determines the libmesh_node id for the current Elem …
jwpeterson Oct 20, 2025
f4af06b
Move lambda outside of elem block loop
jwpeterson Oct 20, 2025
e9b7a0a
Use helper lambda for Bezier extraction data as well
jwpeterson Oct 20, 2025
98cfd03
Also store/set _set_unique_ids_from_maps flag on the ExodusII_IO_Helper
jwpeterson Oct 20, 2025
dfed7de
Update libmesh_node_id lookup in ExodusII_IO_Helper::read_edge_blocks…
jwpeterson Oct 20, 2025
4e68688
Use same variable naming, casts when determining libmesh_node_id
jwpeterson Oct 20, 2025
4a501df
Add ExodusII_IO_Helper::get_libmesh_node_id() helper function
jwpeterson Oct 20, 2025
c214402
Drop get_libmesh_node_id() lambda
jwpeterson Oct 20, 2025
0ef5997
Use slightly more general std::make_tuple in loop declaration
jwpeterson Oct 20, 2025
7f3a7a7
Change ExodusII_IO_Helper::get_libmesh_node_id() to take a 1-based Ex…
jwpeterson Oct 21, 2025
8dc9d41
Call get_libmesh_node_id() when reading nodeset info
jwpeterson Oct 21, 2025
c7d0fdc
Call get_libmesh_node_id() in ExodusII_IO_Helper::read_nodal_var_valu…
jwpeterson Oct 21, 2025
6914226
Call ExodusII_IO_Helper::get_libmesh_node_id() from ExodusII_IO::read()
jwpeterson Oct 21, 2025
6396a4d
Add comment about _set_unique_ids_from_maps flag behavior when writin…
jwpeterson Oct 21, 2025
7393af0
Store unique_ids in the node_num_map when writing Nodes
jwpeterson Oct 21, 2025
3be119a
Add exo file with non-trivial node_num_map for unit testing
jwpeterson Oct 31, 2025
44ac77d
Run bootstrap
jwpeterson Oct 31, 2025
4e605fe
Add ExodusII_IO unit test that sets node unique ids from the node_num…
jwpeterson Oct 31, 2025
ff34a93
Add test verifying that the unique_ids have been set
jwpeterson Oct 31, 2025
59b43b7
Add test of same mesh without setting the flag
jwpeterson Oct 31, 2025
aa07bff
Add helper function that the test calls twice
jwpeterson Oct 31, 2025
36066fe
Add comments about setting _end_elem_id, and that we don't set the un…
jwpeterson Nov 3, 2025
dd06e91
Add ExodusII_IO_Helper::get_libmesh_elem_id()
jwpeterson Nov 3, 2025
4619ba4
Call ExodusII_IO_Helper::get_libmesh_elem_id() while adding Elems to …
jwpeterson Nov 3, 2025
d1be057
Call ExodusII_IO_Helper::get_libmesh_elem_id() while reading sidesets
jwpeterson Nov 3, 2025
2df7237
Call ExodusII_IO_Helper::get_libmesh_elem_id() while reading elemsets
jwpeterson Nov 3, 2025
7bc1081
Move code that sets Elem unique_ids to separate function
jwpeterson Nov 3, 2025
ee0c780
Add/use ExodusII_IO_Helper::set_node_unique_id()
jwpeterson Nov 3, 2025
44c6974
Add private ExodusII_IO_Helper::set_dof_object_unique_id() implementa…
jwpeterson Nov 3, 2025
a31cc2e
Use libmesh_vector_at macro for bounds checking
jwpeterson Nov 3, 2025
c4a1ae9
Fix indentation level
jwpeterson Nov 3, 2025
9f59bea
Support set_unique_ids_from_maps flag when writing elem_num_map
jwpeterson Nov 3, 2025
5d7c442
Use ExodusII_IO_Helper::get_libmesh_elem_id() in read_elemental_var_v…
jwpeterson Nov 3, 2025
3a4bfe4
Add ExodusII_IO_Helper::get_libmesh_id()
jwpeterson Nov 4, 2025
84fb1a8
Call ExodusII_IO_Helper::get_libmesh_id() from ExodusII_IO_Helper::ge…
jwpeterson Nov 4, 2025
b3d98f4
Rename unit test
jwpeterson Nov 4, 2025
22ff09e
Add exo file with non-trivial elem_num_map_for unit testing
jwpeterson Nov 4, 2025
d9007dd
Run bootstrap
jwpeterson Nov 4, 2025
205c8be
Add testExodusSetElemUniqueIdsFromMaps() unit test
jwpeterson Nov 4, 2025
fc30535
Fix compilation issues identified by CI
jwpeterson Nov 4, 2025
39f7770
Improve comments on the _set_unique_ids_from_maps flag
jwpeterson Nov 7, 2025
b969c48
Change name set_{node,elem}_unique_id() -> conditionally_set_{node,el…
jwpeterson Nov 7, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions include/mesh/exodusII_io.h
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,45 @@ class ExodusII_IO : public MeshInput<MeshBase>,
*/
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.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's add a comment on why one might want to do this? (ReplicatedMesh using O(max_id) storage)

*
* 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);

/**
* By default, we only write out the elements physically stored in
* the mesh. If we have any SIDE_DISCONTINUOUS variables, however,
Expand Down Expand Up @@ -670,6 +709,14 @@ class ExodusII_IO : public MeshInput<MeshBase>,
*/
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.
Expand Down
43 changes: 43 additions & 0 deletions include/mesh/exodusII_io_helper.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@ namespace libMesh

// Forward declarations
class MeshBase;
class DofObject;

/**
* This is the \p ExodusII_IO_Helper class. This class hides the
Expand Down Expand Up @@ -305,6 +306,44 @@ class ExodusII_IO_Helper : public ParallelObject
int time_step,
std::map<dof_id_type, Real> & elem_var_value_map);

/**
* 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);

/**
* 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 conditionally_set_elem_unique_id(
MeshBase & mesh, Elem * elem, int zero_based_elem_num_map_index);

private:

/**
* Internal implementation for the two sets of functions above.
*/
dof_id_type get_libmesh_id(
int exodus_id,
const std::vector<int> & num_map);

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.
*/
Expand Down Expand Up @@ -850,6 +889,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;
Expand Down
2 changes: 1 addition & 1 deletion include/mesh/mesh_base.h
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
142 changes: 71 additions & 71 deletions src/mesh/exodusII_io.C
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -325,23 +327,30 @@ void ExodusII_IO::read (const std::string & fname)
std::unordered_map<const Node *, Elem *> spline_nodeelem_ptrs;

// Loop over the nodes, create Nodes with local processor_id 0.
for (int i=0; i<exio_helper->num_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];
// 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]), exodus_id-1);
Node * added_node = mesh.add_point (Point(exio_helper->x[i], exio_helper->y[i], exio_helper->z[i]), libmesh_node_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<unsigned>(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<unsigned>(libmesh_node_id),
"Error! Mesh assigned node ID "
<< added_node->id()
<< " which is different from the (zero-based) Exodus ID "
<< exodus_id-1
<< libmesh_node_id
<< "!");

// If the _set_unique_ids_from_maps flag is true, set the
// unique_id for "node", otherwise do nothing.
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
// to attach a NodeElem to each to make sure it doesn't get
Expand All @@ -361,6 +370,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);
Expand Down Expand Up @@ -449,33 +465,31 @@ void ExodusII_IO::read (const std::string & fname)
// Assign the current subdomain to this Elem
uelem->subdomain_id() = static_cast<subdomain_id_type>(subdomain_id);

// 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];
// Determine the libmesh elem id implied by "j". The
// 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);

// 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);
uelem->set_id(libmesh_elem_id);

// Record that we have seen an element of dimension uelem->dim()
elems_of_dimension[uelem->dim()] = true;

// 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<unsigned>(exodus_id-1),
// If the _set_unique_ids_from_maps flag is true, set the
// unique_id for "elem", otherwise do nothing.
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.
libmesh_error_msg_if(elem->id() != static_cast<unsigned>(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
Expand Down Expand Up @@ -528,17 +542,14 @@ void ExodusII_IO::read (const std::string & fname)
{
for (int k=0; k<exio_helper->num_nodes_per_elem; k++)
{
// global index
int gi = (elem_num)*exio_helper->num_nodes_per_elem + conv.get_node_map(k);

// 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;
// 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(exodus_node_id);

// Set the node pointer in the Elem
elem->set_node(k, mesh.node_ptr(libmesh_node_id));
Expand Down Expand Up @@ -609,10 +620,13 @@ 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 dof_id_type libmesh_node_id =
exio_helper->node_num_map[exio_helper->connect[gi] - 1] - 1;
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(exodus_node_id);

if (coef != 0) // Ignore irrelevant spline nodes
key.emplace_back(libmesh_node_id, coef);
Expand Down Expand Up @@ -703,15 +717,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<dof_id_type>(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);
Expand Down Expand Up @@ -804,14 +813,10 @@ void ExodusII_IO::read (const std::string & fname)
std::map<Elem *, MeshBase::elemset_type> 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<dof_id_type>(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);
Expand Down Expand Up @@ -887,22 +892,9 @@ void ExodusII_IO::read (const std::string & fname)

for (int i=0; i<exio_helper->num_nodes_per_set[nodeset]; ++i)
{
int exodus_id = exio_helper->node_sets_node_list[i + offset];

// 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<std::size_t>(exodus_id - 1) >= 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;
mesh.get_boundary_info().add_node(cast_int<dof_id_type>(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);
}
}
}
Expand Down Expand Up @@ -971,7 +963,15 @@ 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;

// 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)
{
Expand Down
Loading