LCOV - code coverage report
Current view: top level - src/mesh - nemesis_io.C (source / functions) Hit Total Coverage
Test: libMesh/libmesh: #4493 (1fc31f) with base e7717b Lines: 409 598 68.4 %
Date: 2026-07-08 01:01:35 Functions: 16 37 43.2 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : // The libMesh Finite Element Library.
       2             : // Copyright (C) 2002-2026 Benjamin S. Kirk, John W. Peterson, Roy H. Stogner
       3             : 
       4             : // This library is free software; you can redistribute it and/or
       5             : // modify it under the terms of the GNU Lesser General Public
       6             : // License as published by the Free Software Foundation; either
       7             : // version 2.1 of the License, or (at your option) any later version.
       8             : 
       9             : // This library is distributed in the hope that it will be useful,
      10             : // but WITHOUT ANY WARRANTY; without even the implied warranty of
      11             : // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
      12             : // Lesser General Public License for more details.
      13             : 
      14             : // You should have received a copy of the GNU Lesser General Public
      15             : // License along with this library; if not, write to the Free Software
      16             : // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
      17             : 
      18             : 
      19             : // LibMesh includes
      20             : #include "libmesh/distributed_mesh.h"
      21             : #include "libmesh/dof_map.h" // local_index
      22             : #include "libmesh/elem.h"
      23             : #include "libmesh/exodusII_io.h"
      24             : #include "libmesh/libmesh_logging.h"
      25             : #include "libmesh/nemesis_io.h"
      26             : #include "libmesh/nemesis_io_helper.h"
      27             : #include "libmesh/node.h"
      28             : #include "libmesh/parallel.h"
      29             : #include "libmesh/utility.h" // deallocate
      30             : #include "libmesh/boundary_info.h"
      31             : #include "libmesh/mesh_communication.h"
      32             : #include "libmesh/fe_interface.h"
      33             : #include "libmesh/fe_type.h"
      34             : #include "libmesh/equation_systems.h"
      35             : #include "libmesh/numeric_vector.h"
      36             : #include "libmesh/int_range.h"
      37             : 
      38             : // C++ includes
      39             : #include <memory>
      40             : #include <numeric> // std::accumulate
      41             : 
      42             : namespace libMesh
      43             : {
      44             : 
      45             : 
      46             : //-----------------------------------------------
      47             : // anonymous namespace for implementation details
      48             : namespace {
      49             : 
      50             : #if defined(LIBMESH_HAVE_EXODUS_API) && defined(LIBMESH_HAVE_NEMESIS_API)
      51             : struct CompareGlobalIdxMappings
      52             : {
      53             :   // strict weak ordering for a.first -> a.second mapping.  since we can only map to one
      54             :   // value only order the first entry
      55          80 :   bool operator()(const std::pair<unsigned int, unsigned int> & a,
      56             :                   const std::pair<unsigned int, unsigned int> & b) const
      57         720 :   { return a.first < b.first; }
      58             : 
      59             :   // strict weak ordering for a.first -> a.second mapping.  lookups will
      60             :   // be in terms of a single integer, which is why we need this method.
      61         140 :   bool operator()(const std::pair<unsigned int, unsigned int> & a,
      62             :                   const unsigned int b) const
      63        1820 :   { return a.first < b; }
      64             : };
      65             : #endif // defined(LIBMESH_HAVE_EXODUS_API) && defined(LIBMESH_HAVE_NEMESIS_API)
      66             : 
      67             : // Nemesis & ExodusII use int for all integer values, even the ones which
      68             : // should never be negative.  we like to use unsigned as a force of habit,
      69             : // this trivial little method saves some typing & also makes sure something
      70             : // is not horribly wrong.
      71             : template <typename T>
      72        2014 : inline unsigned int to_uint ( const T & t )
      73             : {
      74             :   libmesh_assert_equal_to (t, static_cast<T>(static_cast<unsigned int>(t)));
      75             : 
      76       27992 :   return static_cast<unsigned int>(t);
      77             : }
      78             : 
      79             : // test equality for a.first -> a.second mapping.  since we can only map to one
      80             : // value only test the first entry
      81             : #if defined(LIBMESH_HAVE_EXODUS_API) && defined(LIBMESH_HAVE_NEMESIS_API) && !defined(NDEBUG)
      82          40 : inline bool global_idx_mapping_equality (const std::pair<unsigned int, unsigned int> & a,
      83             :                                          const std::pair<unsigned int, unsigned int> & b)
      84             : {
      85          40 :   return a.first == b.first;
      86             : }
      87             : #endif
      88             : 
      89             : }
      90             : 
      91             : 
      92             : 
      93             : // ------------------------------------------------------------
      94             : // Nemesis_IO class members
      95        8729 : Nemesis_IO::Nemesis_IO (MeshBase & mesh,
      96        8729 :                         bool single_precision) :
      97             :   MeshInput<MeshBase> (mesh, /*is_parallel_format=*/true),
      98             :   MeshOutput<MeshBase> (mesh, /*is_parallel_format=*/true),
      99             :   ParallelObject (mesh),
     100             : #if defined(LIBMESH_HAVE_EXODUS_API) && defined(LIBMESH_HAVE_NEMESIS_API)
     101           0 :   nemhelper(std::make_unique<Nemesis_IO_Helper>(*this, false, single_precision)),
     102        8237 :   _timestep(1),
     103             : #endif
     104        8237 :   _verbose (false),
     105        8237 :   _append(false),
     106        8729 :   _allow_empty_variables(false)
     107             : {
     108             :   // if !LIBMESH_HAVE_EXODUS_API, we didn't use this
     109         246 :   libmesh_ignore(single_precision);
     110        8729 : }
     111             : 
     112             : 
     113             : 
     114         142 : Nemesis_IO::Nemesis_IO (const MeshBase & mesh,
     115         142 :                         bool single_precision) :
     116             :   MeshInput<MeshBase> (),
     117             :   MeshOutput<MeshBase> (mesh, /*is_parallel_format=*/true),
     118             :   ParallelObject (mesh),
     119             : #if defined(LIBMESH_HAVE_EXODUS_API) && defined(LIBMESH_HAVE_NEMESIS_API)
     120           0 :   nemhelper(std::make_unique<Nemesis_IO_Helper>(*this, false, single_precision)),
     121         134 :   _timestep(1),
     122             : #endif
     123         134 :   _verbose (false),
     124         134 :   _append(false),
     125         142 :   _allow_empty_variables(false)
     126             : {
     127             :   // if !LIBMESH_HAVE_EXODUS_API, we didn't use this
     128           4 :   libmesh_ignore(single_precision);
     129         142 : }
     130             : 
     131             : 
     132             : 
     133             : // Destructor.  Defined in the C file so we can be sure to get away
     134             : // with a forward declaration of Nemesis_IO_Helper in the header file.
     135        8371 : Nemesis_IO::~Nemesis_IO () = default;
     136             : 
     137             : 
     138             : 
     139           0 : void Nemesis_IO::verbose (bool set_verbosity)
     140             : {
     141           0 :   _verbose = set_verbosity;
     142             : 
     143             : #if defined(LIBMESH_HAVE_EXODUS_API) && defined(LIBMESH_HAVE_NEMESIS_API)
     144             :   // Set the verbose flag in the helper object
     145             :   // as well.
     146           0 :   nemhelper->verbose = _verbose;
     147             : #endif
     148           0 : }
     149             : 
     150             : 
     151             : 
     152           0 : void Nemesis_IO::write_complex_magnitude (bool val)
     153             : {
     154             : #if defined(LIBMESH_HAVE_EXODUS_API) && defined(LIBMESH_HAVE_NEMESIS_API)
     155           0 :   nemhelper->write_complex_abs = val;
     156             : #endif
     157           0 :   libmesh_ignore(val);
     158           0 : }
     159             : 
     160             : 
     161             : 
     162           0 : void Nemesis_IO::append(bool val)
     163             : {
     164           0 :   _append = val;
     165           0 : }
     166             : 
     167             : 
     168             : 
     169           0 : void Nemesis_IO::set_output_variables(const std::vector<std::string> & output_variables,
     170             :                                       bool allow_empty)
     171             : {
     172           0 :   _output_variables = output_variables;
     173           0 :   _allow_empty_variables = allow_empty;
     174           0 : }
     175             : 
     176             : 
     177             : 
     178        8871 : void Nemesis_IO::assert_symmetric_cmaps()
     179             : {
     180             : #ifndef NDEBUG
     181             : #if defined(LIBMESH_HAVE_EXODUS_API) && defined(LIBMESH_HAVE_NEMESIS_API)
     182             :   // We expect the communication maps to be symmetric - e.g. if processor i thinks it
     183             :   // communicates with processor j, then processor j should also be expecting to
     184             :   // communicate with i.  We can assert that here easily enough with an alltoall,
     185             :   // but let's only do it when not in optimized mode to limit unnecessary communication.
     186             :   {
     187         500 :     std::vector<unsigned char> pid_send_partner (this->n_processors(), 0);
     188             : 
     189             :     // strictly speaking, we should expect to communicate with ourself...
     190         250 :     pid_send_partner[this->processor_id()] = 1;
     191             : 
     192             :     // mark each processor id we reference with a node cmap
     193         452 :     for (unsigned int cmap=0; cmap<to_uint(nemhelper->num_node_cmaps); cmap++)
     194             :       {
     195         202 :         libmesh_assert_less (nemhelper->node_cmap_ids[cmap], this->n_processors());
     196             : 
     197         202 :         pid_send_partner[nemhelper->node_cmap_ids[cmap]] = 1;
     198             :       }
     199             : 
     200             :     // Copy the send pairing so we can catch the receive paring and
     201             :     // test for equality
     202         500 :     const std::vector<unsigned char> pid_recv_partner (pid_send_partner);
     203             : 
     204         250 :     this->comm().alltoall (pid_send_partner);
     205             : 
     206         250 :     libmesh_assert (pid_send_partner == pid_recv_partner);
     207             :   }
     208             : #endif
     209             : #endif
     210        8871 : }
     211             : 
     212             : 
     213             : 
     214             : #if defined(LIBMESH_HAVE_EXODUS_API) && defined(LIBMESH_HAVE_NEMESIS_API)
     215         850 : void Nemesis_IO::read (const std::string & base_filename)
     216             : {
     217          48 :   LOG_SCOPE ("read()","Nemesis_IO");
     218             : 
     219             :   // This function must be run on all processors at once
     220          24 :   parallel_object_only();
     221             : 
     222         850 :   if (_verbose)
     223             :     {
     224           0 :       libMesh::out << "[" << this->processor_id() << "] ";
     225           0 :       libMesh::out << "Reading Nemesis file on processor: " << this->processor_id() << std::endl;
     226             :     }
     227             : 
     228             :   // Construct the Nemesis filename based on the number of processors and the
     229             :   // current processor ID.
     230         874 :   std::string nemesis_filename = nemhelper->construct_nemesis_filename(base_filename);
     231             : 
     232         850 :   if (_verbose)
     233           0 :     libMesh::out << "Opening file: " << nemesis_filename << std::endl;
     234             : 
     235             :   // Open the Exodus file in EX_READ mode
     236         874 :   nemhelper->open(nemesis_filename.c_str(), /*read_only=*/true);
     237             : 
     238             :   // Get a reference to the mesh.  We need to be specific
     239             :   // since Nemesis_IO is multiply-inherited
     240             :   // MeshBase & mesh = this->mesh();
     241         850 :   MeshBase & mesh = MeshInput<MeshBase>::mesh();
     242             : 
     243             :   // We're reading a file on each processor, so our mesh is
     244             :   // partitioned into that many parts as it's created
     245          48 :   this->set_n_partitions(this->n_processors());
     246             : 
     247             :   // Local information: Read the following information from the standard Exodus header
     248             :   //  title[0]
     249             :   //  num_dim
     250             :   //  num_nodes
     251             :   //  num_elem
     252             :   //  num_elem_blk
     253             :   //  num_node_sets
     254             :   //  num_side_sets
     255         850 :   nemhelper->read_and_store_header_info();
     256         850 :   nemhelper->print_header();
     257             : 
     258             :   // Get global information: number of nodes, elems, blocks, nodesets and sidesets
     259         850 :   nemhelper->get_init_global();
     260             : 
     261             :   // Get "load balance" information.  This includes the number of internal & border
     262             :   // nodes and elements as well as the number of communication maps.
     263         850 :   nemhelper->get_loadbal_param();
     264             : 
     265             :   // Do some error checking
     266         850 :   libmesh_error_msg_if(nemhelper->num_external_nodes,
     267             :                        "ERROR: there should be no external nodes in an element-based partitioning!");
     268             : 
     269          24 :   libmesh_assert_equal_to (nemhelper->num_nodes,
     270             :                            (nemhelper->num_internal_nodes +
     271             :                             nemhelper->num_border_nodes));
     272             : 
     273          24 :   libmesh_assert_equal_to (nemhelper->num_elem,
     274             :                            (nemhelper->num_internal_elems +
     275             :                             nemhelper->num_border_elems));
     276             : 
     277          24 :   libmesh_assert_less_equal (nemhelper->num_nodes, nemhelper->num_nodes_global);
     278          24 :   libmesh_assert_less_equal (nemhelper->num_elem, nemhelper->num_elems_global);
     279             : 
     280             :   // Read nodes from the exodus file: this fills the nemhelper->x,y,z arrays.
     281         850 :   nemhelper->read_nodes();
     282             : 
     283             :   // Reads the nemhelper->node_num_map array, node_num_map[i] is the global node number for
     284             :   // local node number i.
     285         850 :   nemhelper->read_node_num_map();
     286             : 
     287             :   // The get_cmap_params() function reads in the:
     288             :   //  node_cmap_ids[],
     289             :   //  node_cmap_node_cnts[],
     290             :   //  elem_cmap_ids[],
     291             :   //  elem_cmap_elem_cnts[],
     292         850 :   nemhelper->get_cmap_params();
     293             : 
     294             :   // Read the IDs of the interior, boundary, and external nodes.  This function
     295             :   // fills the vectors:
     296             :   //  node_mapi[],
     297             :   //  node_mapb[],
     298             :   //  node_mape[]
     299         850 :   nemhelper->get_node_map();
     300             : 
     301             :   // Read each node communication map for this processor.  This function
     302             :   // fills the vectors of vectors named:
     303             :   //  node_cmap_node_ids[][]
     304             :   //  node_cmap_proc_ids[][]
     305         850 :   nemhelper->get_node_cmap();
     306             : 
     307          24 :   libmesh_assert_equal_to (to_uint(nemhelper->num_node_cmaps), nemhelper->node_cmap_node_cnts.size());
     308          24 :   libmesh_assert_equal_to (to_uint(nemhelper->num_node_cmaps), nemhelper->node_cmap_node_ids.size());
     309          24 :   libmesh_assert_equal_to (to_uint(nemhelper->num_node_cmaps), nemhelper->node_cmap_proc_ids.size());
     310             : 
     311         850 :   this->assert_symmetric_cmaps();
     312             : 
     313             :   // We now have enough information to infer node ownership.  We start by assuming
     314             :   // we own all the nodes on this processor.  We will then interrogate the
     315             :   // node cmaps and see if a lower-rank processor is associated with any of
     316             :   // our nodes.  If so, then that processor owns the node, not us...
     317         922 :   std::vector<processor_id_type> node_ownership (nemhelper->num_internal_nodes +
     318         850 :                                                  nemhelper->num_border_nodes,
     319         946 :                                                  this->processor_id());
     320             : 
     321             :   // a map from processor id to cmap number, to be used later
     322          48 :   std::map<unsigned int, unsigned int> pid_to_cmap_map;
     323             : 
     324             :   // For each node_cmap...
     325        1850 :   for (unsigned int cmap=0; cmap<to_uint(nemhelper->num_node_cmaps); cmap++)
     326             :     {
     327             :       // Good time for error checking...
     328          20 :       libmesh_assert_equal_to (to_uint(nemhelper->node_cmap_node_cnts[cmap]),
     329             :                                nemhelper->node_cmap_node_ids[cmap].size());
     330             : 
     331          20 :       libmesh_assert_equal_to (to_uint(nemhelper->node_cmap_node_cnts[cmap]),
     332             :                                nemhelper->node_cmap_proc_ids[cmap].size());
     333             : 
     334             :       // In all the samples I have seen, node_cmap_ids[cmap] is the processor
     335             :       // rank of the remote processor...
     336             :       const processor_id_type adjcnt_pid_idx =
     337        1020 :         cast_int<processor_id_type>(nemhelper->node_cmap_ids[cmap]);
     338             : 
     339          20 :       libmesh_assert_less (adjcnt_pid_idx, this->n_processors());
     340          20 :       libmesh_assert_not_equal_to (adjcnt_pid_idx, this->processor_id());
     341             : 
     342             :       // We only expect one cmap per adjacent processor
     343          20 :       libmesh_assert (!pid_to_cmap_map.count(adjcnt_pid_idx));
     344             : 
     345        1000 :       pid_to_cmap_map[adjcnt_pid_idx] = cmap;
     346             : 
     347             :       // ...and each node in that cmap...
     348        3304 :       for (unsigned int idx=0; idx<to_uint(nemhelper->node_cmap_node_cnts[cmap]); idx++)
     349             :         {
     350             :           //  Are the node_cmap_ids and node_cmap_proc_ids really redundant?
     351         100 :           libmesh_assert_equal_to
     352             :             (adjcnt_pid_idx,
     353             :              cast_int<processor_id_type>(nemhelper->node_cmap_proc_ids[cmap][idx]));
     354             : 
     355             :           // we are expecting the exodus node numbering to be 1-based...
     356        2284 :           const unsigned int local_node_idx = nemhelper->node_cmap_node_ids[cmap][idx]-1;
     357             : 
     358         100 :           libmesh_assert_less (local_node_idx, node_ownership.size());
     359             : 
     360             :           // if the adjacent processor is lower rank than the current
     361             :           // owner for this node, then it will get the node...
     362        2184 :           node_ownership[local_node_idx] =
     363        2994 :             std::min(node_ownership[local_node_idx], adjcnt_pid_idx);
     364             :         }
     365             :     } // We now should have established proper node ownership.
     366             : 
     367             :   // now that ownership is established, we can figure out how many nodes we
     368             :   // will be responsible for numbering.
     369          24 :   unsigned int num_nodes_i_must_number = 0;
     370             : 
     371        3526 :   for (const auto & pid : node_ownership)
     372        2880 :     if (pid == this->processor_id())
     373        1816 :       num_nodes_i_must_number++;
     374             : 
     375             :   // more error checking...
     376          24 :   libmesh_assert_greater_equal (num_nodes_i_must_number, nemhelper->num_internal_nodes);
     377          24 :   libmesh_assert (num_nodes_i_must_number <= to_uint(nemhelper->num_internal_nodes +
     378             :                                                      nemhelper->num_border_nodes));
     379         850 :   if (_verbose)
     380           0 :     libMesh::out << "[" << this->processor_id() << "] "
     381           0 :                  << "num_nodes_i_must_number="
     382           0 :                  << num_nodes_i_must_number
     383           0 :                  << std::endl;
     384             : 
     385             :   // The call to get_loadbal_param() gets 7 pieces of information.  We allgather
     386             :   // these now across all processors to determine some global numberings. We should
     387             :   // also gather the number of nodes each processor thinks it will number so that
     388             :   // we can (i) determine our offset, and (ii) do some error checking.
     389         874 :   std::vector<int> all_loadbal_data ( 8 );
     390         850 :   all_loadbal_data[0] = nemhelper->num_internal_nodes;
     391         850 :   all_loadbal_data[1] = nemhelper->num_border_nodes;
     392         850 :   all_loadbal_data[2] = nemhelper->num_external_nodes;
     393         850 :   all_loadbal_data[3] = nemhelper->num_internal_elems;
     394         850 :   all_loadbal_data[4] = nemhelper->num_border_elems;
     395         850 :   all_loadbal_data[5] = nemhelper->num_node_cmaps;
     396         850 :   all_loadbal_data[6] = nemhelper->num_elem_cmaps;
     397         850 :   all_loadbal_data[7] = num_nodes_i_must_number;
     398             : 
     399         850 :   this->comm().allgather (all_loadbal_data, /* identical_buffer_sizes = */ true);
     400             : 
     401             :   // OK, we are now in a position to request new global indices for all the nodes
     402             :   // we do not own
     403             : 
     404             :   // Let's get a unique message tag to use for send()/receive()
     405         898 :   Parallel::MessageTag nodes_tag = mesh.comm().get_unique_tag();
     406             : 
     407             :   std::vector<std::vector<int>>
     408         898 :     needed_node_idxs (nemhelper->num_node_cmaps); // the indices we will ask for
     409             : 
     410             :   std::vector<Parallel::Request>
     411         898 :     needed_nodes_requests (nemhelper->num_node_cmaps);
     412             : 
     413        1850 :   for (unsigned int cmap=0; cmap<to_uint(nemhelper->num_node_cmaps); cmap++)
     414             :     {
     415             :       // We know we will need no more indices than there are nodes
     416             :       // in this cmap, but that number is an upper bound in general
     417             :       // since the neighboring processor associated with the cmap
     418             :       //  may not actually own it
     419        1040 :       needed_node_idxs[cmap].reserve   (nemhelper->node_cmap_node_cnts[cmap]);
     420             : 
     421        1020 :       const unsigned int adjcnt_pid_idx = nemhelper->node_cmap_ids[cmap];
     422             : 
     423             :       // ...and each node in that cmap...
     424        3204 :       for (unsigned int idx=0; idx<to_uint(nemhelper->node_cmap_node_cnts[cmap]); idx++)
     425             :         {
     426             :           const unsigned int
     427        2284 :             local_node_idx  = nemhelper->node_cmap_node_ids[cmap][idx]-1,
     428        2184 :             owning_pid_idx  = node_ownership[local_node_idx];
     429             : 
     430             :           // add it to the request list for its owning processor.
     431        2184 :           if (owning_pid_idx == adjcnt_pid_idx)
     432             :             {
     433             :               const unsigned int
     434         860 :                 global_node_idx = nemhelper->node_num_map[local_node_idx]-1;
     435         910 :               needed_node_idxs[cmap].push_back(global_node_idx);
     436             :             }
     437             :         }
     438             :       // now post the send for this cmap
     439        1020 :       this->comm().send (adjcnt_pid_idx,              // destination
     440          40 :                          needed_node_idxs[cmap],      // send buffer
     441          40 :                          needed_nodes_requests[cmap], // request
     442             :                          nodes_tag);
     443             :     } // all communication requests for getting updated global indices for border
     444             :       // nodes have been initiated
     445             : 
     446             :   // Figure out how many nodes each processor thinks it will number and make sure
     447             :   // that it adds up to the global number of nodes. Also, set up global node
     448             :   // index offsets for each processor.
     449             :   std::vector<unsigned int>
     450         898 :     all_num_nodes_i_must_number (this->n_processors());
     451             : 
     452        9164 :   for (auto pid : make_range(this->n_processors()))
     453        8410 :     all_num_nodes_i_must_number[pid] = all_loadbal_data[8*pid + 7];
     454             : 
     455             :   // The sum of all the entries in this vector should sum to the number of global nodes
     456          24 :   libmesh_assert (std::accumulate(all_num_nodes_i_must_number.begin(),
     457             :                                   all_num_nodes_i_must_number.end(),
     458             :                                   0) == nemhelper->num_nodes_global);
     459             : 
     460         850 :   unsigned int my_next_node = 0;
     461        4582 :   for (auto pid : make_range(this->processor_id()))
     462        3744 :     my_next_node += all_num_nodes_i_must_number[pid];
     463             : 
     464         850 :   const unsigned int my_node_offset = my_next_node;
     465             : 
     466         850 :   if (_verbose)
     467           0 :     libMesh::out << "[" << this->processor_id() << "] "
     468           0 :                  << "my_node_offset="
     469           0 :                  << my_node_offset
     470           0 :                  << std::endl;
     471             : 
     472             :   // Add internal nodes to the DistributedMesh, using the node ID offset we
     473             :   // computed and the current processor's ID.
     474        2014 :   for (unsigned int i=0; i<to_uint(nemhelper->num_internal_nodes); ++i)
     475             :     {
     476        1164 :       const unsigned int local_node_idx  = nemhelper->node_mapi[i]-1;
     477             : #ifndef NDEBUG
     478         104 :       const unsigned int owning_pid_idx  = node_ownership[local_node_idx];
     479             : #endif
     480             : 
     481             :       // an internal node we do not own? huh??
     482         104 :       libmesh_assert_equal_to (owning_pid_idx, this->processor_id());
     483         104 :       libmesh_assert_less (my_next_node, nemhelper->num_nodes_global);
     484             : 
     485             :       // "Catch" the node pointer after addition, make sure the
     486             :       // ID matches the requested value.
     487             :       Node * added_node =
     488        1372 :         mesh.add_point (Point(nemhelper->x[local_node_idx],
     489         208 :                               nemhelper->y[local_node_idx],
     490        1164 :                               nemhelper->z[local_node_idx]),
     491             :                         my_next_node,
     492         208 :                         this->processor_id());
     493             : 
     494             :       // Make sure the node we added has the ID we thought it would
     495        1164 :       if (added_node->id() != my_next_node)
     496             :         {
     497           0 :           libMesh::err << "Error, node added with ID " << added_node->id()
     498           0 :                        << ", but we wanted ID " << my_next_node << std::endl;
     499             :         }
     500             : 
     501             :       // Set a unique_id ourselves since ReplicatedMesh can't handle
     502             :       // distributed unique_id generation.  Make sure it doesn't
     503             :       // overlap element unique_id() values either.
     504             : #ifdef LIBMESH_ENABLE_UNIQUE_ID
     505        1268 :       added_node->set_unique_id(added_node->id() + nemhelper->num_elems_global);
     506             : #endif
     507             : 
     508             :       // update the local->global index map, keeping it 1-based
     509        1268 :       nemhelper->node_num_map[local_node_idx] = my_next_node++ + 1;
     510             :     }
     511             : 
     512             :   // Now, for the boundary nodes...  We may very well own some of them,
     513             :   // but there may be others for which we have requested the new global
     514             :   // id.  We expect to be asked for the ids of the ones we own, so
     515             :   // we need to create a map from the old global id to the new one
     516             :   // we are about to create.
     517             :   typedef std::vector<std::pair<unsigned int, unsigned int>> global_idx_mapping_type;
     518          48 :   global_idx_mapping_type old_global_to_new_global_map;
     519         850 :   old_global_to_new_global_map.reserve (num_nodes_i_must_number // total # i will have
     520         850 :                                         - (my_next_node         // amount i have thus far
     521             :                                            - my_node_offset));  // this should be exact!
     522             :   CompareGlobalIdxMappings global_idx_mapping_comp;
     523             : 
     524        2362 :   for (unsigned int i=0; i<to_uint(nemhelper->num_border_nodes); ++i)
     525             :     {
     526             :       const unsigned int
     527        1512 :         local_node_idx  = nemhelper->node_mapb[i]-1,
     528        1512 :         owning_pid_idx  = node_ownership[local_node_idx];
     529             : 
     530             :       // if we own it...
     531        1612 :       if (owning_pid_idx == this->processor_id())
     532             :         {
     533             :           const unsigned int
     534         652 :             global_node_idx = nemhelper->node_num_map[local_node_idx]-1;
     535             : 
     536             :           // we will number it, and create a mapping from its old global index to
     537             :           // the new global index, for lookup purposes when neighbors come calling
     538         652 :           old_global_to_new_global_map.emplace_back(global_node_idx, my_next_node);
     539             : 
     540             :           // "Catch" the node pointer after addition, make sure the
     541             :           // ID matches the requested value.
     542             :           Node * added_node =
     543         802 :             mesh.add_point (Point(nemhelper->x[local_node_idx],
     544         100 :                                   nemhelper->y[local_node_idx],
     545         100 :                                   nemhelper->z[local_node_idx]),
     546             :                             my_next_node,
     547         100 :                             this->processor_id());
     548             : 
     549             :           // Make sure the node we added has the ID we thought it would
     550         652 :           if (added_node->id() != my_next_node)
     551             :             {
     552           0 :               libMesh::err << "Error, node added with ID " << added_node->id()
     553           0 :                            << ", but we wanted ID " << my_next_node << std::endl;
     554             :             }
     555             : 
     556             :           // Set a unique_id ourselves since ReplicatedMesh can't handle
     557             :           // distributed unique_id generation.  Make sure it doesn't
     558             :           // overlap element unique_id() values either.
     559             : #ifdef LIBMESH_ENABLE_UNIQUE_ID
     560         702 :           added_node->set_unique_id(added_node->id() + nemhelper->num_elems_global);
     561             : #endif
     562             : 
     563             :           // update the local->global index map, keeping it 1-based
     564         702 :           nemhelper->node_num_map[local_node_idx] = my_next_node++ + 1;
     565             :         }
     566             :     }
     567             :   // That should cover numbering all the nodes which belong to us...
     568          24 :   libmesh_assert_equal_to (num_nodes_i_must_number, (my_next_node - my_node_offset));
     569             : 
     570             :   // Let's sort the mapping so we can efficiently answer requests
     571         850 :   std::sort (old_global_to_new_global_map.begin(),
     572             :              old_global_to_new_global_map.end(),
     573             :              global_idx_mapping_comp);
     574             : 
     575             :   // and it had better be unique...
     576          24 :   libmesh_assert (std::unique (old_global_to_new_global_map.begin(),
     577             :                                old_global_to_new_global_map.end(),
     578             :                                global_idx_mapping_equality) ==
     579             :                   old_global_to_new_global_map.end());
     580             : 
     581             :   // We can now catch incoming requests and process them. for efficiency
     582             :   // let's do whatever is available next
     583          48 :   std::map<unsigned int, std::vector<int>> requested_node_idxs; // the indices asked of us
     584             : 
     585         898 :   std::vector<Parallel::Request> requested_nodes_requests(nemhelper->num_node_cmaps);
     586             : 
     587             :   // We know we will receive the request from a given processor before
     588             :   // we receive its reply to our request. However, we may receive
     589             :   // a request and a response from one processor before getting
     590             :   // a request from another processor.  So what we are doing here
     591             :   // is processing whatever message comes next, while recognizing
     592             :   // we will receive a request from a processor before receiving
     593             :   // its reply
     594         898 :   std::vector<bool> processed_cmap (nemhelper->num_node_cmaps, false);
     595             : 
     596        2850 :   for (unsigned int comm_step=0; comm_step<2*to_uint(nemhelper->num_node_cmaps); comm_step++)
     597             :     {
     598             :       // query the first message which is available
     599             :       const Parallel::Status
     600        2040 :         status (this->comm().probe (Parallel::any_source,
     601          80 :                                     nodes_tag));
     602             :       const unsigned int
     603        2000 :         requesting_pid_idx = status.source(),
     604        2000 :         source_pid_idx     = status.source();
     605             : 
     606             :       // this had better be from a processor we are expecting...
     607          40 :       libmesh_assert (pid_to_cmap_map.count(requesting_pid_idx));
     608             : 
     609             :       // the local cmap which corresponds to the source processor
     610        2000 :       const unsigned int cmap = pid_to_cmap_map[source_pid_idx];
     611             : 
     612        2040 :       if (!processed_cmap[cmap])
     613             :         {
     614          20 :           processed_cmap[cmap] = true;
     615             : 
     616             :           // we should only get one request per paired processor
     617          20 :           libmesh_assert (!requested_node_idxs.count(requesting_pid_idx));
     618             : 
     619             :           // get a reference to the request buffer for this processor to
     620             :           // avoid repeated map lookups
     621        1000 :           std::vector<int> & xfer_buf (requested_node_idxs[requesting_pid_idx]);
     622             : 
     623             :           // actually receive the message.
     624        1000 :           this->comm().receive (requesting_pid_idx, xfer_buf, nodes_tag);
     625             : 
     626             :           // Fill the request
     627        1860 :           for (auto i : index_range(xfer_buf))
     628             :             {
     629             :               // the requested old global node index, *now 0-based*
     630         860 :               const unsigned int old_global_node_idx = xfer_buf[i];
     631             : 
     632             :               // find the new global node index for the requested node -
     633             :               // note that requesting_pid_idx thinks we own this node,
     634             :               // so we better!
     635             :               const global_idx_mapping_type::const_iterator it =
     636         810 :                 std::lower_bound (old_global_to_new_global_map.begin(),
     637             :                                   old_global_to_new_global_map.end(),
     638             :                                   old_global_node_idx,
     639          50 :                                   global_idx_mapping_comp);
     640             : 
     641          50 :               libmesh_assert (it != old_global_to_new_global_map.end());
     642          50 :               libmesh_assert_equal_to (it->first, old_global_node_idx);
     643          50 :               libmesh_assert_greater_equal (it->second, my_node_offset);
     644          50 :               libmesh_assert_less (it->second, my_next_node);
     645             : 
     646             :               // overwrite the requested old global node index with the new global index
     647         860 :               xfer_buf[i] = it->second;
     648             :             }
     649             : 
     650             :           // and send the new global indices back to the processor which asked for them
     651        1020 :           this->comm().send (requesting_pid_idx,
     652             :                              xfer_buf,
     653          40 :                              requested_nodes_requests[cmap],
     654             :                              nodes_tag);
     655             :         } // done processing the request
     656             : 
     657             :       // this is the second time we have heard from this processor,
     658             :       // so it must be its reply to our request
     659             :       else
     660             :         {
     661             :           // a long time ago, we sent off our own requests.  now it is time to catch the
     662             :           // replies and get the new global node numbering.  note that for any reply
     663             :           // we receive, the corresponding nonblocking send from above *must* have been
     664             :           // completed, since the reply is in response to that request!!
     665             : 
     666             :           // if we have received a reply, our send *must* have completed
     667             :           // (note we never actually need to wait on the request)
     668          20 :           libmesh_assert (needed_nodes_requests[cmap].test());
     669          20 :           libmesh_assert_equal_to (to_uint(nemhelper->node_cmap_ids[cmap]), source_pid_idx);
     670             : 
     671             :           // now post the receive for this cmap
     672        1000 :           this->comm().receive (source_pid_idx,
     673          40 :                                 needed_node_idxs[cmap],
     674          60 :                                 nodes_tag);
     675             : 
     676          20 :           libmesh_assert_less_equal (needed_node_idxs[cmap].size(),
     677             :                                      nemhelper->node_cmap_node_ids[cmap].size());
     678             : 
     679        3204 :           for (std::size_t i=0, j=0, ncnis=nemhelper->node_cmap_node_ids[cmap].size(); i < ncnis; i++)
     680             :             {
     681             :               const unsigned int
     682        2284 :                 local_node_idx  = nemhelper->node_cmap_node_ids[cmap][i]-1,
     683        2184 :                 owning_pid_idx  = node_ownership[local_node_idx];
     684             : 
     685             :               // if this node is owned by source_pid_idx, its new global id
     686             :               // is in the buffer we just received
     687        2184 :               if (owning_pid_idx == source_pid_idx)
     688             :                 {
     689          50 :                   libmesh_assert_less (j, needed_node_idxs[cmap].size());
     690             : 
     691             :                   const unsigned int // now 0-based!
     692         910 :                     global_node_idx = needed_node_idxs[cmap][j++];
     693             : 
     694             :                   // "Catch" the node pointer after addition, make sure the
     695             :                   // ID matches the requested value.
     696             :                   Node * added_node =
     697        1010 :                     mesh.add_point (Point(nemhelper->x[local_node_idx],
     698         100 :                                           nemhelper->y[local_node_idx],
     699         100 :                                           nemhelper->z[local_node_idx]),
     700             :                                     cast_int<dof_id_type>(global_node_idx),
     701         100 :                                     cast_int<processor_id_type>(source_pid_idx));
     702             : 
     703             :                   // Make sure the node we added has the ID we thought it would
     704         860 :                   if (added_node->id() != global_node_idx)
     705             :                     {
     706           0 :                       libMesh::err << "Error, node added with ID " << added_node->id()
     707           0 :                                    << ", but we wanted ID " << global_node_idx << std::endl;
     708             :                     }
     709             : 
     710             :                   // Set a unique_id ourselves since ReplicatedMesh can't handle
     711             :                   // distributed unique_id generation.  Make sure it doesn't
     712             :                   // overlap element unique_id() values either.
     713             : #ifdef LIBMESH_ENABLE_UNIQUE_ID
     714         910 :                   added_node->set_unique_id(added_node->id() + nemhelper->num_elems_global);
     715             : #endif
     716             : 
     717             :                   // update the local->global index map, keeping it 1-based
     718         860 :                   nemhelper->node_num_map[local_node_idx] = global_node_idx + 1;
     719             : 
     720             :                   // we are not really going to use my_next_node again, but we can
     721             :                   // keep incrementing it to track how many nodes we have added
     722             :                   // to the mesh
     723         860 :                   my_next_node++;
     724             :                 }
     725             :             }
     726             :         }
     727             :     } // end of node index communication loop
     728             : 
     729             :   // we had better have added all the nodes we need to!
     730          24 :   libmesh_assert_equal_to ((my_next_node - my_node_offset), to_uint(nemhelper->num_nodes));
     731             : 
     732             :   // After all that, we should be done with all node-related arrays
     733             :   // *except* the node_num_map.
     734             :   // So let's clean up the arrays we are done with.
     735             :   {
     736         826 :     Utility::deallocate (nemhelper->node_mapi);
     737         826 :     Utility::deallocate (nemhelper->node_mapb);
     738         826 :     Utility::deallocate (nemhelper->node_mape);
     739         826 :     Utility::deallocate (nemhelper->node_cmap_ids);
     740         826 :     Utility::deallocate (nemhelper->node_cmap_node_cnts);
     741         826 :     Utility::deallocate (nemhelper->node_cmap_node_ids);
     742         826 :     Utility::deallocate (nemhelper->node_cmap_proc_ids);
     743         826 :     Utility::deallocate (nemhelper->x);
     744         826 :     Utility::deallocate (nemhelper->y);
     745         826 :     Utility::deallocate (nemhelper->z);
     746         826 :     Utility::deallocate (needed_node_idxs);
     747         826 :     Utility::deallocate (node_ownership);
     748             :   }
     749             : 
     750         850 :   Parallel::wait (needed_nodes_requests);
     751         850 :   Parallel::wait (requested_nodes_requests);
     752          24 :   requested_node_idxs.clear();
     753             : 
     754             :   // See what the node count is up to now.
     755         850 :   if (_verbose)
     756             :     {
     757             :       // Report the number of nodes which have been added locally
     758           0 :       libMesh::out << "[" << this->processor_id() << "] ";
     759           0 :       libMesh::out << "mesh.n_nodes()=" << mesh.n_nodes() << std::endl;
     760             : 
     761             :       // Reports the number of nodes that have been added in total.
     762           0 :       libMesh::out << "[" << this->processor_id() << "] ";
     763           0 :       libMesh::out << "mesh.parallel_n_nodes()=" << mesh.parallel_n_nodes() << std::endl;
     764             :     }
     765             : 
     766             : 
     767             : 
     768             :   // --------------------------------------------------------------------------------
     769             :   // --------------------------------------------------------------------------------
     770             :   // --------------------------------------------------------------------------------
     771             : 
     772             : 
     773             :   // We can now read in the elements...Exodus stores them in blocks in which all
     774             :   // elements have the same geometric type.  This code is adapted directly from exodusII_io.C
     775             : 
     776             :   // Assertion: The sum of the border and internal elements on all processors
     777             :   // should equal nemhelper->num_elems_global
     778             : #ifndef NDEBUG
     779             :   {
     780          24 :     int sum_internal_elems=0, sum_border_elems=0;
     781          72 :     for (unsigned int j=3,c=0; c<this->n_processors(); j+=8,++c)
     782          48 :       sum_internal_elems += all_loadbal_data[j];
     783             : 
     784          72 :     for (unsigned int j=4,c=0; c<this->n_processors(); j+=8,++c)
     785          48 :       sum_border_elems += all_loadbal_data[j];
     786             : 
     787          24 :     if (_verbose)
     788             :       {
     789           0 :         libMesh::out << "[" << this->processor_id() << "] ";
     790           0 :         libMesh::out << "sum_internal_elems=" << sum_internal_elems << std::endl;
     791             : 
     792           0 :         libMesh::out << "[" << this->processor_id() << "] ";
     793           0 :         libMesh::out << "sum_border_elems=" << sum_border_elems << std::endl;
     794             :       }
     795             : 
     796          24 :     libmesh_assert_equal_to (sum_internal_elems+sum_border_elems, nemhelper->num_elems_global);
     797             :   }
     798             : #endif
     799             : 
     800             :   // We need to set the mesh dimension, but the following...
     801             :   // mesh.set_mesh_dimension(static_cast<unsigned int>(nemhelper->num_dim));
     802             : 
     803             :   // ... is not sufficient since some codes report num_dim==3 for two dimensional
     804             :   // meshes living in 3D, even though all the elements are of 2D type.  Therefore,
     805             :   // we instead use the dimension of the highest element found for the Mesh dimension,
     806             :   // similar to what is done by the Exodus reader, except here it requires a
     807             :   // parallel communication.
     808         850 :   elems_of_dimension.resize(4, false); // will use 1-based
     809             : 
     810             :   // Fills in the:
     811             :   // global_elem_blk_ids[] and
     812             :   // global_elem_blk_cnts[] arrays.
     813         850 :   nemhelper->get_eb_info_global();
     814             : 
     815             :   //   // Fills in the vectors
     816             :   //   // elem_mapi[num_internal_elems]
     817             :   //   // elem_mapb[num_border_elems  ]
     818             :   //   // These tell which of the (locally-numbered) elements are internal and which are border elements.
     819             :   //   // In our test example these arrays are sorted (but non-contiguous), which makes it possible to
     820             :   //   // binary search for each element ID... however I don't think we need to distinguish between the
     821             :   //   // two types, since either can have nodes the boundary!
     822             :   //   nemhelper->get_elem_map();
     823             : 
     824             :   // Fills in the vectors of vectors:
     825             :   // elem_cmap_elem_ids[][]
     826             :   // elem_cmap_side_ids[][]
     827             :   // elem_cmap_proc_ids[][]
     828             :   // These arrays are of size num_elem_cmaps * elem_cmap_elem_cnts[i], i = 0..num_elem_cmaps
     829         850 :   nemhelper->get_elem_cmap();
     830             : 
     831             :   // Get information about the element blocks:
     832             :   // (read in the array nemhelper->block_ids[])
     833         850 :   nemhelper->read_block_info();
     834             : 
     835             :   // Reads the nemhelper->elem_num_map array.
     836             :   // elem_num_map[i] is the exodus element number for local element
     837             :   // number i, which makes elem_num_map[i]-1 the libMesh element
     838             :   // number.
     839         850 :   nemhelper->read_elem_num_map();
     840             : 
     841          24 :   std::size_t local_elem_num = 0;
     842             : 
     843             :   // Read in the element connectivity for each block by
     844             :   // looping over all the blocks.
     845        1700 :   for (unsigned int i=0; i<to_uint(nemhelper->num_elem_blk); i++)
     846             :     {
     847             :       // Read the information for block i:  For nemhelper->block_ids[i], reads
     848             :       // elem_type
     849             :       // num_elem_this_blk
     850             :       // num_nodes_per_elem
     851             :       // num_attr
     852             :       // connect <-- the nodal connectivity array for each element in the block.
     853         850 :       nemhelper->read_elem_in_block(i);
     854             : 
     855             :       // Note that with parallel files it is possible we have no elements in
     856             :       // this block!
     857         850 :       if (!nemhelper->num_elem_this_blk) continue;
     858             : 
     859             :       // Set subdomain ID based on the block ID.
     860             :       subdomain_id_type subdomain_id =
     861         432 :         restrict_int<subdomain_id_type>(nemhelper->block_ids[i]);
     862             : 
     863             :       // Create a type string (this uses the null-terminated string ctor).
     864         432 :       const std::string type_str ( nemhelper->elem_type.data() );
     865             : 
     866             :       // Set any relevant node/edge maps for this element
     867         410 :       const auto & conv = nemhelper->get_conversion(type_str);
     868             : 
     869         410 :       if (_verbose)
     870           0 :         libMesh::out << "Reading a block of " << type_str << " elements." << std::endl;
     871             : 
     872             :       // Loop over all the elements in this block
     873        1328 :       for (unsigned int j=0; j<to_uint(nemhelper->num_elem_this_blk); j++)
     874             :         {
     875         996 :           auto uelem = Elem::build (conv.libmesh_elem_type());
     876             : 
     877             :           // Assign subdomain and processor ID to the newly-created Elem.
     878             :           // Assigning the processor ID beforehand ensures that the Elem is
     879             :           // not added as an "unpartitioned" element.  Note that the element
     880             :           // numbering in Exodus is also 1-based.
     881         918 :           uelem->subdomain_id() = subdomain_id;
     882         996 :           uelem->processor_id() = this->processor_id();
     883         918 :           uelem->set_id()       = nemhelper->elem_num_map[local_elem_num++]-1;
     884             : 
     885             :           // Handle unique_id numbering, just in case we're using a
     886             :           // ReplicatedMesh that doesn't know how to handle it in
     887             :           // parallel.
     888             : #ifdef LIBMESH_ENABLE_UNIQUE_ID
     889         216 :           uelem->set_unique_id(uelem->id());
     890             : #endif
     891             : 
     892             :           // Mark that we have seen an element of the current element's
     893             :           // dimension.
     894         918 :           elems_of_dimension[uelem->dim()] = true;
     895             : 
     896             :           // Add the created Elem to the Mesh, catch the Elem
     897             :           // pointer that the Mesh throws back.
     898         996 :           Elem * elem = mesh.add_elem(std::move(uelem));
     899             : 
     900             :           // We are expecting the element "thrown back" by libmesh to have the ID we specified for it...
     901             :           // Check to see that really is the case.  Note that local_elem_num was post-incremented, so
     902             :           // subtract 1 when performing the check.
     903          78 :           libmesh_assert_equal_to(elem->id(),
     904             :                                   cast_int<dof_id_type>(nemhelper->elem_num_map[local_elem_num-1]-1));
     905             : 
     906             :           // Set all the nodes for this element
     907         918 :           if (_verbose)
     908           0 :             libMesh::out << "[" << this->processor_id() << "] "
     909           0 :                          << "Setting nodes for Elem " << elem->id() << std::endl;
     910             : 
     911        4686 :           for (unsigned int k=0; k<to_uint(nemhelper->num_nodes_per_elem); k++)
     912             :             {
     913             :               const unsigned int
     914        3768 :                 gi              = (j*nemhelper->num_nodes_per_elem +       // index into connectivity array
     915        3768 :                                    conv.get_node_map(k)),
     916        3768 :                 local_node_idx  = nemhelper->connect[gi]-1,                // local node index
     917        3768 :                 global_node_idx = nemhelper->node_num_map[local_node_idx]-1; // new global node index
     918             : 
     919             :               // Set node number
     920        3768 :               elem->set_node(k, mesh.node_ptr(global_node_idx));
     921             :             }
     922         762 :         } // for (unsigned int j=0; j<nemhelper->num_elem_this_blk; j++)
     923             :     } // end for (unsigned int i=0; i<nemhelper->num_elem_blk; i++)
     924             : 
     925        1700 :   for (const auto & [id, name] : nemhelper->id_to_block_names)
     926         850 :     if (name != "")
     927           0 :       mesh.subdomain_name(id) = name;
     928             : 
     929         850 :   if (_verbose)
     930             :     {
     931             :       // Print local elems_of_dimension information
     932           0 :       for (auto i : IntRange<std::size_t>(1, elems_of_dimension.size()))
     933           0 :         libMesh::out << "[" << this->processor_id() << "] "
     934           0 :                      << "elems_of_dimension[" << i << "]=" << elems_of_dimension[i] << std::endl;
     935             :     }
     936             : 
     937             :   // Get the max dimension seen on the current processor
     938         850 :   unsigned char max_dim_seen = 0;
     939        3400 :   for (auto i : IntRange<std::size_t>(1, elems_of_dimension.size()))
     940        2550 :     if (elems_of_dimension[i])
     941         410 :       max_dim_seen = static_cast<unsigned char>(i);
     942             : 
     943             :   // Do a global max to determine the max dimension seen by all processors.
     944             :   // It should match -- I don't think we even support calculations on meshes
     945             :   // with elements of different dimension...
     946         850 :   this->comm().max(max_dim_seen);
     947             : 
     948         850 :   if (_verbose)
     949             :     {
     950             :       // Print the max element dimension from all processors
     951           0 :       libMesh::out << "[" << this->processor_id() << "] "
     952           0 :                    << "max_dim_seen=" << +max_dim_seen << std::endl;
     953             :     }
     954             : 
     955             :   // Set the mesh dimension to the largest encountered for an element
     956        1652 :   mesh.set_mesh_dimension(max_dim_seen);
     957             : 
     958             : #if LIBMESH_DIM < 3
     959             :   libmesh_error_msg_if(mesh.mesh_dimension() > LIBMESH_DIM,
     960             :                        "Cannot open dimension "
     961             :                        << mesh.mesh_dimension()
     962             :                        << " mesh file when configured without "
     963             :                        << mesh.mesh_dimension()
     964             :                        << "D support." );
     965             : #endif
     966             : 
     967             : 
     968             :   // Global sideset information, they are distributed as well, not sure if they will require communication...?
     969         850 :   nemhelper->get_ss_param_global();
     970             : 
     971         850 :   if (_verbose)
     972             :     {
     973           0 :       libMesh::out << "[" << this->processor_id() << "] "
     974           0 :                    << "Read global sideset parameter information." << std::endl;
     975             : 
     976             :       // These global values should be the same on all processors...
     977           0 :       libMesh::out << "[" << this->processor_id() << "] "
     978           0 :                    << "Number of global sideset IDs: " << nemhelper->global_sideset_ids.size() << std::endl;
     979             :     }
     980             : 
     981             :   // Read *local* sideset info the same way it is done in
     982             :   // exodusII_io_helper.  May be called any time after
     983             :   // nemhelper->read_and_store_header_info(); This sets num_side_sets and resizes
     984             :   // elem_list, side_list, and id_list to num_elem_all_sidesets.  Note
     985             :   // that there appears to be the same number of sidesets in each file
     986             :   // but they all have different numbers of entries (some are empty).
     987             :   // Note that the sum of "nemhelper->num_elem_all_sidesets" over all
     988             :   // processors should equal the sum of the entries in the "num_global_side_counts" array
     989             :   // filled up by nemhelper->get_ss_param_global()
     990         850 :   nemhelper->read_sideset_info();
     991             : 
     992         850 :   if (_verbose)
     993             :     {
     994           0 :       libMesh::out << "[" << this->processor_id() << "] "
     995           0 :                    << "nemhelper->num_side_sets = " << nemhelper->num_side_sets << std::endl;
     996             : 
     997           0 :       libMesh::out << "[" << this->processor_id() << "] "
     998           0 :                    << "nemhelper->num_elem_all_sidesets = " << nemhelper->num_elem_all_sidesets << std::endl;
     999             : 
    1000           0 :       if (nemhelper->num_side_sets > 0)
    1001             :         {
    1002           0 :           libMesh::out << "Sideset names are: ";
    1003           0 :           for (const auto & [id, name] : nemhelper->id_to_ss_names)
    1004           0 :             libMesh::out << "(" << id << "," << name << ") ";
    1005           0 :           libMesh::out << std::endl;
    1006             :         }
    1007             :     }
    1008             : 
    1009             : #ifdef DEBUG
    1010             :   {
    1011             :     // In DEBUG mode, check that the global number of sidesets reported
    1012             :     // in each nemesis file matches the sum of all local sideset counts
    1013             :     // from each processor.  This requires a small communication, so only
    1014             :     // do it in DEBUG mode.
    1015          24 :     int sum_num_global_side_counts = std::accumulate(nemhelper->num_global_side_counts.begin(),
    1016          24 :                                                      nemhelper->num_global_side_counts.end(),
    1017             :                                                      0);
    1018             : 
    1019             :     // MPI sum up the local files contributions
    1020          24 :     int sum_num_elem_all_sidesets = nemhelper->num_elem_all_sidesets;
    1021          24 :     this->comm().sum(sum_num_elem_all_sidesets);
    1022             : 
    1023          24 :     libmesh_error_msg_if(sum_num_global_side_counts != sum_num_elem_all_sidesets,
    1024             :                          "Error! global side count reported by Nemesis does not "
    1025             :                          "match the side count reported by the individual files!");
    1026             :   }
    1027             : #endif
    1028             : 
    1029             :   // Note that exodus stores sidesets in separate vectors but we want to pack
    1030             :   // them all into a single vector.  So when we call read_sideset(), we pass an offset
    1031             :   // into the single vector of all IDs
    1032        4250 :   for (int offset=0, i=0; i<nemhelper->num_side_sets; i++)
    1033             :     {
    1034        3400 :       offset += (i > 0 ? nemhelper->num_sides_per_set[i-1] : 0); // Compute new offset
    1035        3400 :       nemhelper->read_sideset (i, offset);
    1036             :     }
    1037             : 
    1038             :   // Now that we have the lists of elements, sides, and IDs, we are ready to set them
    1039             :   // in the BoundaryInfo object of our Mesh object.  This is slightly different in parallel...
    1040             :   // For example, I think the IDs in each of the split Exodus files are numbered locally,
    1041             :   // and we have to know the appropriate ID for this processor to be able to set the
    1042             :   // entry in BoundaryInfo.  This id should be given by
    1043             :   // elem_num_map[i]-1 for the local index i
    1044             : 
    1045             :   // Debugging:
    1046             :   // Print entries of elem_list
    1047             :   // libMesh::out << "[" << this->processor_id() << "] "
    1048             :   //        << "elem_list = ";
    1049             :   // for (const auto & id : nemhelper->elem_list)
    1050             :   //   libMesh::out << id << ", ";
    1051             :   // libMesh::out << std::endl;
    1052             : 
    1053             :   // Print entries of side_list
    1054             :   // libMesh::out << "[" << this->processor_id() << "] "
    1055             :   //        << "side_list = ";
    1056             :   // for (const auto & id : nemhelper->side_list)
    1057             :   //   libMesh::out << id << ", ";
    1058             :   // libMesh::out << std::endl;
    1059             : 
    1060             : 
    1061             :   // Loop over the entries of the elem_list, get their pointers from the
    1062             :   // Mesh data structure, and assign the appropriate side to the BoundaryInfo object.
    1063        2170 :   for (auto e : index_range(nemhelper->elem_list))
    1064             :     {
    1065             :       // Exodus numbering is 1-based
    1066        1320 :       const std::size_t local_id = nemhelper->elem_list[e]-1;
    1067        1320 :       const dof_id_type elem_id = nemhelper->elem_num_map[local_id]-1;
    1068             : 
    1069        1320 :       Elem * elem = mesh.elem_ptr(elem_id);
    1070             : 
    1071             :       // The side numberings in libmesh and exodus are not 1:1, so we need to map
    1072             :       // whatever side number is stored in Exodus into a libmesh side number using
    1073             :       // a conv object...
    1074        1320 :       const auto & conv = nemhelper->get_conversion(elem->type());
    1075             : 
    1076             :       // Finally, we are ready to add the element and its side to the BoundaryInfo object.
    1077             :       // Call the version of add_side which takes a pointer, since we have already gone to
    1078             :       // the trouble of getting said pointer...
    1079        1320 :       mesh.get_boundary_info().add_side(elem,
    1080        1432 :                                         cast_int<unsigned short>(conv.get_side_map(nemhelper->side_list[e]-1)), // Exodus numbering is 1-based
    1081        1432 :                                         cast_int<boundary_id_type>(nemhelper->id_list[e]));
    1082             :     }
    1083             : 
    1084        4250 :   for (const auto & [id, name] : nemhelper->id_to_ss_names)
    1085        3400 :     if (name != "")
    1086        3400 :       mesh.get_boundary_info().sideset_name(id) = name;
    1087             : 
    1088             :   // Debugging: make sure there are as many boundary conditions in the
    1089             :   // boundary ID object as expected.  Note that, at this point, the
    1090             :   // mesh still thinks it's serial, so n_boundary_conds() returns the
    1091             :   // local number of boundary conditions (and is therefore cheap)
    1092             :   // which should match nemhelper->elem_list.size().
    1093             :   {
    1094         850 :     std::size_t nbcs = mesh.get_boundary_info().n_boundary_conds();
    1095         874 :     libmesh_error_msg_if(nbcs != nemhelper->elem_list.size(),
    1096             :                          "[" << this->processor_id() << "] "
    1097             :                          << "BoundaryInfo contains "
    1098             :                          << nbcs
    1099             :                          << " boundary conditions, while the Exodus file had "
    1100             :                          << nemhelper->elem_list.size());
    1101             :   }
    1102             : 
    1103             :   // Read global nodeset parameters?  We might be able to use this to verify
    1104             :   // something about the local files, but I haven't figured out what yet...
    1105         850 :   nemhelper->get_ns_param_global();
    1106             : 
    1107             :   // Read local nodeset info
    1108         850 :   nemhelper->read_nodeset_info();
    1109             : 
    1110         850 :   if (_verbose)
    1111             :     {
    1112           0 :       libMesh::out << "[" << this->processor_id() << "] ";
    1113           0 :       libMesh::out << "nemhelper->num_node_sets=" << nemhelper->num_node_sets << std::endl;
    1114           0 :       if (nemhelper->num_node_sets > 0)
    1115             :         {
    1116           0 :           libMesh::out << "Nodeset names are: ";
    1117           0 :           for (const auto & [id, name] : nemhelper->id_to_ns_names)
    1118           0 :             libMesh::out << "(" << id << "," << name << ") ";
    1119           0 :           libMesh::out << std::endl;
    1120             :         }
    1121             :     }
    1122             : 
    1123             :   //  // Debugging, what is currently in nemhelper->node_num_map anyway?
    1124             :   //  libMesh::out << "[" << this->processor_id() << "] "
    1125             :   //       << "nemhelper->node_num_map = ";
    1126             :   //
    1127             :   //  for (const auto & id : nemhelper->node_num_map)
    1128             :   //    libMesh::out << id << ", ";
    1129             :   //  libMesh::out << std::endl;
    1130             : 
    1131             :   // For each nodeset,
    1132        4250 :   for (int nodeset=0; nodeset<nemhelper->num_node_sets; nodeset++)
    1133             :     {
    1134             :       // Get the user-defined ID associated with the nodeset
    1135        3400 :       int nodeset_id = nemhelper->nodeset_ids[nodeset];
    1136             : 
    1137        3400 :       if (_verbose)
    1138             :         {
    1139           0 :           libMesh::out << "[" << this->processor_id() << "] ";
    1140           0 :           libMesh::out << "nemhelper->nodeset_ids[" << nodeset << "]=" << nodeset_id << std::endl;
    1141             :         }
    1142             : 
    1143             :       // Read the nodeset from file, store them in a vector
    1144        3400 :       nemhelper->read_nodeset(nodeset);
    1145             : 
    1146             :       // Add nodes from the node_list to the BoundaryInfo object
    1147        5752 :       for (auto node : index_range(nemhelper->node_list))
    1148             :         {
    1149             :           // Don't run past the end of our node map!
    1150        2736 :           libmesh_error_msg_if(to_uint(nemhelper->node_list[node]-1) >= nemhelper->node_num_map.size(),
    1151             :                                "Error, index is past the end of node_num_map array!");
    1152             : 
    1153             :           // We should be able to use the node_num_map data structure set up previously to determine
    1154             :           // the proper global node index.
    1155        2352 :           unsigned global_node_id = nemhelper->node_num_map[ nemhelper->node_list[node]-1 /*Exodus is 1-based!*/ ]-1;
    1156             : 
    1157        2352 :           if (_verbose)
    1158             :             {
    1159           0 :               libMesh::out << "[" << this->processor_id() << "] "
    1160           0 :                            << "nodeset " << nodeset
    1161           0 :                            << ", local node number: " << nemhelper->node_list[node]-1
    1162           0 :                            << ", global node id: " << global_node_id
    1163           0 :                            << std::endl;
    1164             :             }
    1165             : 
    1166             :           // Add the node to the BoundaryInfo object with the proper nodeset_id
    1167         192 :           mesh.get_boundary_info().add_node
    1168        2352 :             (cast_int<dof_id_type>(global_node_id),
    1169         192 :              cast_int<boundary_id_type>(nodeset_id));
    1170             :         }
    1171             :     }
    1172             : 
    1173        4250 :   for (const auto & [id, name] : nemhelper->id_to_ns_names)
    1174        3400 :     if (name != "")
    1175        3400 :       mesh.get_boundary_info().nodeset_name(id) = name;
    1176             : 
    1177             :   // See what the elem count is up to now.
    1178         850 :   if (_verbose)
    1179             :     {
    1180             :       // Report the number of elements which have been added locally
    1181           0 :       libMesh::out << "[" << this->processor_id() << "] ";
    1182           0 :       libMesh::out << "mesh.n_elem()=" << mesh.n_elem() << std::endl;
    1183             : 
    1184             :       // Reports the number of elements that have been added in total.
    1185           0 :       libMesh::out << "[" << this->processor_id() << "] ";
    1186           0 :       libMesh::out << "mesh.parallel_n_elem()=" << mesh.parallel_n_elem() << std::endl;
    1187             :     }
    1188             : 
    1189             :   // For DistributedMesh, it seems that _is_serial is true by default.  A hack to
    1190             :   // make the Mesh think it's parallel might be to call:
    1191         850 :   mesh.update_post_partitioning();
    1192         850 :   MeshCommunication().make_node_unique_ids_parallel_consistent(mesh);
    1193         850 :   mesh.delete_remote_elements();
    1194             : 
    1195             :   // If that didn't work, then we're actually reading into a
    1196             :   // ReplicatedMesh, so we want to gather *all* elements
    1197         850 :   if (mesh.is_serial())
    1198             :     // Don't just use mesh.allgather(); that's a no-op, since
    1199             :     // ReplicatedMesh didn't expect to be distributed in the first
    1200             :     // place!
    1201         425 :     MeshCommunication().allgather(mesh);
    1202             :   else
    1203             :     // Gather neighboring elements so that a distributed mesh has the
    1204             :     // proper "ghost" neighbor information.
    1205         425 :     MeshCommunication().gather_neighboring_elements(cast_ref<DistributedMesh &>(mesh));
    1206             : 
    1207             : #ifdef LIBMESH_ENABLE_UNIQUE_ID
    1208             :   // We've been setting unique_ids by hand; let's make sure that later
    1209             :   // ones are consistent with them.
    1210         850 :   mesh.set_next_unique_id(mesh.parallel_max_unique_id()+1);
    1211             : #endif
    1212        2454 : }
    1213             : 
    1214             : #else
    1215             : 
    1216             : void Nemesis_IO::read (const std::string &)
    1217             : {
    1218             :   libmesh_error_msg("ERROR, Nemesis API is not defined!");
    1219             : }
    1220             : 
    1221             : #endif // #if defined(LIBMESH_HAVE_EXODUS_API) && defined(LIBMESH_HAVE_NEMESIS_API)
    1222             : 
    1223             : 
    1224             : 
    1225             : 
    1226             : 
    1227             : #if defined(LIBMESH_HAVE_EXODUS_API) && defined(LIBMESH_HAVE_NEMESIS_API)
    1228             : 
    1229        7313 : void Nemesis_IO::write (const std::string & base_filename)
    1230             : {
    1231             :   // Get a constant reference to the mesh for writing
    1232         412 :   const MeshBase & mesh = MeshOutput<MeshBase>::mesh();
    1233             : 
    1234             :   // Create the filename for this processor given the base_filename passed in.
    1235        7519 :   std::string nemesis_filename = nemhelper->construct_nemesis_filename(base_filename);
    1236             : 
    1237             :   // If the user has set the append flag here, it doesn't really make
    1238             :   // sense: the intent of this function is to write a Mesh with no
    1239             :   // data, while "appending" is really intended to add data to an
    1240             :   // existing file.  If we're verbose, print a message to this effect.
    1241         206 :   if (_append && _verbose)
    1242             :     libmesh_warning("Warning: Appending in Nemesis_IO::write() does not make sense.\n"
    1243             :                     "Creating a new file instead!");
    1244             : 
    1245       14420 :   nemhelper->create(nemesis_filename);
    1246             : 
    1247             :   // Initialize data structures and write some global Nemesis-specific data, such as
    1248             :   // communication maps, to file.
    1249        7313 :   nemhelper->initialize(nemesis_filename,mesh);
    1250             : 
    1251             :   // Make sure we're writing communication maps we can reuse as
    1252             :   // expected when reading
    1253        7313 :   this->assert_symmetric_cmaps();
    1254             : 
    1255             :   // Call the Nemesis-specialized version of write_nodal_coordinates() to write
    1256             :   // the nodal coordinates.
    1257        7313 :   nemhelper->write_nodal_coordinates(mesh);
    1258             : 
    1259             :   // Call the Nemesis-specialized version of write_elements() to write
    1260             :   // the elements.  Note: Must write a zero if a given global block ID has no
    1261             :   // elements...
    1262        7313 :   nemhelper->write_elements(mesh);
    1263             : 
    1264             :   // Call our specialized function to write the nodesets
    1265        7313 :   nemhelper->write_nodesets(mesh);
    1266             : 
    1267             :   // Call our specialized write_sidesets() function to write the sidesets to file
    1268        7313 :   nemhelper->write_sidesets(mesh);
    1269             : 
    1270             :   // Not sure if this is really necessary, but go ahead and flush the file
    1271             :   // once we have written all this stuff.
    1272        7313 :   nemhelper->update();
    1273             : 
    1274        7313 :   if ((mesh.get_boundary_info().n_edge_conds() > 0) && _verbose)
    1275             :     libmesh_warning("Warning: Mesh contains edge boundary IDs, but these "
    1276             :                     "are not supported by the Nemesis format.");
    1277        7313 : }
    1278             : 
    1279             : #else
    1280             : 
    1281             : void Nemesis_IO::write (const std::string & )
    1282             : {
    1283             :   libmesh_error_msg("ERROR, Nemesis API is not defined!");
    1284             : }
    1285             : 
    1286             : #endif // #if defined(LIBMESH_HAVE_EXODUS_API) && defined(LIBMESH_HAVE_NEMESIS_API)
    1287             : 
    1288             : 
    1289             : #if defined(LIBMESH_HAVE_EXODUS_API) && defined(LIBMESH_HAVE_NEMESIS_API)
    1290             : 
    1291           0 : void Nemesis_IO::write_timestep (const std::string & fname,
    1292             :                                  const EquationSystems & es,
    1293             :                                  const int timestep,
    1294             :                                  const Real time)
    1295             : {
    1296           0 :   _timestep=timestep;
    1297           0 :   write_equation_systems(fname,es);
    1298             : 
    1299           0 :   nemhelper->write_timestep(timestep, time);
    1300           0 : }
    1301             : 
    1302             : #else
    1303             : 
    1304             : void Nemesis_IO::write_timestep (const std::string &,
    1305             :                                  const EquationSystems &,
    1306             :                                  const int,
    1307             :                                  const Real)
    1308             : {
    1309             :   libmesh_error_msg("ERROR, Nemesis API is not defined!");
    1310             : }
    1311             : 
    1312             : #endif // #if defined(LIBMESH_HAVE_EXODUS_API) && defined(LIBMESH_HAVE_NEMESIS_API)
    1313             : 
    1314             : 
    1315             : 
    1316             : #if defined(LIBMESH_HAVE_EXODUS_API) && defined(LIBMESH_HAVE_NEMESIS_API)
    1317             : 
    1318         708 : void Nemesis_IO::prepare_to_write_nodal_data (const std::string & fname,
    1319             :                                               const std::vector<std::string> & names)
    1320             : {
    1321          40 :   const MeshBase & mesh = MeshOutput<MeshBase>::mesh();
    1322             : 
    1323         708 :   std::string nemesis_filename = nemhelper->construct_nemesis_filename(fname);
    1324             : 
    1325         708 :   if (!nemhelper->opened_for_writing)
    1326             :     {
    1327             :       // If we're appending, open() the file with read_only=false,
    1328             :       // otherwise create() it and write the contents of the mesh to
    1329             :       // it.
    1330         708 :       if (_append)
    1331             :         {
    1332           0 :           nemhelper->open(nemesis_filename.c_str(), /*read_only=*/false);
    1333             :           // After opening the file, read the header so that certain
    1334             :           // fields, such as the number of nodes and the number of
    1335             :           // elements, are correctly initialized for the subsequent
    1336             :           // call to write the nodal solution.
    1337           0 :           nemhelper->read_and_store_header_info();
    1338             : 
    1339             :           // ...and reading the block info
    1340           0 :           nemhelper->read_block_info();
    1341             : 
    1342             :           // ...and rebuild the "exodus_node_num_to_libmesh" map
    1343           0 :           nemhelper->compute_num_global_elem_blocks(mesh);
    1344           0 :           nemhelper->build_element_and_node_maps(mesh);
    1345             :         }
    1346             :       else
    1347             :         {
    1348        1396 :           nemhelper->create(nemesis_filename);
    1349         708 :           nemhelper->initialize(nemesis_filename,mesh);
    1350             : 
    1351             :           // Make sure we're writing communication maps we can reuse
    1352             :           // as expected when reading
    1353         708 :           this->assert_symmetric_cmaps();
    1354             : 
    1355         708 :           nemhelper->write_nodal_coordinates(mesh);
    1356         708 :           nemhelper->write_elements(mesh);
    1357         708 :           nemhelper->write_nodesets(mesh);
    1358         708 :           nemhelper->write_sidesets(mesh);
    1359             : 
    1360         708 :           if ((mesh.get_boundary_info().n_edge_conds() > 0) && _verbose)
    1361             :             libmesh_warning("Warning: Mesh contains edge boundary IDs, but these "
    1362             :                             "are not supported by the ExodusII format.");
    1363             :         }
    1364             :     }
    1365             : 
    1366             :   // Even if we were already open for writing, we might not have
    1367             :   // initialized the nodal variable names yet. Even if we did, it
    1368             :   // should not hurt to call this twice because the routine sets a
    1369             :   // flag the first time it is called.
    1370             : #ifdef LIBMESH_USE_COMPLEX_NUMBERS
    1371             :   std::vector<std::string> complex_names =
    1372           8 :     nemhelper->get_complex_names(names, nemhelper->write_complex_abs);
    1373           8 :   nemhelper->initialize_nodal_variables(complex_names);
    1374             : #else
    1375         700 :   nemhelper->initialize_nodal_variables(names);
    1376             : #endif
    1377         716 : }
    1378             : 
    1379             : #else
    1380             : 
    1381             : void Nemesis_IO::prepare_to_write_nodal_data (const std::string &,
    1382             :                                               const std::vector<std::string> &)
    1383             : {
    1384             :   libmesh_error_msg("ERROR, Nemesis API is not defined.");
    1385             : }
    1386             : 
    1387             : #endif
    1388             : 
    1389             : 
    1390             : 
    1391             : #if defined(LIBMESH_HAVE_EXODUS_API) && defined(LIBMESH_HAVE_NEMESIS_API)
    1392             : 
    1393           0 : void Nemesis_IO::write_nodal_data (const std::string & base_filename,
    1394             :                                    const NumericVector<Number> & parallel_soln,
    1395             :                                    const std::vector<std::string> & names)
    1396             : {
    1397           0 :   LOG_SCOPE("write_nodal_data(parallel)", "Nemesis_IO");
    1398             : 
    1399             :   // Only prepare and write nodal variables that are also in
    1400             :   // _output_variables, unless _output_variables is empty. This is the
    1401             :   // same logic that is in ExodusII_IO::write_nodal_data().
    1402           0 :   std::vector<std::string> output_names;
    1403             : 
    1404           0 :   if (_allow_empty_variables || !_output_variables.empty())
    1405           0 :     output_names = _output_variables;
    1406             :   else
    1407           0 :     output_names = names;
    1408             : 
    1409           0 :   this->prepare_to_write_nodal_data(base_filename, output_names);
    1410             : 
    1411             :   // Call the new version of write_nodal_solution() that takes a
    1412             :   // NumericVector directly without localizing.
    1413           0 :   nemhelper->write_nodal_solution(parallel_soln, names, _timestep, output_names);
    1414           0 : }
    1415             : 
    1416             : 
    1417             : 
    1418         708 : void Nemesis_IO::write_nodal_data (const std::string & base_filename,
    1419             :                                    const EquationSystems & es,
    1420             :                                    const std::set<std::string> * system_names)
    1421             : {
    1422          40 :   LOG_SCOPE("write_nodal_data(parallel)", "Nemesis_IO");
    1423             : 
    1424             :   // Only prepare and write nodal variables that are also in
    1425             :   // _output_variables, unless _output_variables is empty. This is the
    1426             :   // same logic that is in ExodusII_IO::write_nodal_data().
    1427          60 :   std::vector<std::string> output_names;
    1428             : 
    1429         708 :   if (_allow_empty_variables || !_output_variables.empty())
    1430           0 :     output_names = _output_variables;
    1431             :   else
    1432         708 :     es.build_variable_names  (output_names, nullptr, system_names);
    1433             : 
    1434         708 :   this->prepare_to_write_nodal_data(base_filename, output_names);
    1435             : 
    1436          40 :   std::vector<std::pair<unsigned int, unsigned int>> var_nums;
    1437             :   // If we pass in an empty vector below, it will return all of the
    1438             :   // var nums in es, which we don't want.
    1439         708 :   if (!output_names.empty())
    1440         560 :     var_nums = es.find_variable_numbers(output_names);
    1441             : 
    1442         708 :   nemhelper->write_nodal_solution(es, var_nums, _timestep, output_names);
    1443         708 : }
    1444             : 
    1445             : #else
    1446             : 
    1447             : void Nemesis_IO::write_nodal_data (const std::string &,
    1448             :                                    const NumericVector<Number> &,
    1449             :                                    const std::vector<std::string> &)
    1450             : {
    1451             :   libmesh_error_msg("ERROR, Nemesis API is not defined.");
    1452             : }
    1453             : 
    1454             : void Nemesis_IO::write_nodal_data (const std::string &,
    1455             :                                    const EquationSystems &,
    1456             :                                    const std::set<std::string> *)
    1457             : {
    1458             :   libmesh_error_msg("ERROR, Nemesis API is not defined.");
    1459             : }
    1460             : 
    1461             : #endif
    1462             : 
    1463             : 
    1464             : 
    1465             : #if defined(LIBMESH_HAVE_EXODUS_API) && defined(LIBMESH_HAVE_NEMESIS_API)
    1466             : 
    1467        7595 : void Nemesis_IO::write_element_data (const EquationSystems & es)
    1468             : {
    1469        7595 :   libmesh_error_msg_if(!nemhelper->opened_for_writing,
    1470             :                        "ERROR, Nemesis file must be initialized before outputting elemental variables.");
    1471             : 
    1472             :   // To be (possibly) filled with a filtered list of variable names to output.
    1473         428 :   std::vector<std::string> names;
    1474             : 
    1475             :   // If _output_variables is populated, find_elemental_data_variable_numbers()
    1476             :   // will filter this list to the variables that can be written as elemental data.
    1477        7595 :   if (_output_variables.size())
    1478           0 :     names.assign(_output_variables.begin(), _output_variables.end());
    1479             : 
    1480             :   // The 'names' vector will here be updated with the variable's names
    1481             :   // that are actually eligible to write
    1482             :   std::vector<std::pair<unsigned int, unsigned int>> var_nums =
    1483        7595 :     es.find_elemental_data_variable_numbers(names);
    1484             : 
    1485             :   // find_variable_numbers() can return an empty vector, in which case there are no elemental data
    1486             :   // variables to write, and we can just return.
    1487        7595 :   if (var_nums.empty())
    1488             :     {
    1489           0 :       if (_verbose)
    1490           0 :         libMesh::out << "No elemental data variables to be written." << std::endl;
    1491           0 :       return;
    1492             :     }
    1493             : 
    1494             :   // Store the list of subdomains on which each variable *that we are
    1495             :   // going to plot* is active. Note: if any of these sets is _empty_,
    1496             :   // the variable in question is active on _all_ subdomains.
    1497         642 :   std::vector<std::set<subdomain_id_type>> vars_active_subdomains;
    1498        7595 :   es.get_vars_active_subdomains(names, vars_active_subdomains);
    1499             : 
    1500         428 :   const MeshBase & mesh = MeshOutput<MeshBase>::mesh();
    1501             : 
    1502             : #ifdef LIBMESH_USE_COMPLEX_NUMBERS
    1503             :   std::vector<std::string> complex_names =
    1504         105 :     nemhelper->get_complex_names(names, nemhelper->write_complex_abs);
    1505             : 
    1506             :   std::vector<std::set<subdomain_id_type>>
    1507             :     complex_vars_active_subdomains =
    1508             :     nemhelper->get_complex_vars_active_subdomains(vars_active_subdomains,
    1509         105 :                                                   nemhelper->write_complex_abs);
    1510         105 :   nemhelper->initialize_element_variables(complex_names, complex_vars_active_subdomains);
    1511             : 
    1512             :   // Call (non-virtual) function to write the elemental data in
    1513             :   // parallel.  This function is named similarly to the corresponding
    1514             :   // function in the Exodus helper, but it has a different calling
    1515             :   // sequence and is not virtual or an override.
    1516         105 :   nemhelper->write_element_values(mesh,
    1517             :                                   es,
    1518             :                                   var_nums,
    1519             :                                   _timestep,
    1520             :                                   complex_vars_active_subdomains);
    1521             : 
    1522             : #else
    1523             :   // Call the Nemesis version of initialize_element_variables().
    1524             :   //
    1525             :   // The Exodus helper version of this function writes an incorrect
    1526             :   // truth table in parallel that somehow does not account for the
    1527             :   // case where a subdomain does not appear on one or more of the
    1528             :   // processors. So, we override that function's behavior in the
    1529             :   // Nemesis helper.
    1530        7490 :   nemhelper->initialize_element_variables(names, vars_active_subdomains);
    1531             : 
    1532             :   // Call (non-virtual) function to write the elemental data in
    1533             :   // parallel.  This function is named similarly to the corresponding
    1534             :   // function in the Exodus helper, but it has a different calling
    1535             :   // sequence and is not virtual or an override.
    1536        7490 :   nemhelper->write_element_values(mesh,
    1537             :                                   es,
    1538             :                                   var_nums,
    1539             :                                   _timestep,
    1540             :                                   vars_active_subdomains);
    1541             : #endif
    1542       14334 : }
    1543             : 
    1544             : #else
    1545             : 
    1546             : void Nemesis_IO::write_element_data (const EquationSystems &)
    1547             : {
    1548             :   libmesh_not_implemented();
    1549             : }
    1550             : 
    1551             : #endif
    1552             : 
    1553             : 
    1554             : 
    1555             : #if defined(LIBMESH_HAVE_EXODUS_API) && defined(LIBMESH_HAVE_NEMESIS_API)
    1556             : 
    1557           0 : void Nemesis_IO::write_nodal_data (const std::string & base_filename,
    1558             :                                    const std::vector<Number> & soln,
    1559             :                                    const std::vector<std::string> & names)
    1560             : {
    1561           0 :   LOG_SCOPE("write_nodal_data(serialized)", "Nemesis_IO");
    1562             : 
    1563           0 :   this->prepare_to_write_nodal_data(base_filename, names);
    1564             : 
    1565           0 :   nemhelper->write_nodal_solution(soln, names, _timestep);
    1566           0 : }
    1567             : 
    1568             : #else
    1569             : 
    1570             : void Nemesis_IO::write_nodal_data (const std::string &,
    1571             :                                    const std::vector<Number> &,
    1572             :                                    const std::vector<std::string> &)
    1573             : {
    1574             :   libmesh_error_msg("ERROR, Nemesis API is not defined.");
    1575             : }
    1576             : 
    1577             : #endif
    1578             : 
    1579             : 
    1580             : 
    1581             : 
    1582             : #if defined(LIBMESH_HAVE_EXODUS_API) && defined(LIBMESH_HAVE_NEMESIS_API)
    1583             : 
    1584           0 : void Nemesis_IO::write_global_data (const std::vector<Number> & soln,
    1585             :                                     const std::vector<std::string> & names)
    1586             : {
    1587           0 :   libmesh_error_msg_if(!nemhelper->opened_for_writing,
    1588             :                        "ERROR, Nemesis file must be initialized before outputting global variables.");
    1589             : 
    1590             : #ifdef LIBMESH_USE_COMPLEX_NUMBERS
    1591             : 
    1592             :   std::vector<std::string> complex_names =
    1593           0 :     nemhelper->get_complex_names(names, nemhelper->write_complex_abs);
    1594             : 
    1595           0 :   nemhelper->initialize_global_variables(complex_names);
    1596             : 
    1597           0 :   unsigned int num_values = soln.size();
    1598           0 :   unsigned int num_vars = names.size();
    1599           0 :   unsigned int num_elems = num_values / num_vars;
    1600             : 
    1601             :   // This will contain the real and imaginary parts and the magnitude
    1602             :   // of the values in soln
    1603           0 :   int nco = nemhelper->write_complex_abs ? 3 : 2;
    1604           0 :   std::vector<Real> complex_soln(nco * num_values);
    1605             : 
    1606           0 :   for (unsigned i=0; i<num_vars; ++i)
    1607             :     {
    1608           0 :       for (unsigned int j=0; j<num_elems; ++j)
    1609             :         {
    1610           0 :           Number value = soln[i*num_vars + j];
    1611           0 :           complex_soln[nco*i*num_elems + j] = value.real();
    1612             :         }
    1613           0 :       for (unsigned int j=0; j<num_elems; ++j)
    1614             :         {
    1615           0 :           Number value = soln[i*num_vars + j];
    1616           0 :           complex_soln[nco*i*num_elems + num_elems + j] = value.imag();
    1617             :         }
    1618           0 :       if (nemhelper->write_complex_abs)
    1619             :         {
    1620           0 :           for (unsigned int j=0; j<num_elems; ++j)
    1621             :             {
    1622           0 :               Number value = soln[i*num_vars + j];
    1623           0 :               complex_soln[3*i*num_elems + 2*num_elems + j] = std::abs(value);
    1624             :             }
    1625             :         }
    1626             :     }
    1627             : 
    1628           0 :   nemhelper->write_global_values(complex_soln, _timestep);
    1629             : 
    1630             : #else
    1631             : 
    1632             :   // Call the Exodus writer implementation
    1633           0 :   nemhelper->initialize_global_variables( names );
    1634           0 :   nemhelper->write_global_values( soln, _timestep);
    1635             : 
    1636             : #endif
    1637             : 
    1638           0 : }
    1639             : 
    1640             : #else
    1641             : 
    1642             : void Nemesis_IO::write_global_data (const std::vector<Number> &,
    1643             :                                     const std::vector<std::string> &)
    1644             : {
    1645             :   libmesh_error_msg("ERROR, Nemesis API is not defined.");
    1646             : }
    1647             : 
    1648             : #endif // #if defined(LIBMESH_HAVE_EXODUS_API) && defined(LIBMESH_HAVE_NEMESIS_API)
    1649             : 
    1650             : 
    1651             : 
    1652             : #if defined(LIBMESH_HAVE_EXODUS_API) && defined(LIBMESH_HAVE_NEMESIS_API)
    1653             : 
    1654           0 : void Nemesis_IO::write_information_records (const std::vector<std::string> & records)
    1655             : {
    1656           0 :   libmesh_error_msg_if(!nemhelper->opened_for_writing,
    1657             :                        "ERROR, Nemesis file must be initialized before outputting information records.");
    1658             : 
    1659             :   // Call the Exodus writer implementation
    1660           0 :   nemhelper->write_information_records( records );
    1661           0 : }
    1662             : 
    1663             : 
    1664         142 : const std::vector<std::string> & Nemesis_IO::get_nodal_var_names()
    1665             : {
    1666         142 :   nemhelper->read_var_names(ExodusII_IO_Helper::NODAL);
    1667         142 :   return nemhelper->nodal_var_names;
    1668             : }
    1669             : 
    1670           0 : const std::vector<std::string> & Nemesis_IO::get_elem_var_names()
    1671             : {
    1672           0 :   nemhelper->read_var_names(ExodusII_IO_Helper::ELEMENTAL);
    1673           0 :   return nemhelper->elem_var_names;
    1674             : }
    1675             : 
    1676           0 : const std::vector<std::string> & Nemesis_IO::get_global_var_names()
    1677             : {
    1678           0 :   nemhelper->read_var_names(ExodusII_IO_Helper::GLOBAL);
    1679           0 :   return nemhelper->global_var_names;
    1680             : }
    1681             : 
    1682             : 
    1683           0 : const std::vector<Real> & Nemesis_IO::get_time_steps()
    1684             : {
    1685           0 :   libmesh_error_msg_if
    1686             :     (!nemhelper->opened_for_reading,
    1687             :      "ERROR, ExodusII file must be opened for reading before calling Nemesis_IO::get_time_steps()!");
    1688             : 
    1689           0 :   nemhelper->read_time_steps();
    1690           0 :   return nemhelper->time_steps;
    1691             : }
    1692             : 
    1693             : 
    1694           0 : int Nemesis_IO::get_num_time_steps()
    1695             : {
    1696           0 :   libmesh_error_msg_if(!nemhelper->opened_for_reading && !nemhelper->opened_for_writing,
    1697             :                        "ERROR, ExodusII file must be opened for reading or writing before calling Nemesis_IO::get_num_time_steps()!");
    1698             : 
    1699           0 :   nemhelper->read_num_time_steps();
    1700           0 :   return nemhelper->num_time_steps;
    1701             : }
    1702             : 
    1703             : 
    1704         580 : void Nemesis_IO::copy_nodal_solution(System & system,
    1705             :                                      std::string system_var_name,
    1706             :                                      std::string exodus_var_name,
    1707             :                                      unsigned int timestep)
    1708             : {
    1709         580 :   libmesh_error_msg_if(!nemhelper->opened_for_reading,
    1710             :                        "ERROR, Nemesis file must be opened for reading before copying a nodal solution!");
    1711             : 
    1712        1144 :   nemhelper->read_nodal_var_values(exodus_var_name, timestep);
    1713             : 
    1714         580 :   const unsigned int var_num = system.variable_number(system_var_name);
    1715             : 
    1716        2100 :   for (auto p : nemhelper->nodal_var_values)
    1717             :     {
    1718         115 :       dof_id_type i = p.first;
    1719        1520 :       const Node * node = MeshInput<MeshBase>::mesh().node_ptr(i);
    1720             : 
    1721        1520 :       if (node && node->n_comp(system.number(), var_num) > 0)
    1722             :         {
    1723        1520 :           dof_id_type dof_index = node->dof_number(system.number(), var_num, 0);
    1724             : 
    1725             :           // If the dof_index is local to this processor, set the value
    1726        1405 :           if (system.get_dof_map().local_index(dof_index))
    1727        1140 :             system.solution->set (dof_index, p.second);
    1728             :         }
    1729             :     }
    1730             : 
    1731         580 :   system.solution->close();
    1732         580 :   system.update();
    1733         580 : }
    1734             : 
    1735             : 
    1736             : 
    1737        1128 : void Nemesis_IO::copy_elemental_solution(System & system,
    1738             :                                          std::string system_var_name,
    1739             :                                          std::string exodus_var_name,
    1740             :                                          unsigned int timestep)
    1741             : {
    1742          32 :   parallel_object_only();
    1743             : 
    1744        1128 :   const unsigned int var_num = system.variable_number(system_var_name);
    1745        1128 :   const auto & var_type = system.variable_type(var_num);
    1746        1128 :   libmesh_error_msg_if(!EquationSystems::is_elemental_data_fe_type(var_type) ||
    1747             :                        FEInterface::field_type(var_type) == TYPE_VECTOR,
    1748             :                        "Error! Trying to copy elemental solution into a variable that is not scalar elemental data.");
    1749             : 
    1750        1128 :   const MeshBase & mesh = MeshInput<MeshBase>::mesh();
    1751             : 
    1752             :   // Map from element ID to elemental variable value.  We need to use
    1753             :   // a map here rather than a vector (e.g. elem_var_values) since the
    1754             :   // libmesh element numbering can contain "holes".  This is the case
    1755             :   // if we are reading elemental var values from an adaptively refined
    1756             :   // mesh that has not been sequentially renumbered.
    1757          64 :   std::map<dof_id_type, Real> elem_var_value_map;
    1758             : 
    1759        1128 :   libmesh_error_msg_if(!nemhelper->opened_for_reading,
    1760             :                        "ERROR, Nemesis file must be opened for reading before copying an elemental solution!");
    1761             : 
    1762        2224 :   nemhelper->read_elemental_var_values(exodus_var_name, timestep, elem_var_value_map);
    1763             : 
    1764             :   std::map<dof_id_type, Real>::iterator
    1765          32 :     it = elem_var_value_map.begin(),
    1766          32 :     end = elem_var_value_map.end();
    1767             : 
    1768        2592 :   for (; it!=end; ++it)
    1769             :     {
    1770        1464 :       const Elem * elem = mesh.query_elem_ptr(it->first);
    1771             : 
    1772        1464 :       if (elem && elem->n_comp(system.number(), var_num) > 0)
    1773             :         {
    1774        1464 :           dof_id_type dof_index = elem->dof_number(system.number(), var_num, 0);
    1775         128 :           libmesh_assert(system.get_dof_map().local_index(dof_index));
    1776        1464 :           system.solution->set (dof_index, it->second);
    1777             :         }
    1778             :     }
    1779             : 
    1780        1128 :   system.solution->close();
    1781        1128 :   system.update();
    1782             : 
    1783          32 :   parallel_object_only();
    1784        1128 : }
    1785             : 
    1786             : 
    1787             : 
    1788           0 : void Nemesis_IO::copy_scalar_solution(System & system,
    1789             :                                       std::vector<std::string> system_var_names,
    1790             :                                       std::vector<std::string> exodus_var_names,
    1791             :                                       unsigned int timestep)
    1792             : {
    1793           0 :   libmesh_error_msg_if(!nemhelper->opened_for_reading,
    1794             :                        "ERROR, Nemesis file must be opened for reading before copying a scalar solution!");
    1795             : 
    1796           0 :   libmesh_error_msg_if(system_var_names.size() != exodus_var_names.size(),
    1797             :                        "ERROR, the number of system_var_names must match exodus_var_names.");
    1798             : 
    1799           0 :   std::vector<Real> values_from_exodus;
    1800           0 :   read_global_variable(exodus_var_names, timestep, values_from_exodus);
    1801             : 
    1802           0 :   if (system.processor_id() == (system.n_processors()-1))
    1803             :   {
    1804           0 :     const DofMap & dof_map = system.get_dof_map();
    1805             : 
    1806           0 :     for (auto i : index_range(system_var_names))
    1807             :     {
    1808           0 :       const unsigned int var_num = system.variable_scalar_number(system_var_names[i], 0);
    1809             : 
    1810           0 :       std::vector<dof_id_type> SCALAR_dofs;
    1811           0 :       dof_map.SCALAR_dof_indices(SCALAR_dofs, var_num);
    1812             : 
    1813           0 :       system.solution->set (SCALAR_dofs[0], values_from_exodus[i]);
    1814             :     }
    1815             :   }
    1816             : 
    1817           0 :   system.solution->close();
    1818           0 :   system.update();
    1819           0 : }
    1820             : 
    1821             : 
    1822           0 : void Nemesis_IO::read_global_variable(std::vector<std::string> global_var_names,
    1823             :                                        unsigned int timestep,
    1824             :                                        std::vector<Real> & global_values)
    1825             : {
    1826           0 :   std::size_t size = global_var_names.size();
    1827           0 :   libmesh_error_msg_if(size == 0, "ERROR, empty list of global variables to read from the Nemesis file.");
    1828             : 
    1829             :   // read the values for all global variables
    1830           0 :   std::vector<Real> values_from_exodus;
    1831           0 :   nemhelper->read_var_names(ExodusII_IO_Helper::GLOBAL);
    1832           0 :   nemhelper->read_global_values(values_from_exodus, timestep);
    1833           0 :   std::vector<std::string> global_var_names_exodus = nemhelper->global_var_names;
    1834             : 
    1835           0 :   if (values_from_exodus.size() == 0)
    1836           0 :     return;   // This will happen in parallel on procs that are not 0
    1837             : 
    1838           0 :   global_values.clear();
    1839           0 :   for (std::size_t i = 0; i != size; ++i)
    1840             :     {
    1841             :       // for each global variable in global_var_names, look the corresponding one in global_var_names_from_exodus
    1842             :       // and fill global_values accordingly
    1843           0 :       auto it = find(global_var_names_exodus.begin(), global_var_names_exodus.end(), global_var_names[i]);
    1844           0 :       if (it != global_var_names_exodus.end())
    1845           0 :         global_values.push_back(values_from_exodus[it - global_var_names_exodus.begin()]);
    1846             :       else
    1847           0 :         libmesh_error_msg("ERROR, Global variable " << global_var_names[i] << \
    1848             :                           " not found in Nemesis file.");
    1849             :     }
    1850           0 : }
    1851             : 
    1852           0 : Nemesis_IO_Helper & Nemesis_IO::get_nemio_helper()
    1853             : {
    1854             :   // Provide a warning when accessing the helper object
    1855             :   // since it is a non-public API and is likely to see
    1856             :   // future API changes
    1857             :   libmesh_experimental();
    1858             : 
    1859           0 :   return *nemhelper;
    1860             : }
    1861             : 
    1862           0 : void Nemesis_IO::set_hdf5_writing(bool write_hdf5)
    1863             : {
    1864           0 :     nemhelper->set_hdf5_writing(write_hdf5);
    1865           0 : }
    1866             : 
    1867             : #else
    1868             : 
    1869             : void Nemesis_IO::write_information_records ( const std::vector<std::string> & )
    1870             : {
    1871             :   libmesh_error_msg("ERROR, Nemesis API is not defined.");
    1872             : }
    1873             : 
    1874             : const std::vector<std::string> & Nemesis_IO::get_elem_var_names()
    1875             : {
    1876             :   libmesh_error_msg("ERROR, Nemesis API is not defined.");
    1877             : 
    1878             :   // Prevent potential compiler warnings about missing return statement
    1879             :   return _output_variables;
    1880             : }
    1881             : 
    1882             : const std::vector<std::string> & Nemesis_IO::get_nodal_var_names()
    1883             : {
    1884             :   libmesh_error_msg("ERROR, Nemesis API is not defined.");
    1885             : 
    1886             :   // Prevent potential compiler warnings about missing return statement
    1887             :   return _output_variables;
    1888             : }
    1889             : 
    1890             : const std::vector<std::string> & Nemesis_IO::get_global_var_names()
    1891             : {
    1892             :   libmesh_error_msg("ERROR, Nemesis API is not defined.");
    1893             : 
    1894             :   // Prevent potential compiler warnings about missing return statement
    1895             :   return _output_variables;
    1896             : }
    1897             : 
    1898             : const std::vector<Real> & Nemesis_IO::get_time_steps()
    1899             : {
    1900             :   libmesh_error_msg("ERROR, Nemesis API is not defined.");
    1901             : }
    1902             : 
    1903             : int Nemesis_IO::get_num_time_steps()
    1904             : {
    1905             :   libmesh_error_msg("ERROR, Nemesis API is not defined.");
    1906             : }
    1907             : 
    1908             : 
    1909             : 
    1910             : void Nemesis_IO::copy_nodal_solution(System &, std::string, std::string, unsigned int)
    1911             : {
    1912             :   libmesh_error_msg("ERROR, Nemesis API is not defined.");
    1913             : }
    1914             : 
    1915             : void Nemesis_IO::set_hdf5_writing(bool) {}
    1916             : 
    1917             : #endif // #if defined(LIBMESH_HAVE_EXODUS_API) && defined(LIBMESH_HAVE_NEMESIS_API)
    1918             : 
    1919             : 
    1920             : 
    1921             : } // namespace libMesh

Generated by: LCOV version 1.14