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