LCOV - code coverage report
Current view: top level - src/mesh - nemesis_io_helper.C (source / functions) Hit Total Coverage
Test: libMesh/libmesh: #4229 (6a9aeb) with base 727f46 Lines: 903 1218 74.1 %
Date: 2025-08-19 19:27:09 Functions: 49 55 89.1 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : // The libMesh Finite Element Library.
       2             : // Copyright (C) 2002-2025 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             : // C++ headers
      20             : #include <iomanip>
      21             : #include <set>
      22             : #include <sstream>
      23             : 
      24             : // Libmesh headers
      25             : #include "libmesh/nemesis_io_helper.h"
      26             : 
      27             : #include "libmesh/boundary_info.h"
      28             : #include "libmesh/dof_map.h"
      29             : #include "libmesh/elem.h"
      30             : #include "libmesh/equation_systems.h"
      31             : #include "libmesh/fe_interface.h"
      32             : #include "libmesh/int_range.h"
      33             : #include "libmesh/mesh_base.h"
      34             : #include "libmesh/node.h"
      35             : #include "libmesh/numeric_vector.h"
      36             : #include "libmesh/parallel.h"
      37             : #include "libmesh/utility.h"
      38             : 
      39             : #if defined(LIBMESH_HAVE_NEMESIS_API) && defined(LIBMESH_HAVE_EXODUS_API)
      40             : 
      41             : #include <libmesh/ignore_warnings.h>
      42             : namespace exII {
      43             : extern "C" {
      44             : #include "exodusII.h" // defines MAX_LINE_LENGTH, MAX_STR_LENGTH used later
      45             : }
      46             : }
      47             : #include <libmesh/restore_warnings.h>
      48             : 
      49             : namespace libMesh
      50             : {
      51             : 
      52             : 
      53             : // Initialize the various integer members to zero.  We can check
      54             : // these later to see if they've been properly initialized...
      55             : // The parent ExodusII_IO_Helper is created with the run_only_on_proc0
      56             : // flag set to false, so that we can make use of its functionality
      57             : // on multiple processors.
      58        8871 : Nemesis_IO_Helper::Nemesis_IO_Helper(const ParallelObject & parent,
      59        8871 :                                      bool verbose_in, bool single_precision) :
      60             :   ExodusII_IO_Helper(parent, verbose_in, /*run_only_on_proc0=*/false, /*single_precision=*/single_precision),
      61        8371 :   nemesis_err_flag(0),
      62        8371 :   num_nodes_global(0),
      63        8371 :   num_elems_global(0),
      64        8371 :   num_elem_blks_global(0),
      65        8371 :   num_node_sets_global(0),
      66        8371 :   num_side_sets_global(0),
      67        8371 :   num_proc(0),
      68        8371 :   num_proc_in_file(0),
      69        8371 :   ftype('\0'),
      70        8371 :   num_internal_nodes(0),
      71        8371 :   num_border_nodes(0),
      72        8371 :   num_external_nodes(0),
      73        8371 :   num_internal_elems(0),
      74        8371 :   num_border_elems(0),
      75        8371 :   num_node_cmaps(0),
      76        8371 :   num_elem_cmaps(0),
      77        8871 :   write_complex_abs(true)
      78             : {
      79             :   // Warn about using untested code!
      80             :   libmesh_experimental();
      81        8871 : }
      82             : 
      83             : 
      84       17992 : Nemesis_IO_Helper::~Nemesis_IO_Helper()
      85             : {
      86             :   // Our destructor is called from Nemesis_IO.  We close the Exodus file here since we have
      87             :   // responsibility for managing the file's lifetime.  Only call ex_update() if the file was
      88             :   // opened for writing!
      89        8871 :   if (this->opened_for_writing)
      90             :     {
      91        8021 :       this->ex_err = exII::ex_update(this->ex_id);
      92        8021 :       EX_EXCEPTIONLESS_CHECK_ERR(ex_err, "Error flushing buffers to file.");
      93             :     }
      94        8871 :   this->close();
      95       26113 : }
      96             : 
      97             : 
      98             : 
      99        3400 : void Nemesis_IO_Helper::read_nodeset(int id)
     100             : {
     101          96 :   libmesh_assert_less (id, nodeset_ids.size());
     102          96 :   libmesh_assert_less (id, num_nodes_per_set.size());
     103          96 :   libmesh_assert_less (id, num_node_df_per_set.size());
     104             : 
     105        3400 :   ex_err = exII::ex_get_set_param(ex_id,
     106             :                                   exII::EX_NODE_SET,
     107         192 :                                   nodeset_ids[id],
     108         192 :                                   &num_nodes_per_set[id],
     109        3400 :                                   &num_node_df_per_set[id]);
     110        3400 :   EX_CHECK_ERR(ex_err, "Error retrieving nodeset parameters.");
     111        3400 :   message("Parameters retrieved successfully for nodeset: ", id);
     112             : 
     113        3496 :   node_list.resize(num_nodes_per_set[id]);
     114             : 
     115             :   // Don't call ex_get_set unless there are actually nodes there to get.
     116             :   // Exodus prints an annoying warning message in DEBUG mode otherwise...
     117        3496 :   if (num_nodes_per_set[id] > 0)
     118             :     {
     119         936 :       ex_err = exII::ex_get_set(ex_id,
     120             :                                 exII::EX_NODE_SET,
     121         144 :                                 nodeset_ids[id],
     122         144 :                                 node_list.data(),
     123             :                                 nullptr); // set_extra_list, ignored for node sets
     124             : 
     125         936 :       EX_CHECK_ERR(ex_err, "Error retrieving nodeset data.");
     126         936 :       message("Data retrieved successfully for nodeset: ", id);
     127             :     }
     128        3400 : }
     129             : 
     130             : 
     131             : 
     132         850 : void Nemesis_IO_Helper::get_init_global()
     133             : {
     134         850 :   nemesis_err_flag =
     135         874 :     Nemesis::ne_get_init_global(ex_id,
     136         850 :                                 &num_nodes_global,
     137         850 :                                 &num_elems_global,
     138         850 :                                 &num_elem_blks_global,
     139         850 :                                 &num_node_sets_global,
     140         850 :                                 &num_side_sets_global);
     141         850 :   EX_CHECK_ERR(nemesis_err_flag, "Error reading initial global data!");
     142             : 
     143         850 :   if (verbose)
     144             :     {
     145           0 :       libMesh::out << "[" << this->processor_id() << "] " << "num_nodes_global=" << num_nodes_global << std::endl;
     146           0 :       libMesh::out << "[" << this->processor_id() << "] " << "num_elems_global=" << num_elems_global << std::endl;
     147           0 :       libMesh::out << "[" << this->processor_id() << "] " << "num_elem_blks_global=" << num_elem_blks_global << std::endl;
     148           0 :       libMesh::out << "[" << this->processor_id() << "] " << "num_node_sets_global=" << num_node_sets_global << std::endl;
     149           0 :       libMesh::out << "[" << this->processor_id() << "] " << "num_side_sets_global=" << num_side_sets_global << std::endl;
     150             :     }
     151         850 : }
     152             : 
     153             : 
     154             : 
     155         850 : void Nemesis_IO_Helper::get_ss_param_global()
     156             : {
     157         850 :   if (num_side_sets_global > 0)
     158             :     {
     159         850 :       global_sideset_ids.resize(num_side_sets_global);
     160         850 :       num_global_side_counts.resize(num_side_sets_global);
     161             : 
     162             :       // df = "distribution factor", not really sure what that is.  I don't yet have a file
     163             :       // which has distribution factors so I guess we'll worry about it later...
     164         850 :       num_global_side_df_counts.resize(num_side_sets_global);
     165             : 
     166         850 :       nemesis_err_flag =
     167         850 :         Nemesis::ne_get_ss_param_global(ex_id,
     168          48 :                                         global_sideset_ids.data(),
     169          48 :                                         num_global_side_counts.data(),
     170          48 :                                         num_global_side_df_counts.data());
     171         850 :       EX_CHECK_ERR(nemesis_err_flag, "Error reading global sideset parameters!");
     172             : 
     173         850 :       if (verbose)
     174             :         {
     175           0 :           libMesh::out << "[" << this->processor_id() << "] " << "Global Sideset IDs, Side Counts, and DF counts:" << std::endl;
     176           0 :           for (auto bn : index_range(global_sideset_ids))
     177             :             {
     178           0 :               libMesh::out << "  [" << this->processor_id() << "] "
     179           0 :                            << "global_sideset_ids["<<bn<<"]=" << global_sideset_ids[bn]
     180           0 :                            << ", num_global_side_counts["<<bn<<"]=" << num_global_side_counts[bn]
     181           0 :                            << ", num_global_side_df_counts["<<bn<<"]=" << num_global_side_df_counts[bn]
     182           0 :                            << std::endl;
     183             :             }
     184             :         }
     185             :     }
     186         850 : }
     187             : 
     188             : 
     189             : 
     190             : 
     191         850 : void Nemesis_IO_Helper::get_ns_param_global()
     192             : {
     193         850 :   if (num_node_sets_global > 0)
     194             :     {
     195         850 :       global_nodeset_ids.resize(num_node_sets_global);
     196         850 :       num_global_node_counts.resize(num_node_sets_global);
     197         850 :       num_global_node_df_counts.resize(num_node_sets_global);
     198             : 
     199         850 :       nemesis_err_flag =
     200         850 :         Nemesis::ne_get_ns_param_global(ex_id,
     201          48 :                                         global_nodeset_ids.data(),
     202          48 :                                         num_global_node_counts.data(),
     203          48 :                                         num_global_node_df_counts.data());
     204         850 :       EX_CHECK_ERR(nemesis_err_flag, "Error reading global nodeset parameters!");
     205             : 
     206         850 :       if (verbose)
     207             :         {
     208           0 :           libMesh::out << "[" << this->processor_id() << "] " << "Global Nodeset IDs, Node Counts, and DF counts:" << std::endl;
     209           0 :           for (auto bn : index_range(global_nodeset_ids))
     210             :             {
     211           0 :               libMesh::out << "  [" << this->processor_id() << "] "
     212           0 :                            << "global_nodeset_ids["<<bn<<"]=" << global_nodeset_ids[bn]
     213           0 :                            << ", num_global_node_counts["<<bn<<"]=" << num_global_node_counts[bn]
     214           0 :                            << ", num_global_node_df_counts["<<bn<<"]=" << num_global_node_df_counts[bn]
     215           0 :                            << std::endl;
     216             :             }
     217             :         }
     218             :     }
     219         850 : }
     220             : 
     221             : 
     222             : 
     223         850 : void Nemesis_IO_Helper::get_eb_info_global()
     224             : {
     225         850 :   global_elem_blk_ids.resize(num_elem_blks_global);
     226         850 :   global_elem_blk_cnts.resize(num_elem_blks_global);
     227             : 
     228         850 :   if (num_elem_blks_global > 0)
     229             :     {
     230         850 :       nemesis_err_flag =
     231         850 :         Nemesis::ne_get_eb_info_global(ex_id,
     232          48 :                                        global_elem_blk_ids.data(),
     233          48 :                                        global_elem_blk_cnts.data());
     234         850 :       EX_CHECK_ERR(nemesis_err_flag, "Error reading global element block info!");
     235             :     }
     236             : 
     237         850 :   if (verbose)
     238             :     {
     239           0 :       libMesh::out << "[" << this->processor_id() << "] " << "Global Element Block IDs and Counts:" << std::endl;
     240           0 :       for (auto bn : index_range(global_elem_blk_ids))
     241             :         {
     242           0 :           libMesh::out << "  [" << this->processor_id() << "] "
     243           0 :                        << "global_elem_blk_ids[" << bn << "]=" << global_elem_blk_ids[bn]
     244           0 :                        << ", global_elem_blk_cnts[" << bn << "]=" << global_elem_blk_cnts[bn]
     245           0 :                        << std::endl;
     246             :         }
     247             :     }
     248         850 : }
     249             : 
     250             : 
     251             : 
     252           0 : void Nemesis_IO_Helper::get_init_info()
     253             : {
     254           0 :   nemesis_err_flag =
     255           0 :     Nemesis::ne_get_init_info(ex_id,
     256             :                               &num_proc,
     257             :                               &num_proc_in_file,
     258             :                               &ftype);
     259           0 :   EX_CHECK_ERR(nemesis_err_flag, "Error reading initial info!");
     260             : 
     261           0 :   if (verbose)
     262             :     {
     263           0 :       libMesh::out << "[" << this->processor_id() << "] " << "num_proc=" << num_proc << std::endl;
     264           0 :       libMesh::out << "[" << this->processor_id() << "] " << "num_proc_in_file=" << num_proc_in_file << std::endl;
     265           0 :       libMesh::out << "[" << this->processor_id() << "] " << "ftype=" << ftype << std::endl;
     266             :     }
     267           0 : }
     268             : 
     269             : 
     270             : 
     271         850 : void Nemesis_IO_Helper::get_loadbal_param()
     272             : {
     273         850 :   nemesis_err_flag =
     274         922 :     Nemesis::ne_get_loadbal_param(ex_id,
     275         850 :                                   &num_internal_nodes,
     276         850 :                                   &num_border_nodes,
     277         850 :                                   &num_external_nodes,
     278         850 :                                   &num_internal_elems,
     279         850 :                                   &num_border_elems,
     280         850 :                                   &num_node_cmaps,
     281         850 :                                   &num_elem_cmaps,
     282          48 :                                   this->processor_id() // The ID of the processor for which info is to be read
     283             :                                   );
     284         850 :   EX_CHECK_ERR(nemesis_err_flag, "Error reading load balance parameters!");
     285             : 
     286             : 
     287         850 :   if (verbose)
     288             :     {
     289           0 :       libMesh::out << "[" << this->processor_id() << "] " << "num_internal_nodes=" << num_internal_nodes << std::endl;
     290           0 :       libMesh::out << "[" << this->processor_id() << "] " << "num_border_nodes=" << num_border_nodes << std::endl;
     291           0 :       libMesh::out << "[" << this->processor_id() << "] " << "num_external_nodes=" << num_external_nodes << std::endl;
     292           0 :       libMesh::out << "[" << this->processor_id() << "] " << "num_internal_elems=" << num_internal_elems << std::endl;
     293           0 :       libMesh::out << "[" << this->processor_id() << "] " << "num_border_elems=" << num_border_elems << std::endl;
     294           0 :       libMesh::out << "[" << this->processor_id() << "] " << "num_node_cmaps=" << num_node_cmaps << std::endl;
     295           0 :       libMesh::out << "[" << this->processor_id() << "] " << "num_elem_cmaps=" << num_elem_cmaps << std::endl;
     296             :     }
     297         850 : }
     298             : 
     299             : 
     300             : 
     301           0 : void Nemesis_IO_Helper::get_elem_map()
     302             : {
     303           0 :   elem_mapi.resize(num_internal_elems);
     304           0 :   elem_mapb.resize(num_border_elems);
     305             : 
     306           0 :   nemesis_err_flag =
     307           0 :     Nemesis::ne_get_elem_map(ex_id,
     308           0 :                              elem_mapi.empty() ? nullptr : elem_mapi.data(),
     309           0 :                              elem_mapb.empty() ? nullptr : elem_mapb.data(),
     310           0 :                              this->processor_id()
     311             :                              );
     312           0 :   EX_CHECK_ERR(nemesis_err_flag, "Error reading element maps!");
     313             : 
     314             : 
     315           0 :   if (verbose)
     316             :     {
     317           0 :       libMesh::out << "[" << this->processor_id() << "] elem_mapi[i] = ";
     318           0 :       for (auto i : make_range(num_internal_elems-1))
     319           0 :         libMesh::out << elem_mapi[i] << ", ";
     320           0 :       libMesh::out << "... " << elem_mapi.back() << std::endl;
     321             : 
     322           0 :       libMesh::out << "[" << this->processor_id() << "] elem_mapb[i] = ";
     323           0 :       for (auto i : make_range(std::min(10, num_border_elems-1)))
     324           0 :         libMesh::out << elem_mapb[i] << ", ";
     325           0 :       libMesh::out << "... " << elem_mapb.back() << std::endl;
     326             :     }
     327           0 : }
     328             : 
     329             : 
     330             : 
     331             : 
     332         850 : void Nemesis_IO_Helper::get_node_map()
     333             : {
     334         850 :   node_mapi.resize(num_internal_nodes);
     335         850 :   node_mapb.resize(num_border_nodes);
     336         850 :   node_mape.resize(num_external_nodes);
     337             : 
     338         850 :   nemesis_err_flag =
     339        1616 :     Nemesis::ne_get_node_map(ex_id,
     340          46 :                              node_mapi.empty() ? nullptr : node_mapi.data(),
     341          44 :                              node_mapb.empty() ? nullptr : node_mapb.data(),
     342           0 :                              node_mape.empty() ? nullptr : node_mape.data(),
     343          48 :                              this->processor_id()
     344             :                              );
     345         850 :   EX_CHECK_ERR(nemesis_err_flag, "Error reading node maps!");
     346             : 
     347         850 :   if (verbose)
     348             :     {
     349             :       // Remark: The Exodus/Nemesis node numbering is always (?) 1-based!  This means the first interior node id will
     350             :       // always be == 1.
     351           0 :       libMesh::out << "[" << this->processor_id() << "] " << "first interior node id=" << node_mapi[0] << std::endl;
     352           0 :       libMesh::out << "[" << this->processor_id() << "] " << "last interior node id=" << node_mapi.back() << std::endl;
     353             : 
     354           0 :       libMesh::out << "[" << this->processor_id() << "] " << "first boundary node id=" << node_mapb[0] << std::endl;
     355           0 :       libMesh::out << "[" << this->processor_id() << "] " << "last boundary node id=" << node_mapb.back() << std::endl;
     356             : 
     357             :       // The number of external nodes is sometimes zero, don't try to access
     358             :       // node_mape.back() in this case!
     359           0 :       if (num_external_nodes > 0)
     360             :         {
     361           0 :           libMesh::out << "[" << this->processor_id() << "] " << "first external node id=" << node_mape[0] << std::endl;
     362           0 :           libMesh::out << "[" << this->processor_id() << "] " << "last external node id=" << node_mape.back() << std::endl;
     363             :         }
     364             :     }
     365         850 : }
     366             : 
     367             : 
     368             : 
     369         850 : void Nemesis_IO_Helper::get_cmap_params()
     370             : {
     371         850 :   node_cmap_ids.resize(num_node_cmaps);
     372         850 :   node_cmap_node_cnts.resize(num_node_cmaps);
     373         850 :   elem_cmap_ids.resize(num_elem_cmaps);
     374         850 :   elem_cmap_elem_cnts.resize(num_elem_cmaps);
     375             : 
     376         850 :   nemesis_err_flag =
     377        2358 :     Nemesis::ne_get_cmap_params(ex_id,
     378          44 :                                 node_cmap_ids.empty()       ? nullptr : node_cmap_ids.data(),
     379          44 :                                 node_cmap_node_cnts.empty() ? nullptr : node_cmap_node_cnts.data(),
     380          44 :                                 elem_cmap_ids.empty()       ? nullptr : elem_cmap_ids.data(),
     381          20 :                                 elem_cmap_elem_cnts.empty() ? nullptr : elem_cmap_elem_cnts.data(),
     382          48 :                                 this->processor_id());
     383         850 :   EX_CHECK_ERR(nemesis_err_flag, "Error reading cmap parameters!");
     384             : 
     385             : 
     386         850 :   if (verbose)
     387             :     {
     388           0 :       libMesh::out << "[" << this->processor_id() << "] ";
     389           0 :       for (auto i : index_range(node_cmap_ids))
     390           0 :         libMesh::out << "node_cmap_ids[" << i << "]=" << node_cmap_ids[i] << " ";
     391           0 :       libMesh::out << std::endl;
     392             : 
     393           0 :       libMesh::out << "[" << this->processor_id() << "] ";
     394           0 :       for (auto i : index_range(node_cmap_node_cnts))
     395           0 :         libMesh::out << "node_cmap_node_cnts[" << i << "]=" << node_cmap_node_cnts[i] << " ";
     396           0 :       libMesh::out << std::endl;
     397             : 
     398           0 :       libMesh::out << "[" << this->processor_id() << "] ";
     399           0 :       for (auto i : index_range(elem_cmap_ids))
     400           0 :         libMesh::out << "elem_cmap_ids[" << i << "]=" << elem_cmap_ids[i] << " ";
     401           0 :       libMesh::out << std::endl;
     402             : 
     403           0 :       libMesh::out << "[" << this->processor_id() << "] ";
     404           0 :       for (auto i : index_range(elem_cmap_elem_cnts))
     405           0 :         libMesh::out << "elem_cmap_elem_cnts[" << i << "]=" << elem_cmap_elem_cnts[i] << " ";
     406           0 :       libMesh::out << std::endl;
     407             :     }
     408         850 : }
     409             : 
     410             : 
     411             : 
     412         850 : void Nemesis_IO_Helper::get_node_cmap()
     413             : {
     414         850 :   node_cmap_node_ids.resize(num_node_cmaps);
     415         850 :   node_cmap_proc_ids.resize(num_node_cmaps);
     416             : 
     417        1850 :   for (auto i : index_range(node_cmap_node_ids))
     418             :     {
     419        1040 :       node_cmap_node_ids[i].resize(node_cmap_node_cnts[i]);
     420        1040 :       node_cmap_proc_ids[i].resize(node_cmap_node_cnts[i]);
     421             : 
     422             :       // Don't call ne_get_node_cmap() if there is nothing there to
     423             :       // get, Nemesis throws an error in this case.
     424        1020 :       if (node_cmap_node_cnts[i] > 0)
     425             :         {
     426        1000 :           nemesis_err_flag =
     427        1000 :             Nemesis::ne_get_node_cmap(ex_id,
     428          40 :                                       node_cmap_ids[i],
     429          40 :                                       node_cmap_node_ids[i].data(),
     430          60 :                                       node_cmap_proc_ids[i].data(),
     431          40 :                                       this->processor_id());
     432        1000 :           EX_CHECK_ERR(nemesis_err_flag, "Error reading node cmap node and processor ids!");
     433             :         }
     434             : 
     435        1000 :       if (verbose)
     436             :         {
     437           0 :           libMesh::out << "[" << this->processor_id() << "] node_cmap_node_ids[" << i << "]=";
     438           0 :           for (const auto & dof : node_cmap_node_ids[i])
     439           0 :             libMesh::out << dof << " ";
     440           0 :           libMesh::out << std::endl;
     441             : 
     442             :           // This is basically a vector, all entries of which are = node_cmap_ids[i]
     443             :           // Not sure if it's always guaranteed to be that or what...
     444           0 :           libMesh::out << "[" << this->processor_id() << "] node_cmap_proc_ids[" << i << "]=";
     445           0 :           for (const auto & dof : node_cmap_proc_ids[i])
     446           0 :             libMesh::out << dof << " ";
     447           0 :           libMesh::out << std::endl;
     448             :         }
     449             :     }
     450         850 : }
     451             : 
     452             : 
     453             : 
     454         850 : void Nemesis_IO_Helper::get_elem_cmap()
     455             : {
     456         850 :   elem_cmap_elem_ids.resize(num_elem_cmaps);
     457         850 :   elem_cmap_side_ids.resize(num_elem_cmaps);
     458         850 :   elem_cmap_proc_ids.resize(num_elem_cmaps);
     459             : 
     460        1754 :   for (auto i : index_range(elem_cmap_elem_ids))
     461             :     {
     462         944 :       elem_cmap_elem_ids[i].resize(elem_cmap_elem_cnts[i]);
     463         944 :       elem_cmap_side_ids[i].resize(elem_cmap_elem_cnts[i]);
     464         944 :       elem_cmap_proc_ids[i].resize(elem_cmap_elem_cnts[i]);
     465             : 
     466         924 :       if (elem_cmap_elem_cnts[i] > 0)
     467             :         {
     468         904 :           nemesis_err_flag =
     469         904 :             Nemesis::ne_get_elem_cmap(ex_id,
     470          40 :                                       elem_cmap_ids[i],
     471          40 :                                       elem_cmap_elem_ids[i].data(),
     472          40 :                                       elem_cmap_side_ids[i].data(),
     473          60 :                                       elem_cmap_proc_ids[i].data(),
     474          40 :                                       this->processor_id());
     475         904 :           EX_CHECK_ERR(nemesis_err_flag, "Error reading elem cmap elem, side, and processor ids!");
     476             :         }
     477             : 
     478         904 :       if (verbose)
     479             :         {
     480           0 :           libMesh::out << "[" << this->processor_id() << "] elem_cmap_elem_ids[" << i << "]=";
     481           0 :           for (const auto & dof : elem_cmap_elem_ids[i])
     482           0 :             libMesh::out << dof << " ";
     483           0 :           libMesh::out << std::endl;
     484             : 
     485             :           // These must be the (local) side IDs (in the ExodusII face numbering scheme)
     486             :           // of the sides shared across processors.
     487           0 :           libMesh::out << "[" << this->processor_id() << "] elem_cmap_side_ids[" << i << "]=";
     488           0 :           for (const auto & dof : elem_cmap_side_ids[i])
     489           0 :             libMesh::out << dof << " ";
     490           0 :           libMesh::out << std::endl;
     491             : 
     492             :           // This is basically a vector, all entries of which are = elem_cmap_ids[i]
     493             :           // Not sure if it's always guaranteed to be that or what...
     494           0 :           libMesh::out << "[" << this->processor_id() << "] elem_cmap_proc_ids[" << i << "]=";
     495           0 :           for (const auto & dof : elem_cmap_proc_ids[i])
     496           0 :             libMesh::out << dof << " ";
     497           0 :           libMesh::out << std::endl;
     498             :         }
     499             :     }
     500         850 : }
     501             : 
     502             : 
     503             : 
     504             : 
     505        8021 : void Nemesis_IO_Helper::put_init_info(unsigned num_proc_in,
     506             :                                       unsigned num_proc_in_file_in,
     507             :                                       const char * ftype_in)
     508             : {
     509        8021 :   nemesis_err_flag =
     510        8021 :     Nemesis::ne_put_init_info(ex_id,
     511             :                               num_proc_in,
     512             :                               num_proc_in_file_in,
     513             :                               const_cast<char *>(ftype_in));
     514             : 
     515        8021 :   EX_CHECK_ERR(nemesis_err_flag, "Error writing initial information!");
     516        8021 : }
     517             : 
     518             : 
     519             : 
     520             : 
     521        8021 : void Nemesis_IO_Helper::put_init_global(dof_id_type num_nodes_global_in,
     522             :                                         dof_id_type num_elems_global_in,
     523             :                                         unsigned num_elem_blks_global_in,
     524             :                                         unsigned num_node_sets_global_in,
     525             :                                         unsigned num_side_sets_global_in)
     526             : {
     527        8021 :   nemesis_err_flag =
     528        8021 :     Nemesis::ne_put_init_global(ex_id,
     529             :                                 num_nodes_global_in,
     530             :                                 num_elems_global_in,
     531             :                                 num_elem_blks_global_in,
     532             :                                 num_node_sets_global_in,
     533             :                                 num_side_sets_global_in);
     534             : 
     535        8021 :   EX_CHECK_ERR(nemesis_err_flag, "Error writing initial global data!");
     536        8021 : }
     537             : 
     538             : 
     539             : 
     540        8021 : void Nemesis_IO_Helper::put_eb_info_global(std::vector<int> & global_elem_blk_ids_in,
     541             :                                            std::vector<int> & global_elem_blk_cnts_in)
     542             : {
     543        8021 :   nemesis_err_flag =
     544        8021 :     Nemesis::ne_put_eb_info_global(ex_id,
     545         452 :                                    global_elem_blk_ids_in.data(),
     546         452 :                                    global_elem_blk_cnts_in.data());
     547             : 
     548        8021 :   EX_CHECK_ERR(nemesis_err_flag, "Error writing global element block information!");
     549        8021 : }
     550             : 
     551             : 
     552             : 
     553             : 
     554        8021 : void Nemesis_IO_Helper::put_ns_param_global(std::vector<int> & global_nodeset_ids_in,
     555             :                                             std::vector<int> & num_global_node_counts_in,
     556             :                                             std::vector<int> & num_global_node_df_counts_in)
     557             : {
     558             :   // Only add nodesets if there are some
     559        8021 :   if (global_nodeset_ids.size())
     560             :     {
     561        5110 :       nemesis_err_flag =
     562        5110 :         Nemesis::ne_put_ns_param_global(ex_id,
     563         288 :                                         global_nodeset_ids_in.data(),
     564         288 :                                         num_global_node_counts_in.data(),
     565         288 :                                         num_global_node_df_counts_in.data());
     566             :     }
     567             : 
     568        8021 :   EX_CHECK_ERR(nemesis_err_flag, "Error writing global nodeset parameters!");
     569        8021 : }
     570             : 
     571             : 
     572             : 
     573             : 
     574        8021 : void Nemesis_IO_Helper::put_ss_param_global(std::vector<int> & global_sideset_ids_in,
     575             :                                             std::vector<int> & num_global_side_counts_in,
     576             :                                             std::vector<int> & num_global_side_df_counts_in)
     577             : {
     578             :   // Only add sidesets if there are some
     579        8021 :   if (global_sideset_ids.size())
     580             :     {
     581        8021 :       nemesis_err_flag =
     582        8021 :         Nemesis::ne_put_ss_param_global(ex_id,
     583         452 :                                         global_sideset_ids_in.data(),
     584         452 :                                         num_global_side_counts_in.data(),
     585         452 :                                         num_global_side_df_counts_in.data());
     586             :     }
     587             : 
     588        8021 :   EX_CHECK_ERR(nemesis_err_flag, "Error writing global sideset parameters!");
     589        8021 : }
     590             : 
     591             : 
     592             : 
     593             : 
     594        8021 : void Nemesis_IO_Helper::put_loadbal_param(unsigned num_internal_nodes_in,
     595             :                                           unsigned num_border_nodes_in,
     596             :                                           unsigned num_external_nodes_in,
     597             :                                           unsigned num_internal_elems_in,
     598             :                                           unsigned num_border_elems_in,
     599             :                                           unsigned num_node_cmaps_in,
     600             :                                           unsigned num_elem_cmaps_in)
     601             : {
     602        8021 :   nemesis_err_flag =
     603        8247 :     Nemesis::ne_put_loadbal_param(ex_id,
     604             :                                   num_internal_nodes_in,
     605             :                                   num_border_nodes_in,
     606             :                                   num_external_nodes_in,
     607             :                                   num_internal_elems_in,
     608             :                                   num_border_elems_in,
     609             :                                   num_node_cmaps_in,
     610             :                                   num_elem_cmaps_in,
     611         452 :                                   this->processor_id());
     612             : 
     613        8021 :   EX_CHECK_ERR(nemesis_err_flag, "Error writing loadbal parameters!");
     614        8021 : }
     615             : 
     616             : 
     617             : 
     618             : 
     619             : 
     620        8021 : void Nemesis_IO_Helper::put_cmap_params(std::vector<int> & node_cmap_ids_in,
     621             :                                         std::vector<int> & node_cmap_node_cnts_in,
     622             :                                         std::vector<int> & elem_cmap_ids_in,
     623             :                                         std::vector<int> & elem_cmap_elem_cnts_in)
     624             : {
     625        8021 :   nemesis_err_flag =
     626       24739 :     Nemesis::ne_put_cmap_params(ex_id,
     627         408 :                                 node_cmap_ids_in.empty() ? nullptr : node_cmap_ids_in.data(),
     628         408 :                                 node_cmap_node_cnts_in.empty() ? nullptr : node_cmap_node_cnts_in.data(),
     629         408 :                                 elem_cmap_ids_in.empty() ? nullptr : elem_cmap_ids_in.data(),
     630         182 :                                 elem_cmap_elem_cnts_in.empty() ? nullptr : elem_cmap_elem_cnts_in.data(),
     631         452 :                                 this->processor_id());
     632             : 
     633        8021 :   EX_CHECK_ERR(nemesis_err_flag, "Error writing cmap parameters!");
     634        8021 : }
     635             : 
     636             : 
     637             : 
     638             : 
     639        8021 : void Nemesis_IO_Helper::put_node_cmap(std::vector<std::vector<int>> & node_cmap_node_ids_in,
     640             :                                       std::vector<std::vector<int>> & node_cmap_proc_ids_in)
     641             : {
     642             :   // Print to screen what we are about to print to Nemesis file
     643        8021 :   if (verbose)
     644             :     {
     645           0 :       for (auto i : index_range(node_cmap_node_ids_in))
     646             :         {
     647           0 :           libMesh::out << "[" << this->processor_id() << "] put_node_cmap() : nodes communicated to proc "
     648           0 :                        << this->node_cmap_ids[i]
     649           0 :                        << " = ";
     650           0 :           for (const auto & node_id : node_cmap_node_ids_in[i])
     651           0 :             libMesh::out << node_id << " ";
     652           0 :           libMesh::out << std::endl;
     653             :         }
     654             : 
     655           0 :       for (auto i : index_range(node_cmap_node_ids_in))
     656             :         {
     657           0 :           libMesh::out << "[" << this->processor_id() << "] put_node_cmap() : processor IDs = ";
     658           0 :           for (const auto & proc_id : node_cmap_proc_ids_in[i])
     659           0 :             libMesh::out << proc_id << " ";
     660           0 :           libMesh::out << std::endl;
     661             :         }
     662             :     }
     663             : 
     664       18829 :   for (auto i : index_range(node_cmap_node_ids_in))
     665             :     {
     666       10990 :       int * node_ids_ptr = node_cmap_node_ids_in[i].empty() ?
     667         182 :         nullptr : node_cmap_node_ids_in[i].data();
     668       10990 :       int * proc_ids_ptr = node_cmap_proc_ids_in[i].empty() ?
     669         182 :         nullptr : node_cmap_proc_ids_in[i].data();
     670             : 
     671       10808 :       nemesis_err_flag =
     672       10990 :         Nemesis::ne_put_node_cmap(ex_id, this->node_cmap_ids[i],
     673             :                                   node_ids_ptr, proc_ids_ptr,
     674         364 :                                   this->processor_id());
     675             : 
     676       10808 :       EX_CHECK_ERR(nemesis_err_flag, "Error writing node communication map to file!");
     677             :     }
     678        8021 : }
     679             : 
     680             : 
     681             : 
     682             : 
     683        8021 : void Nemesis_IO_Helper::put_node_map(std::vector<int> & node_mapi_in,
     684             :                                      std::vector<int> & node_mapb_in,
     685             :                                      std::vector<int> & node_mape_in)
     686             : {
     687        8021 :   nemesis_err_flag =
     688       16229 :     Nemesis::ne_put_node_map(ex_id,
     689         430 :                              node_mapi_in.empty() ? nullptr : node_mapi_in.data(),
     690         408 :                              node_mapb_in.empty() ? nullptr : node_mapb_in.data(),
     691           0 :                              node_mape_in.empty() ? nullptr : node_mape_in.data(),
     692         452 :                              this->processor_id());
     693             : 
     694        8021 :   EX_CHECK_ERR(nemesis_err_flag, "Error writing Nemesis internal and border node maps to file!");
     695        8021 : }
     696             : 
     697             : 
     698             : 
     699             : 
     700        8021 : void Nemesis_IO_Helper::put_elem_cmap(std::vector<std::vector<int>> & elem_cmap_elem_ids_in,
     701             :                                       std::vector<std::vector<int>> & elem_cmap_side_ids_in,
     702             :                                       std::vector<std::vector<int>> & elem_cmap_proc_ids_in)
     703             : {
     704       17047 :   for (auto i : index_range(elem_cmap_ids))
     705             :     {
     706        9026 :       nemesis_err_flag =
     707        9026 :         Nemesis::ne_put_elem_cmap(ex_id,
     708         364 :                                   this->elem_cmap_ids[i],
     709         364 :                                   elem_cmap_elem_ids_in[i].data(),
     710         364 :                                   elem_cmap_side_ids_in[i].data(),
     711         546 :                                   elem_cmap_proc_ids_in[i].data(),
     712         364 :                                   this->processor_id());
     713             : 
     714        9026 :       EX_CHECK_ERR(nemesis_err_flag, "Error writing elem communication map to file!");
     715             :     }
     716        8021 : }
     717             : 
     718             : 
     719             : 
     720             : 
     721        8021 : void Nemesis_IO_Helper::put_elem_map(std::vector<int> & elem_mapi_in,
     722             :                                      std::vector<int> & elem_mapb_in)
     723             : {
     724        8021 :   nemesis_err_flag =
     725       14035 :     Nemesis::ne_put_elem_map(ex_id,
     726         393 :                              elem_mapi_in.empty() ? nullptr : elem_mapi_in.data(),
     727         182 :                              elem_mapb_in.empty() ? nullptr : elem_mapb_in.data(),
     728         452 :                              this->processor_id());
     729             : 
     730        8021 :   EX_CHECK_ERR(nemesis_err_flag, "Error writing Nemesis internal and border element maps to file!");
     731        8021 : }
     732             : 
     733             : 
     734             : 
     735        8021 : void Nemesis_IO_Helper::initialize(std::string title_in, const MeshBase & mesh, bool /*use_discontinuous*/)
     736             : {
     737             :   // Make sure that the reference passed in is really a DistributedMesh
     738             :   // const DistributedMesh & pmesh = cast_ref<const DistributedMesh &>(mesh);
     739         226 :   const MeshBase & pmesh = mesh;
     740             : 
     741             :   // If _write_as_dimension is nonzero, use it to set num_dim later in the Exodus file.
     742        8021 :   if (_write_as_dimension)
     743           0 :     num_dim = _write_as_dimension;
     744        8021 :   else if (_use_mesh_dimension_instead_of_spatial_dimension)
     745           0 :     num_dim = mesh.mesh_dimension();
     746             :   else
     747        8021 :     num_dim = mesh.spatial_dimension();
     748             : 
     749             :   // According to Nemesis documentation, first call when writing should be to
     750             :   // ne_put_init_info().  Our reader doesn't actually call this, but we should
     751             :   // strive to be as close to a normal nemesis file as possible...
     752        8247 :   this->put_init_info(this->n_processors(), 1, "p");
     753             : 
     754             : 
     755             :   // Gather global "initial" information for Nemesis.  This consists of
     756             :   // three parts labeled I, II, and III below...
     757             : 
     758             :   //
     759             :   // I.) Need to compute the number of global element blocks.  To be consistent with
     760             :   // Exodus, we also incorrectly associate the number of element blocks with the
     761             :   // number of libmesh subdomains...
     762             :   //
     763        8021 :   this->compute_num_global_elem_blocks(pmesh);
     764             : 
     765             :   //
     766             :   // II.) Determine the global number of nodesets by communication.
     767             :   // This code relies on BoundaryInfo storing side and node
     768             :   // boundary IDs separately at the time they are added to the
     769             :   // BoundaryInfo object.
     770             :   //
     771        8021 :   this->compute_num_global_nodesets(pmesh);
     772             : 
     773             :   //
     774             :   // III.) Need to compute the global number of sidesets by communication:
     775             :   // This code relies on BoundaryInfo storing side and node
     776             :   // boundary IDs separately at the time they are added to the
     777             :   // BoundaryInfo object.
     778             :   //
     779        8021 :   this->compute_num_global_sidesets(pmesh);
     780             : 
     781             :   // Now write the global data obtained in steps I, II, and III to the Nemesis file
     782        8021 :   this->put_init_global(pmesh.parallel_n_nodes(),
     783        8021 :                         pmesh.parallel_n_elem(),
     784        8021 :                         this->num_elem_blks_global, /* I.   */
     785        8021 :                         this->num_node_sets_global, /* II.  */
     786        8021 :                         this->num_side_sets_global  /* III. */
     787             :                         );
     788             : 
     789             :   // Next, we'll write global element block information to the file.  This was already
     790             :   // gathered in step I. above
     791        8021 :   this->put_eb_info_global(this->global_elem_blk_ids,
     792        8021 :                            this->global_elem_blk_cnts);
     793             : 
     794             : 
     795             :   // Next, write global nodeset information to the file.  This was already gathered in
     796             :   // step II. above.
     797        8021 :   this->num_global_node_df_counts.clear();
     798        8247 :   this->num_global_node_df_counts.resize(this->global_nodeset_ids.size()); // distribution factors all zero...
     799        8021 :   this->put_ns_param_global(this->global_nodeset_ids,
     800        8021 :                             this->num_global_node_counts,
     801         226 :                             this->num_global_node_df_counts);
     802             : 
     803             : 
     804             :   // Next, write global sideset information to the file.  This was already gathered in
     805             :   // step III. above.
     806        8021 :   this->num_global_side_df_counts.clear();
     807        8247 :   this->num_global_side_df_counts.resize(this->global_sideset_ids.size()); // distribution factors all zero...
     808        8021 :   this->put_ss_param_global(this->global_sideset_ids,
     809        8021 :                             this->num_global_side_counts,
     810         226 :                             this->num_global_side_df_counts);
     811             : 
     812             : 
     813             :   // Before we go any further we need to derive consistent node and
     814             :   // element numbering schemes for all local elems and nodes connected
     815             :   // to local elements.
     816             :   //
     817             :   // Must be called *after* the local_subdomain_counts map has been constructed
     818             :   // by the compute_num_global_elem_blocks() function!
     819        8021 :   this->build_element_and_node_maps(pmesh);
     820             : 
     821             :   // Next step is to write "load balance" parameters.  Several things need to
     822             :   // be computed first though...
     823             : 
     824             :   // First we'll collect IDs of border nodes.
     825        8021 :   this->compute_border_node_ids(pmesh);
     826             : 
     827             :   // Next we'll collect numbers of internal and border elements, and internal nodes.
     828             :   // Note: "A border node does not a border element make...", that is, just because one
     829             :   // of an element's nodes has been identified as a border node, the element is not
     830             :   // necessarily a border element.  It must have a side on the boundary between processors,
     831             :   // i.e. have a face neighbor with a different processor id...
     832        8021 :   this->compute_internal_and_border_elems_and_internal_nodes(pmesh);
     833             : 
     834             :   // Finally we are ready to write the loadbal information to the file
     835       10281 :   this->put_loadbal_param(this->num_internal_nodes,
     836        8021 :                           this->num_border_nodes,
     837        8021 :                           this->num_external_nodes,
     838        8021 :                           this->num_internal_elems,
     839        8021 :                           this->num_border_elems,
     840        8021 :                           this->num_node_cmaps,
     841        8021 :                           this->num_elem_cmaps);
     842             : 
     843             : 
     844             :   // Now we need to compute the "communication map" parameters.  These are basically
     845             :   // lists of nodes and elements which need to be communicated between different processors
     846             :   // when the mesh file is read back in.
     847        8021 :   this->compute_communication_map_parameters();
     848             : 
     849             :   // Write communication map parameters to file.
     850        8021 :   this->put_cmap_params(this->node_cmap_ids,
     851        8021 :                         this->node_cmap_node_cnts,
     852        8021 :                         this->elem_cmap_ids,
     853        8021 :                         this->elem_cmap_elem_cnts);
     854             : 
     855             :   // Ready the node communication maps.  The node IDs which
     856             :   // are communicated are the ones currently stored in
     857             :   // proc_nodes_touched_intersections.
     858        8021 :   this->compute_node_communication_maps();
     859             : 
     860             :   // Write the packed node communication vectors to file.
     861        8021 :   this->put_node_cmap(this->node_cmap_node_ids,
     862        8021 :                       this->node_cmap_proc_ids);
     863             : 
     864             :   // Ready the node maps.  These have nothing to do with communication, they map
     865             :   // the nodes to internal, border, and external nodes in the file.
     866        8021 :   this->compute_node_maps();
     867             : 
     868             :   // Call the Nemesis API to write the node maps to file.
     869        8021 :   this->put_node_map(this->node_mapi,
     870        8021 :                      this->node_mapb,
     871        8021 :                      this->node_mape);
     872             : 
     873             :   // Ready the element communication maps.  This includes border
     874             :   // element IDs, sides which are on the border, and the processors to which
     875             :   // they are to be communicated...
     876        8021 :   this->compute_elem_communication_maps();
     877             : 
     878             :   // Call the Nemesis API to write the packed element communication maps vectors to file
     879        8021 :   this->put_elem_cmap(this->elem_cmap_elem_ids,
     880        8021 :                       this->elem_cmap_side_ids,
     881        8021 :                       this->elem_cmap_proc_ids);
     882             : 
     883             :   // Ready the Nemesis element maps (internal and border) for writing to file.
     884        8021 :   this->compute_element_maps();
     885             : 
     886             :   // Call the Nemesis API to write the internal and border element IDs.
     887        8021 :   this->put_elem_map(this->elem_mapi,
     888        8021 :                      this->elem_mapb);
     889             : 
     890             :   // Now write Exodus-specific initialization information, some of which is
     891             :   // different when you are using Nemesis.
     892        8021 :   this->write_exodus_initialization_info(pmesh, title_in);
     893        8021 : } // end initialize()
     894             : 
     895             : 
     896             : 
     897             : 
     898             : 
     899             : 
     900        8021 : void Nemesis_IO_Helper::write_exodus_initialization_info(const MeshBase & pmesh,
     901             :                                                          const std::string & title_in)
     902             : {
     903        8021 :   this->num_elem = static_cast<unsigned int>(std::distance (pmesh.active_local_elements_begin(),
     904       15816 :                                                             pmesh.active_local_elements_end()));
     905             : 
     906             :   // Exodus will also use *global* number of side and node sets,
     907             :   // though it will not write out entries for all of them...
     908        8247 :   this->num_side_sets =
     909         452 :     cast_int<int>(this->global_sideset_ids.size());
     910        8247 :   this->num_node_sets =
     911         452 :     cast_int<int>(this->global_nodeset_ids.size());
     912             : 
     913             :   // We need to write the global number of blocks, even though this processor might not have
     914             :   // elements in some of them!
     915        8021 :   this->num_elem_blk = this->num_elem_blks_global;
     916             : 
     917       15590 :   ex_err = exII::ex_put_init(ex_id,
     918             :                              title_in.c_str(),
     919        8021 :                              this->num_dim,
     920        8021 :                              this->num_nodes,
     921        8021 :                              this->num_elem,
     922         226 :                              this->num_elem_blk,
     923        8021 :                              this->num_node_sets,
     924        8021 :                              this->num_side_sets);
     925             : 
     926        8021 :   EX_CHECK_ERR(ex_err, "Error initializing new Nemesis file.");
     927        8021 : }
     928             : 
     929             : 
     930             : 
     931             : 
     932             : 
     933        8021 : void Nemesis_IO_Helper::compute_element_maps()
     934             : {
     935             :   // Make sure we don't have any leftover info
     936        8021 :   this->elem_mapi.clear();
     937        8021 :   this->elem_mapb.clear();
     938             : 
     939             :   // Copy set contents into vectors
     940        8021 :   this->elem_mapi.resize(this->internal_elem_ids.size());
     941        8021 :   this->elem_mapb.resize(this->border_elem_ids.size());
     942             : 
     943             :   {
     944         226 :     unsigned cnt = 0;
     945       19584 :     for (const auto & id : this->internal_elem_ids)
     946       11563 :       this->elem_mapi[cnt++] = libmesh_map_find(libmesh_elem_num_to_exodus, id);
     947             :   }
     948             : 
     949             :   {
     950         226 :     unsigned cnt = 0;
     951       17574 :     for (const auto & id : this->border_elem_ids)
     952        9553 :       this->elem_mapb[cnt++] = libmesh_map_find(libmesh_elem_num_to_exodus, id);
     953             :   }
     954        8021 : }
     955             : 
     956             : 
     957             : 
     958        8021 : void Nemesis_IO_Helper::compute_elem_communication_maps()
     959             : {
     960             :   // Make sure there is no leftover information
     961        7795 :   this->elem_cmap_elem_ids.clear();
     962        7795 :   this->elem_cmap_side_ids.clear();
     963        7795 :   this->elem_cmap_proc_ids.clear();
     964             : 
     965             :   // Allocate enough space for all our element maps
     966        8021 :   this->elem_cmap_elem_ids.resize(this->num_elem_cmaps);
     967        8021 :   this->elem_cmap_side_ids.resize(this->num_elem_cmaps);
     968        8021 :   this->elem_cmap_proc_ids.resize(this->num_elem_cmaps);
     969             :   {
     970         226 :     unsigned cnt=0; // Index into vectors
     971             :     proc_border_elem_sets_iterator
     972         226 :       it = this->proc_border_elem_sets.begin(),
     973         226 :       end = this->proc_border_elem_sets.end();
     974             : 
     975       17047 :     for (; it != end; ++it)
     976             :       {
     977             :         // Make sure the current elem_cmap_id matches the index in our map of node intersections
     978         182 :         libmesh_assert_equal_to (static_cast<unsigned>(this->elem_cmap_ids[cnt]), it->first);
     979             : 
     980             :         // Get reference to the set of IDs to be packed into the vector
     981         182 :         std::set<std::pair<unsigned,unsigned>> & elem_set = it->second;
     982             : 
     983             :         // Resize the vectors to receive their payload
     984        9208 :         this->elem_cmap_elem_ids[cnt].resize(elem_set.size());
     985        9208 :         this->elem_cmap_side_ids[cnt].resize(elem_set.size());
     986        9208 :         this->elem_cmap_proc_ids[cnt].resize(elem_set.size());
     987             : 
     988         182 :         std::set<std::pair<unsigned,unsigned>>::iterator elem_set_iter = elem_set.begin();
     989             : 
     990             :         // Pack the vectors with elem IDs, side IDs, and processor IDs.
     991       24159 :         for (std::size_t j=0, eceis=this->elem_cmap_elem_ids[cnt].size(); j<eceis; ++j, ++elem_set_iter)
     992             :           {
     993       14951 :             this->elem_cmap_elem_ids[cnt][j] =
     994       14951 :               libmesh_map_find(libmesh_elem_num_to_exodus, elem_set_iter->first);
     995       15477 :             this->elem_cmap_side_ids[cnt][j] = elem_set_iter->second;     // Side ID, this has already been converted above
     996       16003 :             this->elem_cmap_proc_ids[cnt][j] = it->first; // All have the same processor ID
     997             :           }
     998             : 
     999             :         // increment vector index to go to next processor
    1000        9026 :         cnt++;
    1001             :       }
    1002             :   } // end scope for packing
    1003        8021 : }
    1004             : 
    1005             : 
    1006             : 
    1007             : 
    1008             : 
    1009        8021 : void Nemesis_IO_Helper::compute_node_maps()
    1010             : {
    1011             :   // Make sure we don't have any leftover information
    1012        8021 :   this->node_mapi.clear();
    1013        8021 :   this->node_mapb.clear();
    1014         226 :   this->node_mape.clear();
    1015             : 
    1016             :   // Make sure there's enough space to hold all our node IDs
    1017        8021 :   this->node_mapi.resize(this->internal_node_ids.size());
    1018        8021 :   this->node_mapb.resize(this->border_node_ids.size());
    1019             : 
    1020             :   // Copy set contents into vectors
    1021             :   {
    1022         226 :     unsigned cnt = 0;
    1023       28417 :     for (const auto & id : this->internal_node_ids)
    1024       20396 :       this->node_mapi[cnt++] = libmesh_map_find(libmesh_node_num_to_exodus, id);
    1025             :   }
    1026             : 
    1027             :   {
    1028         226 :     unsigned cnt=0;
    1029       26160 :     for (const auto & id : this->border_node_ids)
    1030       18139 :       this->node_mapb[cnt++] = libmesh_map_find(libmesh_node_num_to_exodus, id);
    1031             :   }
    1032        8021 : }
    1033             : 
    1034             : 
    1035             : 
    1036             : 
    1037             : 
    1038        8021 : void Nemesis_IO_Helper::compute_node_communication_maps()
    1039             : {
    1040             :   // Make sure there's no left-over information
    1041        7795 :   this->node_cmap_node_ids.clear();
    1042        7795 :   this->node_cmap_proc_ids.clear();
    1043             : 
    1044         226 :   libmesh_assert_less_equal
    1045             :     (this->proc_nodes_touched_intersections.size(),
    1046             :      std::size_t(this->num_node_cmaps));
    1047             : 
    1048             :   // Allocate enough space for all our node maps
    1049        8021 :   this->node_cmap_node_ids.resize(this->num_node_cmaps);
    1050        8021 :   this->node_cmap_proc_ids.resize(this->num_node_cmaps);
    1051             :   {
    1052         226 :     unsigned cnt=0; // Index into vectors
    1053             :     proc_nodes_touched_iterator
    1054         226 :       it = this->proc_nodes_touched_intersections.begin(),
    1055         226 :       end = this->proc_nodes_touched_intersections.end();
    1056             : 
    1057       18829 :     for (; it != end; ++it)
    1058             :       {
    1059             :         // Make sure the current node_cmap_id matches the index in our map of node intersections
    1060         182 :         libmesh_assert_equal_to (static_cast<unsigned>(this->node_cmap_ids[cnt]), it->first);
    1061             : 
    1062             :         // Get reference to the set of IDs to be packed into the vector.
    1063         182 :         std::set<unsigned> & node_set = it->second;
    1064             : 
    1065             :         // Resize the vectors to receive their payload
    1066       10990 :         this->node_cmap_node_ids[cnt].resize(node_set.size());
    1067       10990 :         this->node_cmap_proc_ids[cnt].resize(node_set.size());
    1068             : 
    1069         182 :         std::set<unsigned>::iterator node_set_iter = node_set.begin();
    1070             : 
    1071             :         // Pack the vectors with node IDs and processor IDs.
    1072       37250 :         for (std::size_t j=0, nceis=this->node_cmap_node_ids[cnt].size(); j<nceis; ++j, ++node_set_iter)
    1073             :           {
    1074       26260 :             this->node_cmap_node_ids[cnt][j] =
    1075       26260 :               libmesh_map_find(libmesh_node_num_to_exodus, *node_set_iter);
    1076       27488 :             this->node_cmap_proc_ids[cnt][j] = it->first;
    1077             :           }
    1078             : 
    1079             :         // increment vector index to go to next processor
    1080       10808 :         cnt++;
    1081             :       }
    1082             :   } // end scope for packing
    1083             : 
    1084             :   // Print out the vectors we just packed
    1085        8021 :   if (verbose)
    1086             :     {
    1087           0 :       for (auto i : index_range(this->node_cmap_node_ids))
    1088             :         {
    1089           0 :           libMesh::out << "[" << this->processor_id() << "] nodes communicated to proc "
    1090           0 :                        << this->node_cmap_ids[i]
    1091           0 :                        << " = ";
    1092           0 :           for (const auto & node_id : this->node_cmap_node_ids[i])
    1093           0 :             libMesh::out << node_id << " ";
    1094           0 :           libMesh::out << std::endl;
    1095             :         }
    1096             : 
    1097           0 :       for (const auto & id_vec : this->node_cmap_node_ids)
    1098             :         {
    1099           0 :           libMesh::out << "[" << this->processor_id() << "] processor ID node communicated to = ";
    1100           0 :           for (const auto & proc_id : id_vec)
    1101           0 :             libMesh::out << proc_id << " ";
    1102           0 :           libMesh::out << std::endl;
    1103             :         }
    1104             :     }
    1105        8021 : }
    1106             : 
    1107             : 
    1108             : 
    1109             : 
    1110        8021 : void Nemesis_IO_Helper::compute_communication_map_parameters()
    1111             : {
    1112             :   // For the nodes, these are the number of entries in the sets in proc_nodes_touched_intersections
    1113             :   // map computed above.  Note: this map does not contain self-intersections so we can loop over it
    1114             :   // directly.
    1115        8021 :   this->node_cmap_node_cnts.clear(); // Make sure we don't have any leftover information...
    1116        8021 :   this->node_cmap_ids.clear();       // Make sure we don't have any leftover information...
    1117        8021 :   this->node_cmap_node_cnts.resize(this->num_node_cmaps);
    1118        8021 :   this->node_cmap_ids.resize(this->num_node_cmaps);
    1119             : 
    1120             :   {
    1121         226 :     unsigned cnt=0; // Index into the vector
    1122             :     proc_nodes_touched_iterator
    1123         226 :       it = this->proc_nodes_touched_intersections.begin(),
    1124         226 :       end = this->proc_nodes_touched_intersections.end();
    1125             : 
    1126       18829 :     for (; it != end; ++it)
    1127             :       {
    1128       10990 :         this->node_cmap_ids[cnt] = it->first; // The ID of the proc we communicate with
    1129       10808 :         this->node_cmap_node_cnts[cnt] = cast_int<int>(it->second.size()); // The number of nodes we communicate
    1130       10808 :         cnt++; // increment vector index!
    1131             :       }
    1132             :   }
    1133             : 
    1134             :   // Print the packed vectors we just filled
    1135        8021 :   if (verbose)
    1136             :     {
    1137           0 :       libMesh::out << "[" << this->processor_id() << "] node_cmap_node_cnts = ";
    1138           0 :       for (const auto & node_cnt : node_cmap_node_cnts)
    1139           0 :         libMesh::out << node_cnt << ", ";
    1140           0 :       libMesh::out << std::endl;
    1141             : 
    1142           0 :       libMesh::out << "[" << this->processor_id() << "] node_cmap_ids = ";
    1143           0 :       for (const auto & node_id : node_cmap_ids)
    1144           0 :         libMesh::out << node_id << ", ";
    1145           0 :       libMesh::out << std::endl;
    1146             :     }
    1147             : 
    1148             :   // For the elements, we have not yet computed all this information..
    1149        8021 :   this->elem_cmap_elem_cnts.clear(); // Make sure we don't have any leftover information...
    1150        8021 :   this->elem_cmap_ids.clear();       // Make sure we don't have any leftover information...
    1151        8021 :   this->elem_cmap_elem_cnts.resize(this->num_elem_cmaps);
    1152        8021 :   this->elem_cmap_ids.resize(this->num_elem_cmaps);
    1153             : 
    1154             :   // Pack the elem_cmap_ids and elem_cmap_elem_cnts vectors
    1155             :   {
    1156         226 :     unsigned cnt=0; // Index into the vectors we're filling
    1157             :     proc_border_elem_sets_iterator
    1158         226 :       it = this->proc_border_elem_sets.begin(),
    1159         226 :       end = this->proc_border_elem_sets.end();
    1160             : 
    1161       17047 :     for (; it != end; ++it)
    1162             :       {
    1163        9208 :         this->elem_cmap_ids[cnt] = it->first; // The ID of the proc we communicate with
    1164        9026 :         this->elem_cmap_elem_cnts[cnt] = cast_int<int>(it->second.size()); // The number of elems we communicate to/from that proc
    1165        9026 :         cnt++; // increment vector index!
    1166             :       }
    1167             :   }
    1168             : 
    1169             :   // Print the packed vectors we just filled
    1170        8021 :   if (verbose)
    1171             :     {
    1172           0 :       libMesh::out << "[" << this->processor_id() << "] elem_cmap_elem_cnts = ";
    1173           0 :       for (const auto & elem_cnt : elem_cmap_elem_cnts)
    1174           0 :         libMesh::out << elem_cnt << ", ";
    1175           0 :       libMesh::out << std::endl;
    1176             : 
    1177           0 :       libMesh::out << "[" << this->processor_id() << "] elem_cmap_ids = ";
    1178           0 :       for (const auto & elem_id : elem_cmap_ids)
    1179           0 :         libMesh::out << elem_id << ", ";
    1180           0 :       libMesh::out << std::endl;
    1181             :     }
    1182        8021 : }
    1183             : 
    1184             : 
    1185             : 
    1186             : 
    1187             : void
    1188        8021 : Nemesis_IO_Helper::compute_internal_and_border_elems_and_internal_nodes(const MeshBase & pmesh)
    1189             : {
    1190             :   // Set of all local, active element IDs.  After we have identified border element
    1191             :   // IDs, the set_difference between this set and the border_elem_ids set will give us
    1192             :   // the set of internal_elem_ids.
    1193         452 :   std::set<unsigned> all_elem_ids;
    1194             : 
    1195             :   // A set of processor IDs which elements on this processor have as
    1196             :   // neighbors.  The size of this set will determine the number of
    1197             :   // element communication maps in Exodus.
    1198         452 :   std::set<unsigned> neighboring_processor_ids;
    1199             : 
    1200       54524 :   for (const auto & elem : pmesh.active_local_element_ptr_range())
    1201             :     {
    1202             :       // Add this Elem's ID to all_elem_ids, later we will take the difference
    1203             :       // between this set and the set of border_elem_ids, to get the set of
    1204             :       // internal_elem_ids.
    1205       21116 :       all_elem_ids.insert(elem->id());
    1206             : 
    1207             :       // Will be set to true if element is determined to be a border element
    1208        1762 :       bool is_border_elem = false;
    1209             : 
    1210             :       // Construct a conversion object for this Element.  This will help us map
    1211             :       // Libmesh numberings into Nemesis numberings for sides.
    1212       21116 :       const auto & conv = get_conversion(elem->type());
    1213             : 
    1214             :       // Add all this element's node IDs to the set of all node IDs.
    1215             :       // The set of internal_node_ids will be the set difference between
    1216             :       // the set of all nodes and the set of border nodes.
    1217             :       //
    1218             :       // In addition, if any node of a local node is listed in the
    1219             :       // border nodes list, then this element goes into the proc_border_elem_sets.
    1220             :       // Note that there is not a 1:1 correspondence between
    1221             :       // border_elem_ids and the entries which go into proc_border_elem_sets.
    1222             :       // The latter is for communication purposes, ie determining which elements
    1223             :       // should be shared between processors.
    1224       93466 :       for (auto node : elem->node_index_range())
    1225       76478 :         this->nodes_attached_to_local_elems.insert(elem->node_id(node));
    1226             : 
    1227             :       // Loop over element's neighbors, see if it has a neighbor which is off-processor
    1228       90730 :       for (auto n : elem->side_index_range())
    1229             :         {
    1230       73514 :           if (elem->neighbor_ptr(n) != nullptr)
    1231             :             {
    1232       54124 :               unsigned neighbor_proc_id = elem->neighbor_ptr(n)->processor_id();
    1233             : 
    1234             :               // If my neighbor has a different processor ID, I must be a border element.
    1235             :               // Also track the neighboring processor ID if it is are different from our processor ID
    1236       58640 :               if (neighbor_proc_id != this->processor_id())
    1237             :                 {
    1238         526 :                   is_border_elem = true;
    1239       14425 :                   neighboring_processor_ids.insert(neighbor_proc_id);
    1240             : 
    1241             :                   // Convert libmesh side(n) of this element into a side ID for Nemesis
    1242       14951 :                   unsigned nemesis_side_id = conv.get_inverse_side_map(n);
    1243             : 
    1244       14951 :                   if (verbose)
    1245           0 :                     libMesh::out << "[" << this->processor_id() << "] LibMesh side "
    1246           0 :                                  << n
    1247           0 :                                  << " mapped to (1-based) Exodus side "
    1248           0 :                                  << nemesis_side_id
    1249           0 :                                  << std::endl;
    1250             : 
    1251             :                   // Add this element's ID and the ID of the side which is on the boundary
    1252             :                   // to the set of border elements for this processor.
    1253             :                   // Note: if the set does not already exist, this creates it.
    1254       14951 :                   this->proc_border_elem_sets[ neighbor_proc_id ].emplace(elem->id(), nemesis_side_id);
    1255             :                 }
    1256             :             }
    1257             :         } // end for loop over neighbors
    1258             : 
    1259             :       // If we're on a border element, add it to the set
    1260       21116 :       if (is_border_elem)
    1261        9553 :         this->border_elem_ids.insert( elem->id() );
    1262             : 
    1263        7569 :     } // end for loop over active local elements
    1264             : 
    1265             :   // Take the set_difference between all elements and border elements to get internal
    1266             :   // element IDs
    1267        7569 :   std::set_difference(all_elem_ids.begin(), all_elem_ids.end(),
    1268             :                       this->border_elem_ids.begin(), this->border_elem_ids.end(),
    1269        8021 :                       std::inserter(this->internal_elem_ids, this->internal_elem_ids.end()));
    1270             : 
    1271             :   // Take the set_difference between all nodes and border nodes to get internal nodes
    1272        7569 :   std::set_difference(this->nodes_attached_to_local_elems.begin(), this->nodes_attached_to_local_elems.end(),
    1273             :                       this->border_node_ids.begin(), this->border_node_ids.end(),
    1274        8021 :                       std::inserter(this->internal_node_ids, this->internal_node_ids.end()));
    1275             : 
    1276        8021 :   if (verbose)
    1277             :     {
    1278           0 :       libMesh::out << "[" << this->processor_id() << "] neighboring_processor_ids = ";
    1279           0 :       for (const auto & id : neighboring_processor_ids)
    1280           0 :         libMesh::out << id << " ";
    1281           0 :       libMesh::out << std::endl;
    1282             :     }
    1283             : 
    1284             :   // The size of the neighboring_processor_ids set should be the number of element communication maps
    1285        8021 :   this->num_elem_cmaps =
    1286         226 :     cast_int<int>(neighboring_processor_ids.size());
    1287             : 
    1288        8021 :   if (verbose)
    1289           0 :     libMesh::out << "[" << this->processor_id() << "] "
    1290           0 :                  << "Number of neighboring processor IDs="
    1291           0 :                  << this->num_elem_cmaps
    1292           0 :                  << std::endl;
    1293             : 
    1294        8021 :   if (verbose)
    1295             :     {
    1296             :       // Print out counts of border elements for each processor
    1297           0 :       for (const auto & [proc_id, set] : proc_border_elem_sets)
    1298             :         {
    1299           0 :           libMesh::out << "[" << this->processor_id() << "] "
    1300           0 :                        << "Proc "
    1301           0 :                        << proc_id << " communicates "
    1302           0 :                        << set.size() << " elements." << std::endl;
    1303             :         }
    1304             :     }
    1305             : 
    1306             :   // Store the number of internal and border elements, and the number of internal nodes,
    1307             :   // to be written to the Nemesis file.
    1308        8021 :   this->num_internal_elems =
    1309         226 :     cast_int<int>(this->internal_elem_ids.size());
    1310        8021 :   this->num_border_elems   =
    1311         226 :     cast_int<int>(this->border_elem_ids.size());
    1312        8021 :   this->num_internal_nodes =
    1313         226 :     cast_int<int>(this->internal_node_ids.size());
    1314             : 
    1315        8021 :   if (verbose)
    1316             :     {
    1317           0 :       libMesh::out << "[" << this->processor_id() << "] num_internal_nodes=" << this->num_internal_nodes << std::endl;
    1318           0 :       libMesh::out << "[" << this->processor_id() << "] num_border_nodes=" << this->num_border_nodes << std::endl;
    1319           0 :       libMesh::out << "[" << this->processor_id() << "] num_border_elems=" << this->num_border_elems << std::endl;
    1320           0 :       libMesh::out << "[" << this->processor_id() << "] num_internal_elems=" << this->num_internal_elems << std::endl;
    1321             :     }
    1322        8021 : }
    1323             : 
    1324             : 
    1325             : 
    1326        8021 : void Nemesis_IO_Helper::compute_num_global_sidesets(const MeshBase & pmesh)
    1327             : {
    1328             :   // 1.) Get reference to the set of side boundary IDs
    1329             :   std::set<boundary_id_type> global_side_boundary_ids
    1330         226 :     (pmesh.get_boundary_info().get_side_boundary_ids().begin(),
    1331        8473 :      pmesh.get_boundary_info().get_side_boundary_ids().end());
    1332             : 
    1333             :   // 2.) Gather boundary side IDs from other processors
    1334        8021 :   this->comm().set_union(global_side_boundary_ids);
    1335             : 
    1336             :   // 3.) Now global_side_boundary_ids actually contains a global list of all side boundary IDs
    1337        8021 :   this->num_side_sets_global =
    1338         226 :     cast_int<int>(global_side_boundary_ids.size());
    1339             : 
    1340             :   // 4.) Pack these sidesets into a vector so they can be written by Nemesis
    1341        8021 :   this->global_sideset_ids.clear(); // Make sure there is no leftover information
    1342        7795 :   this->global_sideset_ids.insert(this->global_sideset_ids.end(),
    1343             :                                   global_side_boundary_ids.begin(),
    1344         678 :                                   global_side_boundary_ids.end());
    1345             : 
    1346        8021 :   if (verbose)
    1347             :     {
    1348           0 :       libMesh::out << "[" << this->processor_id() << "] global_sideset_ids = ";
    1349           0 :       for (const auto & id : this->global_sideset_ids)
    1350           0 :         libMesh::out << id << ", ";
    1351           0 :       libMesh::out << std::endl;
    1352             :     }
    1353             : 
    1354             :   // We also need global counts of sides in each of the sidesets.
    1355             :   // Build a list of (elem, side, bc) tuples.
    1356             :   typedef std::tuple<dof_id_type, unsigned short int, boundary_id_type> Tuple;
    1357        8247 :   std::vector<Tuple> bc_triples = pmesh.get_boundary_info().build_side_list();
    1358             : 
    1359             :   // Iterators to the beginning and end of the current range.
    1360             :   std::vector<Tuple>::iterator
    1361         226 :     it = bc_triples.begin(),
    1362         226 :     new_end = bc_triples.end();
    1363             : 
    1364       34904 :   while (it != new_end)
    1365             :     {
    1366       26883 :       if (pmesh.elem_ref(std::get<0>(*it)).processor_id() != this->processor_id())
    1367             :         {
    1368             :           // Back up the new end iterators to prepare for swap
    1369         652 :           --new_end;
    1370             : 
    1371             :           // Swap places, the non-local elem will now be "past-the-end"
    1372         652 :           std::swap (*it, *new_end);
    1373             :         }
    1374             :       else // elem is local, go to next
    1375         656 :         ++it;
    1376             :     }
    1377             : 
    1378             :   // Erase from "new" end to old.
    1379         226 :   bc_triples.erase(new_end, bc_triples.end());
    1380             : 
    1381        8021 :   this->num_global_side_counts.clear(); // Make sure we don't have any leftover information
    1382        8247 :   this->num_global_side_counts.resize(this->global_sideset_ids.size());
    1383             : 
    1384             :   // Get the count for each global sideset ID
    1385       23988 :   for (auto i : index_range(global_sideset_ids))
    1386             :     {
    1387       15967 :       int id = global_sideset_ids[i];
    1388       16417 :       this->num_global_side_counts[i] =
    1389         450 :         cast_int<int>(std::count_if(bc_triples.begin(),
    1390             :                                     bc_triples.end(),
    1391       17088 :                                     [id](const Tuple & t)->bool { return std::get<2>(t) == id; }));
    1392             :     }
    1393             : 
    1394        8021 :   if (verbose)
    1395             :     {
    1396           0 :       libMesh::out << "[" << this->processor_id() << "] num_global_side_counts = ";
    1397           0 :       for (const auto & cnt : this->num_global_side_counts)
    1398           0 :         libMesh::out << cnt << ", ";
    1399           0 :       libMesh::out << std::endl;
    1400             :     }
    1401             : 
    1402             :   // Finally sum up the result
    1403        8021 :   this->comm().sum(this->num_global_side_counts);
    1404             : 
    1405        8021 :   if (verbose)
    1406             :     {
    1407           0 :       libMesh::out << "[" << this->processor_id() << "] num_global_side_counts = ";
    1408           0 :       for (const auto & cnt : this->num_global_side_counts)
    1409           0 :         libMesh::out << cnt << ", ";
    1410           0 :       libMesh::out << std::endl;
    1411             :     }
    1412        8021 : }
    1413             : 
    1414             : 
    1415             : 
    1416             : 
    1417             : 
    1418             : 
    1419        8021 : void Nemesis_IO_Helper::compute_num_global_nodesets(const MeshBase & pmesh)
    1420             : {
    1421         452 :   std::set<boundary_id_type> local_node_boundary_ids;
    1422             : 
    1423             :   // 1.) Get reference to the set of node boundary IDs *for this processor*
    1424             :   std::set<boundary_id_type> global_node_boundary_ids
    1425         226 :     (pmesh.get_boundary_info().get_node_boundary_ids().begin(),
    1426        8473 :      pmesh.get_boundary_info().get_node_boundary_ids().end());
    1427             : 
    1428             :   // Save a copy of the local_node_boundary_ids...
    1429         226 :   local_node_boundary_ids = global_node_boundary_ids;
    1430             : 
    1431             :   // 2.) Gather boundary node IDs from other processors
    1432        8021 :   this->comm().set_union(global_node_boundary_ids);
    1433             : 
    1434             :   // 3.) Now global_node_boundary_ids actually contains a global list of all node boundary IDs
    1435        8021 :   this->num_node_sets_global =
    1436         226 :     cast_int<int>(global_node_boundary_ids.size());
    1437             : 
    1438             :   // 4.) Create a vector<int> from the global_node_boundary_ids set
    1439        8021 :   this->global_nodeset_ids.clear();
    1440        7795 :   this->global_nodeset_ids.insert(this->global_nodeset_ids.end(),
    1441             :                                   global_node_boundary_ids.begin(),
    1442         678 :                                   global_node_boundary_ids.end());
    1443             : 
    1444        8021 :   if (verbose)
    1445             :     {
    1446           0 :       libMesh::out << "[" << this->processor_id() << "] global_nodeset_ids = ";
    1447           0 :       for (const auto & id : global_nodeset_ids)
    1448           0 :         libMesh::out << id << ", ";
    1449           0 :       libMesh::out << std::endl;
    1450             : 
    1451           0 :       libMesh::out << "[" << this->processor_id() << "] local_node_boundary_ids = ";
    1452           0 :       for (const auto & id : local_node_boundary_ids)
    1453           0 :         libMesh::out << id << ", ";
    1454           0 :       libMesh::out << std::endl;
    1455             :     }
    1456             : 
    1457             :   // 7.) We also need to know the number of nodes which is in each of the nodesets, globally.
    1458             : 
    1459             :   // Build list of (node-id, bc-id) tuples.
    1460             :   typedef std::tuple<dof_id_type, boundary_id_type> Tuple;
    1461        8247 :   std::vector<Tuple> bc_tuples = pmesh.get_boundary_info().build_node_list();
    1462             : 
    1463        8021 :   if (verbose)
    1464             :     {
    1465           0 :       libMesh::out << "[" << this->processor_id() << "] boundary_node_list.size()="
    1466           0 :                    << bc_tuples.size() << std::endl;
    1467           0 :       libMesh::out << "[" << this->processor_id() << "] (boundary_node_id, boundary_id) = ";
    1468           0 :       for (const auto & t : bc_tuples)
    1469           0 :         libMesh::out << "(" << std::get<0>(t) << ", " << std::get<1>(t) << ") ";
    1470           0 :       libMesh::out << std::endl;
    1471             :     }
    1472             : 
    1473             :   // Now get the global information.  In this case, we only want to count boundary
    1474             :   // information for nodes *owned* by this processor, so there are no duplicates.
    1475             : 
    1476             :   // Make sure we don't have any left over information
    1477        8021 :   this->num_global_node_counts.clear();
    1478        8247 :   this->num_global_node_counts.resize(this->global_nodeset_ids.size());
    1479             : 
    1480             :   // Unfortunately, we can't just count up all occurrences of a given id,
    1481             :   // that would give us duplicate entries when we do the parallel summation.
    1482             :   // So instead, only count entries for nodes owned by this processor.
    1483             :   // Start by getting rid of all non-local node entries from the vectors.
    1484             :   std::vector<Tuple>::iterator
    1485         226 :     it = bc_tuples.begin(),
    1486         226 :     new_end = bc_tuples.end();
    1487             : 
    1488       20245 :   while (it != new_end)
    1489             :     {
    1490       12224 :       if (pmesh.node_ptr(std::get<0>(*it))->processor_id() != this->processor_id())
    1491             :         {
    1492             :           // Back up the new end iterators to prepare for swap
    1493         280 :           --new_end;
    1494             : 
    1495             :           // Swap places, the non-local node will now be "past-the-end"
    1496         280 :           std::swap(*it, *new_end);
    1497             :         }
    1498             :       else // node is local, go to next
    1499         288 :         ++it;
    1500             :     }
    1501             : 
    1502             :   // Erase from "new" end to old end.
    1503         226 :   bc_tuples.erase(new_end, bc_tuples.end());
    1504             : 
    1505             :   // Now we can do the local count for each ID...
    1506       19941 :   for (auto i : index_range(global_nodeset_ids))
    1507             :     {
    1508       11920 :       int id = this->global_nodeset_ids[i];
    1509       12256 :       this->num_global_node_counts[i] =
    1510         336 :         cast_int<int>(std::count_if(bc_tuples.begin(),
    1511             :                                     bc_tuples.end(),
    1512       10816 :                                     [id](const Tuple & t)->bool { return std::get<1>(t) == id; }));
    1513             :     }
    1514             : 
    1515             :   // And finally we can sum them up
    1516        8021 :   this->comm().sum(this->num_global_node_counts);
    1517             : 
    1518        8021 :   if (verbose)
    1519             :     {
    1520           0 :       libMesh::out << "[" << this->processor_id() << "] num_global_node_counts = ";
    1521           0 :       for (const auto & cnt : num_global_node_counts)
    1522           0 :         libMesh::out << cnt << ", ";
    1523           0 :       libMesh::out << std::endl;
    1524             :     }
    1525        8021 : }
    1526             : 
    1527             : 
    1528             : 
    1529             : 
    1530        8021 : void Nemesis_IO_Helper::compute_num_global_elem_blocks(const MeshBase & pmesh)
    1531             : {
    1532             :   // 1.) Loop over active local elements, build up set of subdomain IDs.
    1533         452 :   std::set<subdomain_id_type> global_subdomain_ids;
    1534             : 
    1535             :   // This map keeps track of the number of elements in each subdomain over all processors
    1536         452 :   std::map<subdomain_id_type, unsigned> global_subdomain_counts;
    1537             : 
    1538       36932 :   for (const auto & elem : pmesh.active_local_element_ptr_range())
    1539             :     {
    1540       21116 :       subdomain_id_type cur_subdomain = elem->subdomain_id();
    1541             : 
    1542             :       /*
    1543             :       // We can't have a zero subdomain ID in Exodus (for some reason?)
    1544             :       // so map zero subdomains to a max value...
    1545             :       if (cur_subdomain == 0)
    1546             :       cur_subdomain = std::numeric_limits<subdomain_id_type>::max();
    1547             :       */
    1548             : 
    1549       19354 :       global_subdomain_ids.insert(cur_subdomain);
    1550             : 
    1551             :       // Increment the count of elements in this subdomain
    1552       21116 :       global_subdomain_counts[cur_subdomain]++;
    1553        7569 :     }
    1554             : 
    1555             :   // We're next going to this->comm().sum the subdomain counts, so save the local counts
    1556         226 :   this->local_subdomain_counts = global_subdomain_counts;
    1557             : 
    1558             :   {
    1559             :     // 2.) Copy local subdomain IDs into a vector for communication
    1560             :     std::vector<subdomain_id_type> global_subdomain_ids_vector(global_subdomain_ids.begin(),
    1561        8247 :                                                                global_subdomain_ids.end());
    1562             : 
    1563             :     // 3.) Gather them into an enlarged vector
    1564        8021 :     this->comm().allgather(global_subdomain_ids_vector);
    1565             : 
    1566             :     // 4.) Insert any new IDs into the set (any duplicates will be dropped)
    1567        7795 :     global_subdomain_ids.insert(global_subdomain_ids_vector.begin(),
    1568             :                                 global_subdomain_ids_vector.end());
    1569             :   }
    1570             : 
    1571             :   // 5.) Now global_subdomain_ids actually contains a global list of all subdomain IDs
    1572        8021 :   this->num_elem_blks_global =
    1573         226 :     cast_int<int>(global_subdomain_ids.size());
    1574             : 
    1575             :   // Print the number of elements found locally in each subdomain
    1576        8021 :   if (verbose)
    1577             :     {
    1578           0 :       libMesh::out << "[" << this->processor_id() << "] ";
    1579           0 :       for (const auto & [subdomain_id, cnt] : global_subdomain_counts)
    1580             :         {
    1581           0 :           libMesh::out << "ID: "
    1582           0 :                        << static_cast<unsigned>(subdomain_id)
    1583           0 :                        << ", Count: " << cnt << ", ";
    1584             :         }
    1585           0 :       libMesh::out << std::endl;
    1586             :     }
    1587             : 
    1588             :   // 6.) this->comm().sum up the number of elements in each block.  We know the global
    1589             :   // subdomain IDs, so pack them into a vector one by one.  Use a vector of int since
    1590             :   // that is what Nemesis wants
    1591        8247 :   this->global_elem_blk_cnts.resize(global_subdomain_ids.size());
    1592             : 
    1593         226 :   unsigned cnt=0;
    1594             :   // Find the entry in the local map, note: if not found, will be created with 0 default value, which is OK...
    1595       16042 :   for (const auto & id : global_subdomain_ids)
    1596        8021 :     this->global_elem_blk_cnts[cnt++] = global_subdomain_counts[id];
    1597             : 
    1598             :   // Sum up subdomain counts from all processors
    1599        8021 :   this->comm().sum(this->global_elem_blk_cnts);
    1600             : 
    1601        8021 :   if (verbose)
    1602             :     {
    1603           0 :       libMesh::out << "[" << this->processor_id() << "] global_elem_blk_cnts = ";
    1604           0 :       for (const auto & bc : this->global_elem_blk_cnts)
    1605           0 :         libMesh::out << bc << ", ";
    1606           0 :       libMesh::out << std::endl;
    1607             :     }
    1608             : 
    1609             :   // 7.) Create a vector<int> from the global_subdomain_ids set, for passing to Nemesis
    1610        8021 :   this->global_elem_blk_ids.clear();
    1611        7795 :   this->global_elem_blk_ids.insert(this->global_elem_blk_ids.end(), // pos
    1612             :                                    global_subdomain_ids.begin(),
    1613         678 :                                    global_subdomain_ids.end());
    1614             : 
    1615        8021 :   if (verbose)
    1616             :     {
    1617           0 :       libMesh::out << "[" << this->processor_id() << "] global_elem_blk_ids = ";
    1618           0 :       for (const auto & id : this->global_elem_blk_ids)
    1619           0 :         libMesh::out << id << ", ";
    1620           0 :       libMesh::out << std::endl;
    1621             :     }
    1622             : 
    1623             : 
    1624             :   // 8.) We will call put_eb_info_global later, it must be called after this->put_init_global().
    1625        8021 : }
    1626             : 
    1627             : 
    1628             : 
    1629             : 
    1630        8021 : void Nemesis_IO_Helper::build_element_and_node_maps(const MeshBase & pmesh)
    1631             : {
    1632             :   // If we don't have any local subdomains, it had better be because
    1633             :   // we don't have any local elements
    1634             : #ifdef DEBUG
    1635         226 :   if (local_subdomain_counts.empty())
    1636             :     {
    1637          22 :       libmesh_assert(pmesh.active_local_elements_begin() ==
    1638             :                      pmesh.active_local_elements_end());
    1639          22 :       libmesh_assert(this->nodes_attached_to_local_elems.empty());
    1640             :     }
    1641             : #endif
    1642             : 
    1643             :   // Elements have to be numbered contiguously based on what block
    1644             :   // number they are in.  Therefore we have to do a bit of work to get
    1645             :   // the block (ie subdomain) numbers first and store them off as
    1646             :   // block_ids.
    1647             : 
    1648             :   // Make sure there is no leftover information in the subdomain_map, and reserve
    1649             :   // enough space to store the elements we need.
    1650         452 :   this->subdomain_map.clear();
    1651       12555 :   for (const auto & [sbd_id, cnt] : local_subdomain_counts)
    1652             :     {
    1653        4534 :       if (verbose)
    1654             :         {
    1655           0 :           libMesh::out << "[" << this->processor_id() << "] "
    1656           0 :                        << "local_subdomain_counts [" << static_cast<unsigned>(sbd_id) << "]= "
    1657           0 :                        << cnt
    1658           0 :                        << std::endl;
    1659             :         }
    1660             : 
    1661        4534 :       this->subdomain_map[sbd_id].reserve(cnt);
    1662             :     }
    1663             : 
    1664             : 
    1665             :   // First loop over the elements to figure out which elements are in which subdomain
    1666       36932 :   for (const auto & elem : pmesh.active_local_element_ptr_range())
    1667             :     {
    1668             :       // Grab the nodes while we're here.
    1669       93466 :       for (auto n : elem->node_index_range())
    1670       76478 :         this->nodes_attached_to_local_elems.insert( elem->node_id(n) );
    1671             : 
    1672       21116 :       subdomain_id_type cur_subdomain = elem->subdomain_id();
    1673             : 
    1674       21116 :       this->subdomain_map[cur_subdomain].push_back(elem->id());
    1675        7569 :     }
    1676             : 
    1677             :   // Set num_nodes which is used by exodusII_io_helper
    1678        8247 :   this->num_nodes =
    1679         226 :     cast_int<int>(this->nodes_attached_to_local_elems.size());
    1680             : 
    1681             :   // Now come up with a 1-based numbering for these nodes
    1682        8021 :   this->exodus_node_num_to_libmesh.clear(); // Make sure it's empty
    1683        8021 :   this->exodus_node_num_to_libmesh.reserve(this->nodes_attached_to_local_elems.size());
    1684             : 
    1685             :   // Also make sure there's no leftover information in the map which goes the
    1686             :   // other direction.
    1687         452 :   this->libmesh_node_num_to_exodus.clear();
    1688             : 
    1689             :   // Set the map for nodes
    1690       46556 :   for (const auto & id : nodes_attached_to_local_elems)
    1691             :     {
    1692             :       // I.e. given exodus_node_id,
    1693             :       // exodus_node_num_to_libmesh[ exodus_node_id ] returns the
    1694             :       // libmesh ID for that node, plus one.
    1695             :       // Here we index libMesh IDs with an offset of 1 because they're
    1696             :       // the literal numbers that get written to the exodus file, but
    1697             :       // we index Exodus IDs with an offset of 0 because we read that
    1698             :       // exodus data into a C++ vector.  Confused yet?
    1699       38535 :       this->exodus_node_num_to_libmesh.push_back(id+1);
    1700             : 
    1701             :       // Likewise, given libmesh_node_id,
    1702             :       // libmesh_node_num_to_exodus[ libmesh_node_id] returns the
    1703             :       // *Exodus* ID for that node.  Unlike the
    1704             :       // exodus_node_num_to_libmesh vector above, this one is a
    1705             :       // std::map.  We're never handing a data buffer from it over to
    1706             :       // another API so we don't need to do any weird offsets with it.
    1707       38535 :       this->libmesh_node_num_to_exodus[id] =
    1708        5340 :         this->exodus_node_num_to_libmesh.size(); // should never be zero...
    1709             :     }
    1710             : 
    1711             :   // Now we're going to loop over the subdomain map and build a few things right
    1712             :   // now that we'll use later.
    1713             : 
    1714             :   // First make sure our data structures don't have any leftover data...
    1715        8021 :   this->exodus_elem_num_to_libmesh.clear();
    1716        8021 :   this->block_ids.clear();
    1717         452 :   this->libmesh_elem_num_to_exodus.clear();
    1718             : 
    1719             :   // Now loop over each subdomain and get a unique numbering for the elements
    1720       12555 :   for (auto & [block_id, elem_ids_this_subdomain] : subdomain_map)
    1721             :     {
    1722        4534 :       block_ids.push_back(block_id);
    1723             : 
    1724             :       // The code below assumes this subdomain block is not empty, make sure that's the case!
    1725        4534 :       libmesh_error_msg_if(elem_ids_this_subdomain.size() == 0,
    1726             :                            "Error, no element IDs found in subdomain " << block_id);
    1727             : 
    1728             :       // Use the first element in this block to get representative information.
    1729             :       // Note that Exodus assumes all elements in a block are of the same type!
    1730             :       // We are using that same assumption here!
    1731             :       const auto & conv = get_conversion
    1732        4534 :         (pmesh.elem_ref(elem_ids_this_subdomain[0]).type());
    1733        4534 :       this->num_nodes_per_elem =
    1734        4534 :         pmesh.elem_ref(elem_ids_this_subdomain[0]).n_nodes();
    1735             : 
    1736             :       // Get a reference to the connectivity vector for this subdomain.  This vector
    1737             :       // is most likely empty, we are going to fill it up now.
    1738        4534 :       std::vector<int> & current_block_connectivity = this->block_id_to_elem_connectivity[block_id];
    1739             : 
    1740             :       // Just in case it's not already empty...
    1741         204 :       current_block_connectivity.clear();
    1742        4738 :       current_block_connectivity.resize(elem_ids_this_subdomain.size() * this->num_nodes_per_elem);
    1743             : 
    1744       25650 :       for (auto i : index_range(elem_ids_this_subdomain))
    1745             :         {
    1746       21116 :           auto elem_id = elem_ids_this_subdomain[i];
    1747             : 
    1748             :           // Set the number map for elements
    1749             :           // exodus_elem_num_to_libmesh[ exodus_node_id ] returns the
    1750             :           // libmesh ID for that element, plus one.
    1751             :           // Like with nodes above, we index libMesh IDs with an
    1752             :           // offset of 1 because they're the literal numbers that get
    1753             :           // written to the exodus file, but we index Exodus IDs with
    1754             :           // an offset of 0 because we read that exodus data into a
    1755             :           // C++ vector.
    1756       21116 :           this->exodus_elem_num_to_libmesh.push_back(elem_id+1);
    1757             : 
    1758             :           // Likewise, given libmesh elem_id,
    1759             :           // libmesh_elem_num_to_exodus[ elem_id ] returns the
    1760             :           // *Exodus* ID for that node.  Unlike the
    1761             :           // exodus_elem_num_to_libmesh vector above, this one is a
    1762             :           // std::map.  We're never handing a data buffer from it over to
    1763             :           // another API so we don't need to do any weird offsets with it.
    1764       21116 :           this->libmesh_elem_num_to_exodus[elem_id] =
    1765        3524 :             this->exodus_elem_num_to_libmesh.size();
    1766             : 
    1767       21116 :           const Elem & elem = pmesh.elem_ref(elem_id);
    1768             : 
    1769             :           // Exodus/Nemesis want every block to have the same element type
    1770             :           // libmesh_assert_equal_to (elem->type(), conv.libmesh_elem_type());
    1771             : 
    1772             :           // But we can get away with writing e.g. HEX8 and INFHEX8 in
    1773             :           // the same block...
    1774        1762 :           libmesh_assert_equal_to (elem.n_nodes(), Elem::build(conv.libmesh_elem_type(), nullptr)->n_nodes());
    1775             : 
    1776       91704 :           for (auto j : make_range(this->num_nodes_per_elem))
    1777             :             {
    1778       70588 :               const unsigned int connect_index   = (i*this->num_nodes_per_elem)+j;
    1779       70588 :               const unsigned int elem_node_index = conv.get_inverse_node_map(j); // inverse node map is used for writing
    1780             : 
    1781       82368 :               current_block_connectivity[connect_index] =
    1782       76478 :                 libmesh_map_find(libmesh_node_num_to_exodus,
    1783             :                                  elem.node_id(elem_node_index));
    1784             :             }
    1785             :         } // End loop over elems in this subdomain
    1786             :     } // end loop over subdomain_map
    1787        8021 : }
    1788             : 
    1789             : 
    1790             : 
    1791             : 
    1792             : 
    1793        8021 : void Nemesis_IO_Helper::compute_border_node_ids(const MeshBase & pmesh)
    1794             : {
    1795             :   // The set which will eventually contain the IDs of "border nodes".  These are nodes
    1796             :   // that lie on the boundary between one or more processors.
    1797             :   //std::set<unsigned> border_node_ids;
    1798             : 
    1799             :   // map from processor ID to set of nodes which elements from this processor "touch",
    1800             :   // that is,
    1801             :   // proc_nodes_touched[p] = (set all node IDs found in elements owned by processor p)
    1802         226 :   std::map<unsigned, std::set<unsigned>> proc_nodes_touched;
    1803             : 
    1804             : 
    1805             :   // We are going to create a lot of intermediate data structures here, so make sure
    1806             :   // as many as possible all cleaned up by creating scope!
    1807             :   {
    1808             :     // Loop over active (not just active local) elements, make sets of node IDs for each
    1809             :     // processor which has an element that "touches" a node.
    1810      106102 :     for (const auto & elem : pmesh.active_element_ptr_range())
    1811             :       {
    1812             :         // Get reference to the set for this processor.  If it does not exist
    1813             :         // it will be created.
    1814       52189 :         std::set<unsigned> & set_p = proc_nodes_touched[ elem->processor_id() ];
    1815             : 
    1816             :         // Insert all nodes touched by this element into the set
    1817      233291 :         for (auto node : elem->node_index_range())
    1818      192878 :           set_p.insert(elem->node_id(node));
    1819        7569 :       }
    1820             : 
    1821        8021 :     if (verbose)
    1822             :       {
    1823           0 :         libMesh::out << "[" << this->processor_id()
    1824           0 :                      << "] proc_nodes_touched contains "
    1825           0 :                      << proc_nodes_touched.size()
    1826           0 :                      << " sets of nodes."
    1827           0 :                      << std::endl;
    1828             : 
    1829           0 :         for (const auto & [proc_id, set] : proc_nodes_touched)
    1830           0 :           libMesh::out << "[" << this->processor_id()
    1831           0 :                        << "] proc_nodes_touched[" << proc_id << "] has "
    1832           0 :                        << set.size()
    1833           0 :                        << " entries."
    1834           0 :                        << std::endl;
    1835             :       }
    1836             : 
    1837             : 
    1838             :     // Loop over all the sets we just created and compute intersections with the
    1839             :     // this processor's set.  Obviously, don't intersect with ourself.
    1840         226 :     this->proc_nodes_touched_intersections.clear();
    1841       25081 :     for (auto & [proc_id, other_set] : proc_nodes_touched)
    1842             :       {
    1843             :         // Don't compute intersections with ourself
    1844       17488 :         if (proc_id == this->processor_id())
    1845        4814 :           continue;
    1846             : 
    1847         406 :         std::set<unsigned int> this_intersection;
    1848             : 
    1849             :         // Otherwise, compute intersection with other processor and ourself
    1850       12246 :         std::set<unsigned> & my_set = proc_nodes_touched[this->processor_id()];
    1851             : 
    1852       11840 :         std::set_intersection(my_set.begin(), my_set.end(),
    1853             :                               other_set.begin(), other_set.end(),
    1854         406 :                               std::inserter(this_intersection, this_intersection.end()));
    1855             : 
    1856       12246 :         if (!this_intersection.empty())
    1857             :           this->proc_nodes_touched_intersections.emplace
    1858       10626 :             (proc_id, std::move(this_intersection));
    1859             :       }
    1860             : 
    1861        8021 :     if (verbose)
    1862             :       {
    1863           0 :         for (const auto & [proc_id, set] : proc_nodes_touched_intersections)
    1864           0 :           libMesh::out << "[" << this->processor_id()
    1865           0 :                        << "] this->proc_nodes_touched_intersections[" << proc_id << "] has "
    1866           0 :                        << set.size()
    1867           0 :                        << " entries."
    1868           0 :                        << std::endl;
    1869             :       }
    1870             : 
    1871             :     // The number of node communication maps is the number of other processors
    1872             :     // with which we share nodes.
    1873        8021 :     this->num_node_cmaps =
    1874         226 :       cast_int<int>(proc_nodes_touched_intersections.size());
    1875             : 
    1876             :     // We can't be connecting to more processors than exist outside
    1877             :     // ourselves
    1878         226 :     libmesh_assert_less (this->num_node_cmaps, this->n_processors());
    1879             : 
    1880             :     // Compute the set_union of all the preceding intersections.  This will be the set of
    1881             :     // border node IDs for this processor.
    1882       18829 :     for (auto & pr : proc_nodes_touched_intersections)
    1883             :       {
    1884         182 :         std::set<unsigned> & other_set = pr.second;
    1885         364 :         std::set<unsigned> intermediate_result; // Don't think we can insert into one of the sets we're unioning...
    1886             : 
    1887       10444 :         std::set_union(this->border_node_ids.begin(), this->border_node_ids.end(),
    1888             :                        other_set.begin(), other_set.end(),
    1889         364 :                        std::inserter(intermediate_result, intermediate_result.end()));
    1890             : 
    1891             :         // Swap our intermediate result into the final set
    1892         182 :         this->border_node_ids.swap(intermediate_result);
    1893             :       }
    1894             : 
    1895         226 :     libmesh_assert_less_equal
    1896             :       (this->proc_nodes_touched_intersections.size(),
    1897             :        std::size_t(this->num_node_cmaps));
    1898             : 
    1899        8021 :     if (verbose)
    1900             :       {
    1901           0 :         libMesh::out << "[" << this->processor_id()
    1902           0 :                      << "] border_node_ids.size()=" << this->border_node_ids.size()
    1903           0 :                      << std::endl;
    1904             :       }
    1905             :   } // end scope for border node ID creation
    1906             : 
    1907             :   // Store the number of border node IDs to be written to Nemesis file
    1908        8021 :   this->num_border_nodes = cast_int<int>(this->border_node_ids.size());
    1909        8021 : }
    1910             : 
    1911             : 
    1912             : 
    1913             : 
    1914             : 
    1915        8021 : void Nemesis_IO_Helper::write_nodesets(const MeshBase & mesh)
    1916             : {
    1917             :   // Write the nodesets.  In Nemesis, the idea is to "create space" for the global
    1918             :   // set of boundary nodesets, but to only write node IDs which are local to the current
    1919             :   // processor.  This is what is done in Nemesis files created by the "loadbal" script.
    1920             : 
    1921             :   // Store a map of vectors for boundary node IDs on this processor.
    1922             :   // Use a vector of int here so it can be passed directly to Exodus.
    1923         452 :   std::map<boundary_id_type, std::vector<int>> local_node_boundary_id_lists;
    1924             : 
    1925             :   // FIXME: We should build this list only one time!!  We already built it above, but we
    1926             :   // did not have the libmesh to exodus node mapping at that time... for now we'll just
    1927             :   // build it here again, hopefully it's small relative to the size of the entire mesh.
    1928             : 
    1929             :   // Build list of (node-id, bc-id) tuples.
    1930             :   typedef std::tuple<dof_id_type, boundary_id_type> Tuple;
    1931        8247 :   std::vector<Tuple> bc_tuples = mesh.get_boundary_info().build_node_list();
    1932             : 
    1933        8021 :   if (verbose)
    1934             :     {
    1935           0 :       libMesh::out << "[" << this->processor_id() << "] boundary_node_list.size()="
    1936           0 :                    << bc_tuples.size() << std::endl;
    1937           0 :       libMesh::out << "[" << this->processor_id() << "] (boundary_node_id, boundary_id) = ";
    1938           0 :       for (const auto & t : bc_tuples)
    1939           0 :         libMesh::out << "(" << std::get<0>(t) << ", " << std::get<1>(t) << ") ";
    1940           0 :       libMesh::out << std::endl;
    1941             :     }
    1942             : 
    1943             :   // For each node in the node list, add it to the vector of node IDs for that
    1944             :   // set for the local processor.  This will be used later when writing Exodus
    1945             :   // nodesets.
    1946       20245 :   for (const auto & t : bc_tuples)
    1947             :     {
    1948             :       // Don't try to grab a reference to the vector unless the current node is attached
    1949             :       // to a local element.  Otherwise, another processor will be responsible for writing it in its nodeset.
    1950       12224 :       if (const auto it = this->libmesh_node_num_to_exodus.find(std::get<0>(t));
    1951         568 :           it != this->libmesh_node_num_to_exodus.end())
    1952             :         {
    1953             :           // Get reference to the vector where this node ID will be inserted.  If it
    1954             :           // doesn't yet exist, this will create it.
    1955        3792 :           std::vector<int> & current_id_set = local_node_boundary_id_lists[std::get<1>(t)];
    1956             : 
    1957             :           // Push back Exodus-mapped node ID for this set
    1958             :           // TODO: reserve space in these vectors somehow.
    1959        3792 :           current_id_set.push_back( it->second );
    1960             :         }
    1961             :     }
    1962             : 
    1963             :   // See what we got
    1964        8021 :   if (verbose)
    1965             :     {
    1966           0 :       for (const auto & [bndry_id, set] : local_node_boundary_id_lists)
    1967             :         {
    1968           0 :           libMesh::out << "[" << this->processor_id() << "] ID: " << bndry_id << ", ";
    1969             : 
    1970             :           // Libmesh node ID (Exodus Node ID)
    1971           0 :           for (const auto & id : set)
    1972           0 :             libMesh::out << id << ", ";
    1973           0 :           libMesh::out << std::endl;
    1974             :         }
    1975             :     }
    1976             : 
    1977             :   // Loop over *global* nodeset IDs, call the Exodus API.  Note that some nodesets may be empty
    1978             :   // for a given processor.
    1979        8247 :   if (global_nodeset_ids.size() > 0)
    1980             :     {
    1981        5398 :       NamesData names_table(global_nodeset_ids.size(), MAX_STR_LENGTH);
    1982             : 
    1983       17030 :       for (const auto & nodeset_id : this->global_nodeset_ids)
    1984             :         {
    1985             :           const std::string & current_ns_name =
    1986         336 :             mesh.get_boundary_info().get_nodeset_name
    1987       12256 :               (cast_int<boundary_id_type>(nodeset_id));
    1988             : 
    1989             :           // Store this name in a data structure that will be used to
    1990             :           // write sideset names to file.
    1991       11920 :           names_table.push_back_entry(current_ns_name);
    1992             : 
    1993       11920 :           if (verbose)
    1994             :             {
    1995           0 :               libMesh::out << "[" << this->processor_id()
    1996           0 :                            << "] Writing out Exodus nodeset info for ID: " << nodeset_id
    1997           0 :                            << ", Name: " << current_ns_name
    1998           0 :                            << std::endl;
    1999             :             }
    2000             : 
    2001             :           // Convert current global_nodeset_id into an exodus ID, which can't be zero...
    2002       11920 :           int exodus_id = nodeset_id;
    2003             : 
    2004             :           /*
    2005             :           // Exodus can't handle zero nodeset IDs (?)  Use max short here since
    2006             :           // when libmesh reads it back in, it will want to store it as a short...
    2007             :           if (exodus_id==0)
    2008             :           exodus_id = std::numeric_limits<short>::max();
    2009             :           */
    2010             : 
    2011             :           // Try to find this boundary ID in the local list we created
    2012       11920 :           if (const auto it = local_node_boundary_id_lists.find (cast_int<boundary_id_type>(nodeset_id));
    2013         336 :               it == local_node_boundary_id_lists.end())
    2014             :             {
    2015             :               // No nodes found for this boundary ID on this processor
    2016        9544 :               if (verbose)
    2017           0 :                 libMesh::out << "[" << this->processor_id()
    2018           0 :                              << "] No nodeset data for ID: " << nodeset_id
    2019           0 :                              << " on this processor." << std::endl;
    2020             : 
    2021             :               // Call the Exodus interface to write the parameters of this node set
    2022        9544 :               this->ex_err = exII::ex_put_node_set_param(this->ex_id,
    2023             :                                                          exodus_id,
    2024             :                                                          0, /* No nodes for this ID */
    2025             :                                                          0  /* No distribution factors */);
    2026        9544 :               EX_CHECK_ERR(this->ex_err, "Error writing nodeset parameters in Nemesis");
    2027             : 
    2028             :             }
    2029             :           else // Boundary ID *was* found in list
    2030             :             {
    2031             :               // Get reference to the vector of node IDs
    2032         192 :               const std::vector<int> & current_nodeset_ids = it->second;
    2033             : 
    2034             :               // Call the Exodus interface to write the parameters of this node set
    2035        2376 :               this->ex_err = exII::ex_put_node_set_param(this->ex_id,
    2036             :                                                          exodus_id,
    2037         384 :                                                          current_nodeset_ids.size(),
    2038             :                                                          0  /* No distribution factors */);
    2039             : 
    2040        2376 :               EX_CHECK_ERR(this->ex_err, "Error writing nodeset parameters in Nemesis");
    2041             : 
    2042             :               // Call Exodus interface to write the actual node IDs for this boundary ID
    2043        2568 :               this->ex_err = exII::ex_put_node_set(this->ex_id,
    2044             :                                                    exodus_id,
    2045         384 :                                                    current_nodeset_ids.data());
    2046             : 
    2047        2376 :               EX_CHECK_ERR(this->ex_err, "Error writing nodesets in Nemesis");
    2048             : 
    2049             :             }
    2050             :         } // end loop over global nodeset IDs
    2051             : 
    2052             :       // Write out the nodeset names
    2053        5110 :       ex_err = exII::ex_put_names(ex_id,
    2054             :                                   exII::EX_NODE_SET,
    2055             :                                   names_table.get_char_star_star());
    2056        5110 :       EX_CHECK_ERR(ex_err, "Error writing nodeset names");
    2057             :     } // end for loop over global nodeset IDs
    2058        8021 : }
    2059             : 
    2060             : 
    2061             : 
    2062             : 
    2063        8021 : void Nemesis_IO_Helper::write_sidesets(const MeshBase & mesh)
    2064             : {
    2065             :   // Write the sidesets.  In Nemesis, the idea is to "create space" for the global
    2066             :   // set of boundary sidesets, but to only write sideset IDs which are local to the current
    2067             :   // processor.  This is what is done in Nemesis files created by the "loadbal" script.
    2068             :   // See also: ExodusII_IO_Helper::write_sidesets()...
    2069             : 
    2070             : 
    2071             :   // Store a map of vectors for boundary side IDs on this processor.
    2072             :   // Use a vector of int here so it can be passed directly to Exodus.
    2073         452 :   std::map<boundary_id_type, std::vector<int>> local_elem_boundary_id_lists;
    2074         452 :   std::map<boundary_id_type, std::vector<int>> local_elem_boundary_id_side_lists;
    2075             : 
    2076             :   // FIXME: We already built this list once, we should reuse that information!
    2077             :   std::vector<std::tuple<dof_id_type, unsigned short int, boundary_id_type>> bndry_elem_side_id_list =
    2078        8247 :     mesh.get_boundary_info().build_side_list();
    2079             : 
    2080             :   // Integer looping, skipping non-local elements
    2081       34904 :   for (const auto & t : bndry_elem_side_id_list)
    2082             :     {
    2083             :       // Get pointer to current Elem
    2084       26883 :       const Elem * elem = mesh.elem_ptr(std::get<0>(t));
    2085             : 
    2086        2616 :       std::vector<const Elem *> family;
    2087             : #ifdef LIBMESH_ENABLE_AMR
    2088             :       // We need to build up active elements if AMR is enabled and add
    2089             :       // them to the exodus sidesets instead of the potentially inactive "parent" elements
    2090             :       // Technically we don't need to "reset" the tree since the vector was just created.
    2091       26883 :       elem->active_family_tree_by_side(family, std::get<1>(t), /*reset tree=*/false);
    2092             : #else
    2093             :       // If AMR is not even enabled, just push back the element itself
    2094             :       family.push_back( elem );
    2095             : #endif
    2096             : 
    2097             :       // Loop over all the elements in the family tree, store their converted IDs
    2098             :       // and side IDs to the map's vectors.  TODO: Somehow reserve enough space for these
    2099             :       // push_back's...
    2100       64297 :       for (const auto & tree_elem : family)
    2101             :         {
    2102       37414 :           const dof_id_type f_id = tree_elem->id();
    2103       37414 :           const Elem & f = mesh.elem_ref(f_id);
    2104             : 
    2105             :           // If element is local, process it
    2106       39702 :           if (f.processor_id() == this->processor_id())
    2107             :             {
    2108       13728 :               const auto & conv = get_conversion(f.type());
    2109             : 
    2110             :               // Use the libmesh to exodus data structure map to get the proper sideset IDs
    2111             :               // The data structure contains the "collapsed" contiguous ids.
    2112             :               //
    2113             :               // We know the parent element is local, but let's be absolutely sure that all the children have been
    2114             :               // actually mapped to Exodus IDs before we blindly try to add them...
    2115       13728 :               local_elem_boundary_id_lists[ std::get<2>(t) ].push_back( libmesh_map_find(libmesh_elem_num_to_exodus, f_id) );
    2116       13728 :               local_elem_boundary_id_side_lists[ std::get<2>(t) ].push_back(conv.get_inverse_side_map( std::get<1>(t) ));
    2117             :             }
    2118             :         }
    2119             :     }
    2120             : 
    2121             : 
    2122             :   // Loop over *global* sideset IDs, call the Exodus API.  Note that some sidesets may be empty
    2123             :   // for a given processor.
    2124        8247 :   if (global_sideset_ids.size() > 0)
    2125             :     {
    2126        8473 :       NamesData names_table(global_sideset_ids.size(), MAX_STR_LENGTH);
    2127             : 
    2128       23988 :       for (const auto & exodus_id : this->global_sideset_ids)
    2129             :         {
    2130             :           const std::string & current_ss_name =
    2131         450 :             mesh.get_boundary_info().get_sideset_name
    2132       16417 :               (cast_int<boundary_id_type>(exodus_id));
    2133             : 
    2134             :           // Store this name in a data structure that will be used to
    2135             :           // write sideset names to file.
    2136       15967 :           names_table.push_back_entry(current_ss_name);
    2137             : 
    2138       15967 :           if (verbose)
    2139             :             {
    2140           0 :               libMesh::out << "[" << this->processor_id()
    2141           0 :                            << "] Writing out Exodus sideset info for ID: " << exodus_id
    2142           0 :                            << ", Name: " << current_ss_name
    2143           0 :                            << std::endl;
    2144             :             }
    2145             : 
    2146             :           // Try to find this boundary ID in the local list we created
    2147       16417 :           if (const auto it = local_elem_boundary_id_lists.find (cast_int<boundary_id_type>(exodus_id));
    2148         450 :               it == local_elem_boundary_id_lists.end())
    2149             :             {
    2150             :               // No sides found for this boundary ID on this processor
    2151       10774 :               if (verbose)
    2152           0 :                 libMesh::out << "[" << this->processor_id()
    2153           0 :                              << "] No sideset data for ID: " << exodus_id
    2154           0 :                              << " on this processor." << std::endl;
    2155             : 
    2156             :               // Call the Exodus interface to write the parameters of this side set
    2157       21548 :               this->ex_err = exII::ex_put_side_set_param(this->ex_id,
    2158       10774 :                                                          exodus_id,
    2159             :                                                          0, /* No sides for this ID */
    2160             :                                                          0  /* No distribution factors */);
    2161       10774 :               EX_CHECK_ERR(this->ex_err, "Error writing sideset parameters in Nemesis");
    2162             : 
    2163             :             }
    2164             :           else // Boundary ID *was* found in list
    2165             :             {
    2166             :               // Get reference to the vector of elem IDs
    2167         298 :               const std::vector<int> & current_sideset_elem_ids = it->second;
    2168             : 
    2169             :               // Get reference to the vector of side IDs
    2170             :               std::vector<int> & current_sideset_side_ids =
    2171        5193 :                 libmesh_map_find(local_elem_boundary_id_side_lists,
    2172             :                                  cast_int<boundary_id_type>(exodus_id));
    2173             : 
    2174             :               // Call the Exodus interface to write the parameters of this side set
    2175       10684 :               this->ex_err = exII::ex_put_side_set_param(this->ex_id,
    2176        5193 :                                                          exodus_id,
    2177         596 :                                                          current_sideset_elem_ids.size(),
    2178             :                                                          0  /* No distribution factors */);
    2179             : 
    2180        5193 :               EX_CHECK_ERR(this->ex_err, "Error writing sideset parameters in Nemesis");
    2181             : 
    2182             :               // Call Exodus interface to write the actual side IDs for this boundary ID
    2183       10386 :               this->ex_err = exII::ex_put_side_set(this->ex_id,
    2184        5193 :                                                    exodus_id,
    2185         596 :                                                    current_sideset_elem_ids.data(),
    2186         596 :                                                    current_sideset_side_ids.data());
    2187             : 
    2188        5193 :               EX_CHECK_ERR(this->ex_err, "Error writing sidesets in Nemesis");
    2189             :             }
    2190             :         } // end for loop over global sideset IDs
    2191             : 
    2192             :       // Write sideset names to file.  Some of these may be blank strings
    2193             :       // if the current processor didn't have all the sideset names for
    2194             :       // any reason...
    2195        8021 :       ex_err = exII::ex_put_names(this->ex_id,
    2196             :                                   exII::EX_SIDE_SET,
    2197             :                                   names_table.get_char_star_star());
    2198        8021 :       EX_CHECK_ERR(ex_err, "Error writing sideset names");
    2199             : 
    2200             :     } // end if (global_sideset_ids.size() > 0)
    2201        8021 : }
    2202             : 
    2203             : 
    2204             : 
    2205        8021 : void Nemesis_IO_Helper::write_nodal_coordinates(const MeshBase & mesh, bool /*use_discontinuous*/)
    2206             : {
    2207         452 :   auto local_num_nodes = this->exodus_node_num_to_libmesh.size();
    2208             : 
    2209        8021 :   x.resize(local_num_nodes);
    2210        8021 :   y.resize(local_num_nodes);
    2211        8021 :   z.resize(local_num_nodes);
    2212             : 
    2213             :   // Just loop over our list outputting the nodes the way we built the map
    2214       46556 :   for (auto i : make_range(local_num_nodes))
    2215             :     {
    2216       41205 :       const Point & pt = mesh.point(this->exodus_node_num_to_libmesh[i]-1);
    2217       38535 :       x[i]=pt(0);
    2218       38535 :       y[i]=pt(1);
    2219       41205 :       z[i]=pt(2);
    2220             :     }
    2221             : 
    2222        8021 :   if (local_num_nodes)
    2223             :     {
    2224             :       // Call Exodus API to write nodal coordinates...
    2225        4534 :       ex_err = exII::ex_put_coord
    2226       18136 :         (ex_id,
    2227        9272 :          x.empty() ? nullptr : MappedOutputVector(x, _single_precision).data(),
    2228        9272 :          y.empty() ? nullptr : MappedOutputVector(y, _single_precision).data(),
    2229        9272 :          z.empty() ? nullptr : MappedOutputVector(z, _single_precision).data());
    2230        4534 :       EX_CHECK_ERR(ex_err, "Error writing node coordinates");
    2231             : 
    2232             :       // And write the nodal map we created for them
    2233        4534 :       ex_err = exII::ex_put_node_num_map(ex_id, this->exodus_node_num_to_libmesh.data());
    2234        4534 :       EX_CHECK_ERR(ex_err, "Error writing node num map");
    2235             :     }
    2236             :   else // Does the Exodus API want us to write empty nodal coordinates?
    2237             :     {
    2238        3487 :       ex_err = exII::ex_put_coord(ex_id, nullptr, nullptr, nullptr);
    2239        3487 :       EX_CHECK_ERR(ex_err, "Error writing empty node coordinates");
    2240             : 
    2241        3487 :       ex_err = exII::ex_put_node_num_map(ex_id, nullptr);
    2242        3487 :       EX_CHECK_ERR(ex_err, "Error writing empty node num map");
    2243             :     }
    2244        8021 : }
    2245             : 
    2246             : 
    2247             : 
    2248             : 
    2249             : 
    2250        8021 : void Nemesis_IO_Helper::write_elements(const MeshBase & mesh, bool /*use_discontinuous*/)
    2251             : {
    2252             :   // Only write elements if there are elements blocks available.
    2253        8021 :   if (this->num_elem_blks_global > 0)
    2254             :     {
    2255             :       // Data structure to store element block names that will be used to
    2256             :       // write the element block names to file.
    2257        8473 :       NamesData names_table(this->num_elem_blks_global, MAX_STR_LENGTH);
    2258             : 
    2259             :       // Loop over all blocks, even if we don't have elements in each block.
    2260             :       // If we don't have elements we need to write out a 0 for that block...
    2261       16042 :       for (auto i : make_range(this->num_elem_blks_global))
    2262             :         {
    2263             :           // Even if there are no elements for this block on the current
    2264             :           // processor, we still want to write its name to file, if
    2265             :           // possible. MeshBase::subdomain_name() will just return an
    2266             :           // empty string if there is no name associated with the current
    2267             :           // block.
    2268             :           names_table.push_back_entry
    2269        8247 :             (mesh.subdomain_name(cast_int<subdomain_id_type>(this->global_elem_blk_ids[i])));
    2270             : 
    2271             :           // Search for the current global block ID in the map
    2272        8247 :           if (const auto it = this->block_id_to_elem_connectivity.find( this->global_elem_blk_ids[i] );
    2273         226 :               it == this->block_id_to_elem_connectivity.end())
    2274             :             {
    2275             :               // If not found, write a zero to file....
    2276        3487 :               this->ex_err = exII::ex_put_elem_block(this->ex_id,
    2277          22 :                                                      this->global_elem_blk_ids[i],
    2278             :                                                      "Empty",
    2279             :                                                      0, /* n. elements in this block */
    2280             :                                                      0, /* n. nodes per element */
    2281             :                                                      0);  /* number of attributes per element */
    2282             : 
    2283        3487 :               EX_CHECK_ERR(this->ex_err, "Error writing element block from Nemesis.");
    2284             :             }
    2285             : 
    2286             :           // Otherwise, write the actual block information and connectivity to file
    2287             :           else
    2288             :             {
    2289             :               subdomain_id_type block =
    2290        4534 :                 cast_int<subdomain_id_type>(it->first);
    2291         204 :               const std::vector<int> & this_block_connectivity = it->second;
    2292        4534 :               std::vector<dof_id_type> & elements_in_this_block = subdomain_map[block];
    2293             : 
    2294             :               // Use the first element in this block to get representative information.
    2295             :               // Note that Exodus assumes all elements in a block are of the same type!
    2296             :               // We are using that same assumption here!
    2297             :               const auto & conv =
    2298        4534 :                 get_conversion(mesh.elem_ref(elements_in_this_block[0]).type());
    2299             : 
    2300        4534 :               this->num_nodes_per_elem =
    2301        4534 :                 mesh.elem_ref(elements_in_this_block[0]).n_nodes();
    2302             : 
    2303        4534 :               ex_err = exII::ex_put_elem_block(ex_id,
    2304             :                                                block,
    2305        4738 :                                                conv.exodus_elem_type().c_str(),
    2306         408 :                                                elements_in_this_block.size(),
    2307         204 :                                                num_nodes_per_elem,
    2308             :                                                0);
    2309        4534 :               EX_CHECK_ERR(ex_err, "Error writing element block from Nemesis.");
    2310             : 
    2311        4738 :               ex_err = exII::ex_put_elem_conn(ex_id,
    2312             :                                               block,
    2313         408 :                                               this_block_connectivity.data());
    2314        4534 :               EX_CHECK_ERR(ex_err, "Error writing element connectivities from Nemesis.");
    2315             :             }
    2316             :         } // end loop over global block IDs
    2317             : 
    2318             :       // Only call this once, not in the loop above!
    2319       12555 :       ex_err = exII::ex_put_elem_num_map(ex_id,
    2320         430 :                                          exodus_elem_num_to_libmesh.empty() ? nullptr : exodus_elem_num_to_libmesh.data());
    2321        8021 :       EX_CHECK_ERR(ex_err, "Error writing element map");
    2322             : 
    2323             :       // Write the element block names to file.
    2324        8021 :       ex_err = exII::ex_put_names(ex_id, exII::EX_ELEM_BLOCK, names_table.get_char_star_star());
    2325        8021 :       EX_CHECK_ERR(ex_err, "Error writing element block names");
    2326             :     } // end if (this->num_elem_blks_global > 0)
    2327        8021 : }
    2328             : 
    2329             : 
    2330             : 
    2331             : 
    2332             : 
    2333           0 : void Nemesis_IO_Helper::write_nodal_solution(const std::vector<Number> & values,
    2334             :                                              const std::vector<std::string> & names,
    2335             :                                              int timestep)
    2336             : {
    2337           0 :   int num_vars = cast_int<int>(names.size());
    2338             :   //int num_values = values.size(); // Not used?
    2339             : 
    2340           0 :   for (int c=0; c<num_vars; c++)
    2341             :     {
    2342             : #ifdef LIBMESH_USE_COMPLEX_NUMBERS
    2343           0 :       std::vector<Real> real_parts(num_nodes);
    2344           0 :       std::vector<Real> imag_parts(num_nodes);
    2345           0 :       std::vector<Real> magnitudes(num_nodes);
    2346             : 
    2347           0 :       for (int i=0; i<num_nodes; ++i)
    2348             :         {
    2349           0 :           Number value = values[(this->exodus_node_num_to_libmesh[i]-1)*num_vars + c];
    2350           0 :           real_parts[i] = value.real();
    2351           0 :           imag_parts[i] = value.imag();
    2352           0 :           magnitudes[i] = std::abs(value);
    2353             :         }
    2354           0 :       write_nodal_values(3*c+1,real_parts,timestep);
    2355           0 :       write_nodal_values(3*c+2,imag_parts,timestep);
    2356           0 :       write_nodal_values(3*c+3,magnitudes,timestep);
    2357             : #else
    2358           0 :       std::vector<Number> cur_soln(this->num_nodes);
    2359             : 
    2360             :       // Copy out this variable's solution
    2361           0 :       for (int i=0; i<this->num_nodes; i++)
    2362           0 :         cur_soln[i] = values[(this->exodus_node_num_to_libmesh[i]-1)*num_vars + c];
    2363             : 
    2364           0 :       write_nodal_values(c+1,cur_soln,timestep);
    2365             : #endif
    2366             :     }
    2367           0 : }
    2368             : 
    2369             : 
    2370             : 
    2371           0 : void Nemesis_IO_Helper::write_nodal_solution(const NumericVector<Number> & parallel_soln,
    2372             :                                              const std::vector<std::string> & names,
    2373             :                                              int timestep,
    2374             :                                              const std::vector<std::string> & output_names)
    2375             : {
    2376           0 :   int num_vars = cast_int<int>(names.size());
    2377             : 
    2378           0 :   for (int c=0; c<num_vars; c++)
    2379             :     {
    2380             :       // Find the position of names[c] in the output_names vector, if it exists.
    2381           0 :       auto pos = std::find(output_names.begin(), output_names.end(), names[c]);
    2382             : 
    2383             :       // Skip names[c] if it's not supposed to be output.
    2384           0 :       if (pos == output_names.end())
    2385           0 :         continue;
    2386             : 
    2387             :       // Compute the (zero-based) index which determines which
    2388             :       // variable this will be as far as Nemesis is concerned.  This
    2389             :       // will be used below in the write_nodal_values() call.
    2390             :       int variable_name_position =
    2391           0 :         cast_int<int>(std::distance(output_names.begin(), pos));
    2392             : 
    2393             :       // Fill up a std::vector with the dofs for the current variable
    2394           0 :       std::vector<numeric_index_type> required_indices(this->num_nodes);
    2395             : 
    2396           0 :       for (int i=0; i<this->num_nodes; i++)
    2397           0 :         required_indices[i] = static_cast<dof_id_type>(this->exodus_node_num_to_libmesh[i]-1) * num_vars + c;
    2398             : 
    2399             :       // Get the dof values required to write just our local part of
    2400             :       // the solution vector.
    2401           0 :       std::vector<Number> local_soln;
    2402           0 :       parallel_soln.localize(local_soln, required_indices);
    2403             : 
    2404             : #ifndef LIBMESH_USE_COMPLEX_NUMBERS
    2405             :       // Call the ExodusII_IO_Helper function to write the data.
    2406           0 :       write_nodal_values(variable_name_position + 1, local_soln, timestep);
    2407             : #else
    2408             :       // We have the local (complex) values. Now extract the real,
    2409             :       // imaginary, and magnitude values from them.
    2410           0 :       std::vector<Real> real_parts(num_nodes);
    2411           0 :       std::vector<Real> imag_parts(num_nodes);
    2412           0 :       std::vector<Real> magnitudes(num_nodes);
    2413             : 
    2414           0 :       for (int i=0; i<num_nodes; ++i)
    2415             :         {
    2416           0 :           real_parts[i] = local_soln[i].real();
    2417           0 :           imag_parts[i] = local_soln[i].imag();
    2418           0 :           magnitudes[i] = std::abs(local_soln[i]);
    2419             :         }
    2420             : 
    2421             :       // Write the real, imaginary, and magnitude values to file.
    2422           0 :       write_nodal_values(3 * variable_name_position + 1, real_parts, timestep);
    2423           0 :       write_nodal_values(3 * variable_name_position + 2, imag_parts, timestep);
    2424           0 :       write_nodal_values(3 * variable_name_position + 3, magnitudes, timestep);
    2425             : #endif
    2426             :     }
    2427           0 : }
    2428             : 
    2429             : 
    2430             : 
    2431         708 : void Nemesis_IO_Helper::write_nodal_solution(const EquationSystems & es,
    2432             :                                              const std::vector<std::pair<unsigned int, unsigned int>> & var_nums,
    2433             :                                              int timestep,
    2434             :                                              const std::vector<std::string> & output_names)
    2435             : {
    2436          40 :   const MeshBase & mesh = es.get_mesh();
    2437             : 
    2438             :   // FIXME - half this code might be replaceable with a call to
    2439             :   // EquationSystems::build_parallel_solution_vector()...
    2440             : 
    2441        1134 :   for (auto [sys_num, var] : var_nums)
    2442             :     {
    2443          12 :       const System & sys = es.get_system(sys_num);
    2444          12 :       const std::string & name = sys.variable_name(var);
    2445             : 
    2446         426 :       auto pos = std::find(output_names.begin(), output_names.end(), name);
    2447             : 
    2448             :       // Skip this name if it's not supposed to be output.
    2449         438 :       if (pos == output_names.end())
    2450         142 :         continue;
    2451             : 
    2452             :       // Compute the (zero-based) index which determines which
    2453             :       // variable this will be as far as Nemesis is concerned.  This
    2454             :       // will be used below in the write_nodal_values() call.
    2455             :       int variable_name_position =
    2456          16 :         cast_int<int>(std::distance(output_names.begin(), pos));
    2457             : 
    2458             :       // Fill up a std::vector with the dofs for the current variable
    2459         292 :       std::vector<numeric_index_type> required_indices(this->num_nodes);
    2460             : 
    2461             :       // Get the dof values required to write just our local part of
    2462             :       // the solution vector.
    2463          16 :       std::vector<Number> local_soln;
    2464             : 
    2465         284 :       const FEType type = sys.variable_type(var);
    2466         284 :       if (type.family == SCALAR)
    2467             :         {
    2468           0 :           std::vector<numeric_index_type> scalar_indices;
    2469           0 :           sys.get_dof_map().SCALAR_dof_indices(scalar_indices, var);
    2470           0 :           for (int i=0; i<this->num_nodes; i++)
    2471           0 :             required_indices[i] = scalar_indices[0];
    2472           0 :           sys.current_local_solution->get(required_indices, local_soln);
    2473             :         }
    2474             :       else
    2475             :         {
    2476             :           // If we have DoFs at all nodes, e.g. for isoparametric
    2477             :           // elements, this is easy:
    2478           8 :           bool found_all_indices = true;
    2479         912 :           for (int i=0; i<this->num_nodes; i++)
    2480             :             {
    2481         723 :               const Node & node = mesh.node_ref(this->exodus_node_num_to_libmesh[i]-1);
    2482         672 :               if (node.n_comp(sys_num, var))
    2483         628 :                 required_indices[i] = node.dof_number(sys_num, var, 0);
    2484             :               else
    2485             :                 {
    2486           4 :                   found_all_indices = false;
    2487           4 :                   break;
    2488             :                 }
    2489             :             }
    2490             : 
    2491         284 :           if (found_all_indices)
    2492         240 :             sys.current_local_solution->get(required_indices, local_soln);
    2493             :           // Fine, we'll do it the hard way
    2494          16 :           if (!found_all_indices)
    2495             :             {
    2496          44 :               local_soln.resize(num_nodes);
    2497             : 
    2498           4 :               const Variable & var_description = sys.variable(var);
    2499           4 :               const DofMap & dof_map           = sys.get_dof_map();
    2500             : 
    2501           4 :               NumericVector<Number> & sys_soln(*sys.current_local_solution);
    2502           8 :               std::vector<Number>      elem_soln;   // The finite element solution
    2503           8 :               std::vector<Number>      nodal_soln;  // The FE solution interpolated to the nodes
    2504           8 :               std::vector<dof_id_type> dof_indices; // The DOF indices for the finite element
    2505             : 
    2506         172 :               for (const auto & elem : mesh.active_local_element_ptr_range())
    2507          48 :                 if (var_description.active_on_subdomain(elem->subdomain_id()))
    2508             :                   {
    2509          48 :                     dof_map.dof_indices (elem, dof_indices, var);
    2510          52 :                     elem_soln.resize(dof_indices.size());
    2511             : 
    2512         192 :                     for (auto i : index_range(dof_indices))
    2513         156 :                       elem_soln[i] = sys_soln(dof_indices[i]);
    2514             : 
    2515          48 :                     FEInterface::nodal_soln (elem->dim(),
    2516             :                                              type,
    2517             :                                              elem,
    2518             :                                              elem_soln,
    2519             :                                              nodal_soln);
    2520             : 
    2521             :                     // infinite elements should be skipped...
    2522          12 :                     if (!elem->infinite())
    2523         340 :                       for (auto n : elem->node_index_range())
    2524             :                         {
    2525             :                           const std::size_t exodus_num =
    2526         312 :                             libmesh_node_num_to_exodus[elem->node_id(n)];
    2527          24 :                           libmesh_assert_greater(exodus_num, 0);
    2528          24 :                           libmesh_assert_less(exodus_num-1, local_soln.size());
    2529         336 :                           local_soln[exodus_num-1] = nodal_soln[n];
    2530             :                         }
    2531          36 :                   }
    2532             :             }
    2533             :         }
    2534             : 
    2535             : #ifndef LIBMESH_USE_COMPLEX_NUMBERS
    2536             :       // Call the ExodusII_IO_Helper function to write the data.
    2537         280 :       write_nodal_values(variable_name_position + 1, local_soln, timestep);
    2538             : #else
    2539             :       // We have the local (complex) values. Now extract the real,
    2540             :       // imaginary, and magnitude values from them.
    2541           4 :       std::vector<Real> real_parts(num_nodes);
    2542           4 :       std::vector<Real> imag_parts(num_nodes);
    2543           4 :       std::vector<Real> magnitudes(num_nodes);
    2544             : 
    2545          54 :       for (int i=0; i<num_nodes; ++i)
    2546             :         {
    2547          50 :           real_parts[i] = local_soln[i].real();
    2548          50 :           imag_parts[i] = local_soln[i].imag();
    2549          50 :           magnitudes[i] = std::abs(local_soln[i]);
    2550             :         }
    2551             : 
    2552             :       // Write the real, imaginary, and magnitude values to file.
    2553           4 :       write_nodal_values(3 * variable_name_position + 1, real_parts, timestep);
    2554           4 :       write_nodal_values(3 * variable_name_position + 2, imag_parts, timestep);
    2555           4 :       write_nodal_values(3 * variable_name_position + 3, magnitudes, timestep);
    2556             : #endif
    2557             :     }
    2558         708 : }
    2559             : 
    2560             : 
    2561             : 
    2562             : void
    2563        7595 : Nemesis_IO_Helper::initialize_element_variables(std::vector<std::string> names,
    2564             :                                                 const std::vector<std::set<subdomain_id_type>> & vars_active_subdomains)
    2565             : {
    2566             :   // Quick return if there are no element variables to write
    2567        7595 :   if (names.size() == 0)
    2568           0 :     return;
    2569             : 
    2570             :   // Quick return if we have already called this function
    2571        7595 :   if (_elem_vars_initialized)
    2572           0 :     return;
    2573             : 
    2574             :   // Be sure that variables in the file match what we are asking for
    2575        7595 :   if (num_elem_vars > 0)
    2576             :     {
    2577           0 :       this->check_existing_vars(ELEMENTAL, names, this->elem_var_names);
    2578           0 :       return;
    2579             :     }
    2580             : 
    2581             :   // Set the flag so we can skip this stuff on subsequent calls to
    2582             :   // initialize_element_variables()
    2583        7595 :   _elem_vars_initialized = true;
    2584             : 
    2585        7595 :   this->write_var_names(ELEMENTAL, names);
    2586             : 
    2587             :   // Create a truth table from global_elem_blk_ids and the information
    2588             :   // in vars_active_subdomains. Create a truth table of
    2589             :   // size global_elem_blk_ids.size() * names.size().
    2590        8237 :   std::vector<int> truth_tab(global_elem_blk_ids.size() * names.size());
    2591       15190 :   for (auto blk : index_range(global_elem_blk_ids))
    2592       15540 :     for (auto var : index_range(names))
    2593        8163 :       if (vars_active_subdomains[var].empty() ||
    2594        7727 :           vars_active_subdomains[var].count(cast_int<subdomain_id_type>(global_elem_blk_ids[blk])))
    2595        8163 :         truth_tab[names.size() * blk + var] = 1;
    2596             : 
    2597             :   // Write truth table to file.
    2598        7595 :   if (truth_tab.size())
    2599             :     {
    2600        7809 :       ex_err = exII::ex_put_elem_var_tab(ex_id,
    2601             :                                          cast_int<int>(global_elem_blk_ids.size()),
    2602             :                                          cast_int<int>(names.size()),
    2603             :                                          truth_tab.data());
    2604        7595 :       EX_CHECK_ERR(ex_err, "Error writing element truth table.");
    2605             :     }
    2606             : }
    2607             : 
    2608             : 
    2609             : 
    2610             : void
    2611        7595 : Nemesis_IO_Helper::write_element_values(const MeshBase & mesh,
    2612             :                                         const EquationSystems & es,
    2613             :                                         const std::vector<std::pair<unsigned int, unsigned int>> &var_nums,
    2614             :                                         int timestep,
    2615             :                                         const std::vector<std::set<subdomain_id_type>> & vars_active_subdomains)
    2616             : {
    2617             :   // For each variable in names,
    2618             :   //   For each subdomain in subdomain_map,
    2619             :   //     If this (subdomain, variable) combination is active
    2620             :   //       For each component in variable
    2621             :   //         Extract element values into local_soln (localize is a collective)
    2622             :   //         Write local_soln to file
    2623             :   //   Update var_ctr with number of vector components for variable
    2624             :   //
    2625         214 :   unsigned int var_ctr = 0;
    2626       15190 :   for (auto v : index_range(var_nums))
    2627             :     {
    2628        7595 :       const unsigned int sys_num = var_nums[v].first;
    2629        7595 :       const unsigned int var = var_nums[v].second;
    2630         214 :       const System & system = es.get_system(sys_num);
    2631             : 
    2632             :       // We need to check if the constant monomial is a scalar or a vector and set the number of
    2633             :       // components as the mesh spatial dimension for the latter as per es.find_variable_numbers().
    2634             :       // Even for the case where a variable is not active on any subdomain belonging to the
    2635             :       // processor, we still need to know this number to update 'var_ctr'.
    2636             :       const unsigned int n_comps =
    2637         455 :         (system.variable_type(var) == FEType(CONSTANT, MONOMIAL_VEC)) ? mesh.spatial_dimension() : 1;
    2638             : 
    2639             :       // Get list of active subdomains for variable v
    2640         428 :       const auto & active_subdomains = vars_active_subdomains[v];
    2641             : 
    2642       15190 :       for (const int sbd_id_int : global_elem_blk_ids)
    2643             :         {
    2644             :           const subdomain_id_type sbd_id =
    2645        7381 :             cast_int<subdomain_id_type>(sbd_id_int);
    2646         214 :           auto it = subdomain_map.find(sbd_id);
    2647         428 :           const std::vector<dof_id_type> empty_vec;
    2648             :           const std::vector<dof_id_type> & elem_ids =
    2649        7595 :             (it == subdomain_map.end()) ? empty_vec : it->second;
    2650             : 
    2651             :           // Possibly skip this (variable, subdomain) combination. Also, check that there is at
    2652             :           // least one element on the subdomain... Indeed, it is possible to have zero elements,
    2653             :           // e.g., when running "adaptivity_ex3" in parallel with the 'dimension=1' argument.
    2654        7595 :           if ((active_subdomains.empty() || active_subdomains.count(sbd_id)) && elem_ids.size())
    2655             :             {
    2656         384 :               std::vector<numeric_index_type> required_indices;
    2657        4510 :               required_indices.reserve(elem_ids.size());
    2658             : 
    2659             :               // The number of DOF components needs to be equal to the expected number so that we
    2660             :               // know where to store data to correctly correspond to variable names - verify this by
    2661             :               // accessing the n_comp method for the last element ID, which should return the same
    2662             :               // value for all elements on a given subdomain, so we only need to check this once.
    2663         192 :               libmesh_assert_equal_to(n_comps, mesh.elem_ref(elem_ids.back()).n_comp(sys_num, var));
    2664             : 
    2665             :               // Loop through the DOFs of the variable and write the values for it on each element.
    2666             :               // The variable name should have been decomposed by es.find_variable_numbers().
    2667        8720 :               for (unsigned int comp = 0; comp < n_comps; ++comp)
    2668             :               {
    2669       25236 :                 for (const auto & id : elem_ids)
    2670       20834 :                     required_indices.push_back(mesh.elem_ref(id).dof_number(sys_num, var, comp));
    2671             : 
    2672         392 :                 std::vector<Number> local_soln;
    2673        4402 :                 system.current_local_solution->get(required_indices, local_soln);
    2674             : 
    2675             :                 // reset for the next component
    2676         196 :                 required_indices.clear();
    2677             : 
    2678             :                 // It's possible that there's nothing for us to write:
    2679             :                 // we may not be responsible for any elements on the
    2680             :                 // current subdomain.  We did still have to participate
    2681             :                 // in the localize() call above, however, since it is a
    2682             :                 // collective.
    2683        4402 :                 if (local_soln.size())
    2684             :                   {
    2685             : #ifdef LIBMESH_USE_COMPLEX_NUMBERS
    2686         105 :                     int stride = write_complex_abs ? 3 : 2;
    2687         105 :                     std::vector<Real> local_soln_buffer(local_soln.size());
    2688             :                     std::transform(local_soln.begin(), local_soln.end(),
    2689             :                                    local_soln_buffer.begin(), [](Number n) { return n.real(); });
    2690         105 :                     ex_err = exII::ex_put_elem_var(ex_id,
    2691             :                                                    timestep,
    2692         105 :                                                    static_cast<int>(stride*(var_ctr+comp)+1),
    2693             :                                                    static_cast<int>(sbd_id),
    2694             :                                                    static_cast<int>(local_soln.size()),
    2695         105 :                                                    MappedOutputVector(local_soln_buffer, _single_precision).data());
    2696         105 :                     EX_CHECK_ERR(ex_err, "Error writing element real values.");
    2697             : 
    2698             :                     std::transform(local_soln.begin(), local_soln.end(),
    2699             :                                    local_soln_buffer.begin(), [](Number n) { return n.imag(); });
    2700         105 :                     ex_err = exII::ex_put_elem_var(ex_id,
    2701             :                                                    timestep,
    2702         105 :                                                    static_cast<int>(stride*(var_ctr+comp)+2),
    2703             :                                                    static_cast<int>(sbd_id),
    2704             :                                                    static_cast<int>(local_soln.size()),
    2705         105 :                                                    MappedOutputVector(local_soln_buffer, _single_precision).data());
    2706         105 :                     EX_CHECK_ERR(ex_err, "Error writing element imaginary values.");
    2707             : 
    2708         105 :                     if (write_complex_abs)
    2709             :                       {
    2710         105 :                         std::transform(local_soln.begin(), local_soln.end(),
    2711             :                                        local_soln_buffer.begin(), [](Number n) { return std::abs(n); });
    2712         105 :                         ex_err = exII::ex_put_elem_var(ex_id,
    2713             :                                                        timestep,
    2714             :                                                        static_cast<int>(stride*(var_ctr+comp)+2),
    2715             :                                                        static_cast<int>(sbd_id),
    2716             :                                                        static_cast<int>(local_soln.size()),
    2717         105 :                                                        MappedOutputVector(local_soln_buffer, _single_precision).data());
    2718         105 :                         EX_CHECK_ERR(ex_err, "Error writing element magnitudes.");
    2719             :                       }
    2720             : #else // LIBMESH_USE_COMPLEX_NUMBERS
    2721        5081 :                     ex_err = exII::ex_put_elem_var(ex_id,
    2722             :                                                    timestep,
    2723        4297 :                                                    static_cast<int>(var_ctr+comp+1),
    2724             :                                                    static_cast<int>(sbd_id),
    2725         392 :                                                    static_cast<int>(local_soln.size()),
    2726        4689 :                                                    MappedOutputVector(local_soln, _single_precision).data());
    2727        4297 :                     EX_CHECK_ERR(ex_err, "Error writing element values.");
    2728             : #endif // LIBMESH_USE_COMPLEX_NUMBERS
    2729             :                   }
    2730             :               } // end loop over vector components
    2731             :             }
    2732             :         } // end loop over active subdomains
    2733             : 
    2734        7595 :         var_ctr += n_comps;
    2735             :     } // end loop over vars
    2736             : 
    2737        7595 :   this->update();
    2738        7595 : }
    2739             : 
    2740             : 
    2741             : 
    2742        1286 : void Nemesis_IO_Helper::read_var_names_impl(const char * var_type,
    2743             :                                             int & count,
    2744             :                                             std::vector<std::string> & result)
    2745             : {
    2746             :   // Most of what we need to do is the same as for Exodus
    2747        1286 :   this->ExodusII_IO_Helper::read_var_names_impl(var_type, count, result);
    2748             : 
    2749             :   // But with tests where we have more processors than elements,
    2750             :   // Nemesis doesn't let us put variable names in files written by
    2751             :   // processors owning nothing, but we may still *need* those
    2752             :   // variable names on every processor, so let's sync them up...
    2753             : 
    2754        1286 :   processor_id_type pid_broadcasting_names = this->processor_id();
    2755          72 :   const std::size_t n_names = result.size();
    2756        1286 :   if (!n_names)
    2757         154 :     pid_broadcasting_names = DofObject::invalid_processor_id;
    2758             : 
    2759          36 :   libmesh_assert(this->comm().semiverify
    2760             :                  (n_names ? nullptr : &n_names));
    2761             : 
    2762        1250 :   this->comm().min(pid_broadcasting_names);
    2763             : 
    2764        1286 :   if (pid_broadcasting_names != DofObject::invalid_processor_id)
    2765        1286 :     this->comm().broadcast(result, pid_broadcasting_names);
    2766        1286 : }
    2767             : 
    2768             : 
    2769             : 
    2770        8871 : std::string Nemesis_IO_Helper::construct_nemesis_filename(std::string_view base_filename)
    2771             : {
    2772             :   // Build a filename for this processor.  This code is cut-n-pasted from the read function
    2773             :   // and should probably be put into a separate function...
    2774        9371 :   std::ostringstream file_oss;
    2775             : 
    2776             :   // We have to be a little careful here: Nemesis left pads its file
    2777             :   // numbers based on the number of processors, so for example on 10
    2778             :   // processors, we'd have:
    2779             :   // mesh.e.10.00
    2780             :   // mesh.e.10.01
    2781             :   // mesh.e.10.02
    2782             :   // ...
    2783             :   // mesh.e.10.09
    2784             : 
    2785             :   // And on 100 you'd have:
    2786             :   // mesh.e.100.000
    2787             :   // mesh.e.100.001
    2788             :   // ...
    2789             :   // mesh.e.128.099
    2790             : 
    2791             :   // Find the length of the highest processor ID
    2792         500 :   file_oss << (this->n_processors());
    2793        8871 :   unsigned int field_width = cast_int<unsigned int>(file_oss.str().size());
    2794             : 
    2795        8871 :   if (verbose)
    2796           0 :     libMesh::out << "field_width=" << field_width << std::endl;
    2797             : 
    2798       17492 :   file_oss.str(""); // reset the string stream
    2799             :   file_oss << base_filename
    2800        8871 :            << '.' << this->n_processors()
    2801       17242 :            << '.' << std::setfill('0') << std::setw(field_width) << this->processor_id();
    2802             : 
    2803             :   // Return the resulting string
    2804        9121 :   return file_oss.str();
    2805        8371 : }
    2806             : 
    2807             : } // namespace libMesh
    2808             : 
    2809             : #endif // #if defined(LIBMESH_HAVE_NEMESIS_API) && defined(LIBMESH_HAVE_EXODUS_API)

Generated by: LCOV version 1.14