• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

openmc-dev / openmc / 30550696926

30 Jul 2026 02:14PM UTC coverage: 81.446% (-0.006%) from 81.452%
30550696926

Pull #4038

github

web-flow
Merge ec9a98478 into 6285f579e
Pull Request #4038: Write unstructured mesh exodus file from one MPI rank

18375 of 26608 branches covered (69.06%)

Branch coverage included in aggregate %.

7 of 7 new or added lines in 1 file covered. (100.0%)

7 existing lines in 2 files now uncovered.

60048 of 69680 relevant lines covered (86.18%)

47019326.26 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

84.92
/src/state_point.cpp
1
#include "openmc/state_point.h"
2

3
#include <algorithm>
4
#include <cstdint> // for int64_t
5
#include <string>
6

7
#include "openmc/tensor.h"
8
#include <fmt/core.h>
9

10
#include "openmc/bank.h"
11
#include "openmc/bank_io.h"
12
#include "openmc/capi.h"
13
#include "openmc/constants.h"
14
#include "openmc/eigenvalue.h"
15
#include "openmc/error.h"
16
#include "openmc/file_utils.h"
17
#include "openmc/hdf5_interface.h"
18
#include "openmc/mcpl_interface.h"
19
#include "openmc/mesh.h"
20
#include "openmc/message_passing.h"
21
#include "openmc/mgxs_interface.h"
22
#include "openmc/nuclide.h"
23
#include "openmc/output.h"
24
#include "openmc/particle_type.h"
25
#include "openmc/random_ray/flat_source_domain.h"
26
#include "openmc/settings.h"
27
#include "openmc/simulation.h"
28
#include "openmc/tallies/derivative.h"
29
#include "openmc/tallies/filter.h"
30
#include "openmc/tallies/filter_mesh.h"
31
#include "openmc/tallies/tally.h"
32
#include "openmc/timer.h"
33
#include "openmc/vector.h"
34

35
namespace openmc {
36

37
extern "C" int openmc_statepoint_write(const char* filename, bool* write_source)
7,962✔
38
{
39
  simulation::time_statepoint.start();
7,962✔
40

41
  // If a nullptr is passed in, we assume that the user
42
  // wants a default name for this, of the form like output/statepoint.20.h5
43
  std::string filename_;
7,962✔
44
  if (filename) {
7,962✔
45
    filename_ = filename;
810✔
46
  } else {
47
    // Determine width for zero padding
48
    int w = std::to_string(settings::n_max_batches).size();
7,152✔
49

50
    // Tag statepoints written during the forward solve of an adjoint run
51
    const char* forward =
14,304✔
52
      (FlatSourceDomain::solve_ == RandomRaySolve::FORWARD_FOR_ADJOINT)
7,152✔
53
        ? "forward."
7,152✔
54
        : "";
55

56
    // Set filename for state point
57
    filename_ = fmt::format("{0}statepoint.{3}{1:0{2}}.h5",
14,304✔
58
      settings::path_output, simulation::current_batch, w, forward);
7,152✔
59
  }
60

61
  // If a file name was specified, ensure it has .h5 file extension
62
  const auto extension = get_file_extension(filename_);
7,962✔
63
  if (extension != "h5") {
7,962!
64
    warning("openmc_statepoint_write was passed a file extension differing "
×
65
            "from .h5, but an hdf5 file will be written.");
66
  }
67

68
  // Determine whether or not to write the source bank
69
  bool write_source_ = write_source ? *write_source : true;
7,962!
70

71
  // Write message
72
  write_message("Creating state point " + filename_ + "...", 5);
15,924✔
73

74
  hid_t file_id;
7,962✔
75
  if (mpi::master) {
7,962✔
76
    // Create statepoint file
77
    file_id = file_open(filename_, 'w');
7,170✔
78

79
    // Write file type
80
    write_attribute(file_id, "filetype", "statepoint");
7,170✔
81

82
    // Write revision number for state point file
83
    write_attribute(file_id, "version", VERSION_STATEPOINT);
7,170✔
84

85
    // Write OpenMC version
86
    write_attribute(file_id, "openmc_version", VERSION);
7,170✔
87
#ifdef GIT_SHA1
88
    write_attribute(file_id, "git_sha1", GIT_SHA1);
89
#endif
90

91
    // Write current date and time
92
    write_attribute(file_id, "date_and_time", time_stamp());
14,340✔
93

94
    // Write path to input
95
    write_attribute(file_id, "path", settings::path_input);
7,170✔
96

97
    // Write out random number seed
98
    write_dataset(file_id, "seed", openmc_get_seed());
7,170✔
99

100
    // Write out random number stride
101
    write_dataset(file_id, "stride", openmc_get_stride());
7,170✔
102

103
    // Write run information
104
    write_dataset(file_id, "energy_mode",
8,162✔
105
      settings::run_CE ? "continuous-energy" : "multi-group");
106
    switch (settings::run_mode) {
7,170!
107
    case RunMode::FIXED_SOURCE:
2,957✔
108
      write_dataset(file_id, "run_mode", "fixed source");
2,957✔
109
      break;
110
    case RunMode::EIGENVALUE:
4,213✔
111
      write_dataset(file_id, "run_mode", "eigenvalue");
4,213✔
112
      break;
113
    default:
114
      break;
115
    }
116
    write_attribute(file_id, "photon_transport", settings::photon_transport);
7,170✔
117
    write_dataset(file_id, "n_particles", settings::n_particles);
7,170✔
118
    write_dataset(file_id, "n_batches", settings::n_batches);
7,170✔
119

120
    // Write out current batch number
121
    write_dataset(file_id, "current_batch", simulation::current_batch);
7,170✔
122

123
    // Indicate whether source bank is stored in statepoint
124
    write_attribute(file_id, "source_present", write_source_);
7,170✔
125

126
    // Write out information for eigenvalue run
127
    if (settings::run_mode == RunMode::EIGENVALUE)
7,170✔
128
      write_eigenvalue_hdf5(file_id);
4,213✔
129

130
    hid_t tallies_group = create_group(file_id, "tallies");
7,170✔
131

132
    // Write meshes
133
    meshes_to_hdf5(tallies_group);
7,170✔
134

135
    // Write information for derivatives
136
    if (!model::tally_derivs.empty()) {
7,170✔
137
      hid_t derivs_group = create_group(tallies_group, "derivatives");
10✔
138
      for (const auto& deriv : model::tally_derivs) {
60✔
139
        hid_t deriv_group =
50✔
140
          create_group(derivs_group, "derivative " + std::to_string(deriv.id));
50✔
141
        write_dataset(deriv_group, "material", deriv.diff_material);
50✔
142
        if (deriv.variable == DerivativeVariable::DENSITY) {
50✔
143
          write_dataset(deriv_group, "independent variable", "density");
20✔
144
        } else if (deriv.variable == DerivativeVariable::NUCLIDE_DENSITY) {
30✔
145
          write_dataset(deriv_group, "independent variable", "nuclide_density");
20✔
146
          write_dataset(
40✔
147
            deriv_group, "nuclide", data::nuclides[deriv.diff_nuclide]->name_);
20✔
148
        } else if (deriv.variable == DerivativeVariable::TEMPERATURE) {
10!
149
          write_dataset(deriv_group, "independent variable", "temperature");
10✔
150
        } else {
151
          fatal_error("Independent variable for derivative " +
×
152
                      std::to_string(deriv.id) +
×
153
                      " not defined in state_point.cpp");
154
        }
155
        close_group(deriv_group);
50✔
156
      }
157
      close_group(derivs_group);
10✔
158
    }
159

160
    // Write information for filters
161
    hid_t filters_group = create_group(tallies_group, "filters");
7,170✔
162
    write_attribute(filters_group, "n_filters", model::tally_filters.size());
7,170✔
163
    if (!model::tally_filters.empty()) {
7,170✔
164
      // Write filter IDs
165
      vector<int32_t> filter_ids;
4,712✔
166
      filter_ids.reserve(model::tally_filters.size());
4,712✔
167
      for (const auto& filt : model::tally_filters)
16,459✔
168
        filter_ids.push_back(filt->id());
11,747✔
169
      write_attribute(filters_group, "ids", filter_ids);
4,712✔
170

171
      // Write info for each filter
172
      for (const auto& filt : model::tally_filters) {
16,459✔
173
        hid_t filter_group =
11,747✔
174
          create_group(filters_group, "filter " + std::to_string(filt->id()));
11,747✔
175
        filt->to_statepoint(filter_group);
11,747✔
176
        close_group(filter_group);
11,747✔
177
      }
178
    }
4,712✔
179
    close_group(filters_group);
7,170✔
180

181
    // Write information for tallies
182
    write_attribute(tallies_group, "n_tallies", model::tallies.size());
7,170✔
183
    if (!model::tallies.empty()) {
7,170✔
184
      // Write tally IDs
185
      vector<int32_t> tally_ids;
5,232✔
186
      tally_ids.reserve(model::tallies.size());
5,232✔
187
      for (const auto& tally : model::tallies)
26,615✔
188
        tally_ids.push_back(tally->id_);
21,383✔
189
      write_attribute(tallies_group, "ids", tally_ids);
5,232✔
190

191
      // Write all tally information except results
192
      for (const auto& tally : model::tallies) {
26,615✔
193
        hid_t tally_group =
21,383✔
194
          create_group(tallies_group, "tally " + std::to_string(tally->id_));
21,383✔
195

196
        write_dataset(tally_group, "name", tally->name_);
21,383✔
197

198
        if (tally->writable_) {
21,383✔
199
          write_attribute(tally_group, "internal", 0);
20,127✔
200
        } else {
201
          write_attribute(tally_group, "internal", 1);
1,256✔
202
          close_group(tally_group);
1,256✔
203
          continue;
1,256✔
204
        }
205

206
        if (tally->multiply_density()) {
20,127✔
207
          write_attribute(tally_group, "multiply_density", 1);
20,007✔
208
        } else {
209
          write_attribute(tally_group, "multiply_density", 0);
120✔
210
        }
211

212
        if (tally->higher_moments()) {
20,127✔
213
          write_attribute(tally_group, "higher_moments", 1);
10✔
214
        } else {
215
          write_attribute(tally_group, "higher_moments", 0);
20,117✔
216
        }
217

218
        if (tally->estimator_ == TallyEstimator::ANALOG) {
20,127✔
219
          write_dataset(tally_group, "estimator", "analog");
7,468✔
220
        } else if (tally->estimator_ == TallyEstimator::TRACKLENGTH) {
12,659✔
221
          write_dataset(tally_group, "estimator", "tracklength");
11,840✔
222
        } else if (tally->estimator_ == TallyEstimator::COLLISION) {
819!
223
          write_dataset(tally_group, "estimator", "collision");
819✔
224
        }
225

226
        write_dataset(tally_group, "n_realizations", tally->n_realizations_);
20,127✔
227

228
        // Write the ID of each filter attached to this tally
229
        write_dataset(tally_group, "n_filters", tally->filters().size());
20,127✔
230
        if (!tally->filters().empty()) {
20,127✔
231
          vector<int32_t> filter_ids;
19,007✔
232
          filter_ids.reserve(tally->filters().size());
19,007✔
233
          for (auto i_filt : tally->filters())
57,644✔
234
            filter_ids.push_back(model::tally_filters[i_filt]->id());
38,637✔
235
          write_dataset(tally_group, "filters", filter_ids);
19,007✔
236
        }
19,007✔
237

238
        // Write the nuclides this tally scores
239
        vector<std::string> nuclides;
20,127✔
240
        for (auto i_nuclide : tally->nuclides_) {
47,134✔
241
          if (i_nuclide == -1) {
27,007✔
242
            nuclides.push_back("total");
35,334✔
243
          } else {
244
            if (settings::run_CE) {
9,340✔
245
              nuclides.push_back(data::nuclides[i_nuclide]->name_);
9,240✔
246
            } else {
247
              nuclides.push_back(data::mg.nuclides_[i_nuclide].name);
100✔
248
            }
249
          }
250
        }
251
        write_dataset(tally_group, "nuclides", nuclides);
20,127✔
252

253
        if (tally->deriv_ != C_NONE)
20,127✔
254
          write_dataset(
200✔
255
            tally_group, "derivative", model::tally_derivs[tally->deriv_].id);
200✔
256

257
        // Write the tally score bins
258
        vector<std::string> scores;
20,127✔
259
        for (auto sc : tally->scores_)
49,318✔
260
          scores.push_back(reaction_name(sc));
58,382✔
261
        write_dataset(tally_group, "n_score_bins", scores.size());
20,127✔
262
        write_dataset(tally_group, "score_bins", scores);
20,127✔
263

264
        close_group(tally_group);
20,127✔
265
      }
20,127✔
266
    }
5,232✔
267

268
    if (settings::reduce_tallies) {
7,170✔
269
      // Write global tallies
270
      write_dataset(file_id, "global_tallies", simulation::global_tallies);
7,160✔
271

272
      // Write tallies
273
      if (model::active_tallies.size() > 0) {
7,160✔
274
        // Indicate that tallies are on
275
        write_attribute(file_id, "tallies_present", 1);
5,022✔
276

277
        // Write all tally results
278
        for (const auto& tally : model::tallies) {
26,195✔
279
          if (!tally->writable_)
21,173✔
280
            continue;
1,056✔
281

282
          // Write results for each bin
283
          std::string name = "tally " + std::to_string(tally->id_);
20,117✔
284
          hid_t tally_group = open_group(tallies_group, name.c_str());
20,117✔
285
          auto& results = tally->results_;
20,117!
286
          write_tally_results(tally_group, results.shape(0), results.shape(1),
60,351!
287
            results.shape(2), results.data());
40,234!
288
          close_group(tally_group);
20,117✔
289
        }
20,117✔
290
      } else {
291
        // Indicate tallies are off
292
        write_attribute(file_id, "tallies_present", 0);
2,138✔
293
      }
294
    }
295

296
    close_group(tallies_group);
7,170✔
297
  }
298

299
  // Check for the no-tally-reduction method
300
  if (!settings::reduce_tallies) {
7,962✔
301
    // If using the no-tally-reduction method, we need to collect tally
302
    // results before writing them to the state point file.
303
    write_tally_results_nr(file_id);
13✔
304

305
  } else if (mpi::master) {
7,949✔
306
    // Write number of global realizations
307
    write_dataset(file_id, "n_realizations", simulation::n_realizations);
7,160✔
308
  }
309

310
  if (mpi::master) {
7,962✔
311
    // Write out the runtime metrics.
312
    using namespace simulation;
7,170✔
313
    hid_t runtime_group = create_group(file_id, "runtime");
7,170✔
314
    write_dataset(
7,170✔
315
      runtime_group, "total initialization", time_initialize.elapsed());
316
    write_dataset(
7,170✔
317
      runtime_group, "reading cross sections", time_read_xs.elapsed());
318
    write_dataset(runtime_group, "simulation",
7,170✔
319
      time_inactive.elapsed() + time_active.elapsed());
7,170✔
320
    write_dataset(runtime_group, "transport", time_transport.elapsed());
7,170✔
321
    if (settings::run_mode == RunMode::EIGENVALUE) {
7,170✔
322
      write_dataset(runtime_group, "inactive batches", time_inactive.elapsed());
4,213✔
323
    }
324
    write_dataset(runtime_group, "active batches", time_active.elapsed());
7,170✔
325
    if (settings::run_mode == RunMode::EIGENVALUE) {
7,170✔
326
      write_dataset(
4,213✔
327
        runtime_group, "synchronizing fission bank", time_bank.elapsed());
328
      write_dataset(
4,213✔
329
        runtime_group, "sampling source sites", time_bank_sample.elapsed());
330
      write_dataset(
4,213✔
331
        runtime_group, "SEND-RECV source sites", time_bank_sendrecv.elapsed());
332
    }
333
    write_dataset(
7,170✔
334
      runtime_group, "accumulating tallies", time_tallies.elapsed());
335
    write_dataset(runtime_group, "total", time_total.elapsed());
7,170✔
336
    write_dataset(
7,170✔
337
      runtime_group, "writing statepoints", time_statepoint.elapsed());
338
    close_group(runtime_group);
7,170✔
339

340
    file_close(file_id);
7,170✔
341
  }
342

343
#ifdef PHDF5
344
  bool parallel = true;
2,952✔
345
#else
346
  bool parallel = false;
5,010✔
347
#endif
348

349
  // Write the source bank if desired
350
  if (write_source_) {
7,962✔
351
    if (mpi::master || parallel)
3,648!
352
      file_id = file_open(filename_, 'a', true);
3,648✔
353
    write_source_bank(file_id, simulation::source_bank, simulation::work_index);
3,648✔
354
    if (mpi::master || parallel)
3,648!
355
      file_close(file_id);
3,648✔
356
  }
357

358
#if defined(OPENMC_LIBMESH_ENABLED) || defined(OPENMC_DAGMC_ENABLED)
359
  // write unstructured mesh tally files
360
  write_unstructured_mesh_results();
1,745✔
361
#endif
362

363
  simulation::time_statepoint.stop();
7,962✔
364

365
  return 0;
7,962✔
366
}
7,962✔
367

368
void restart_set_keff()
56✔
369
{
370
  if (simulation::restart_batch > settings::n_inactive) {
56!
371
    for (int i = settings::n_inactive; i < simulation::restart_batch; ++i) {
267✔
372
      simulation::k_sum[0] += simulation::k_generation[i];
211✔
373
      simulation::k_sum[1] += std::pow(simulation::k_generation[i], 2);
211✔
374
    }
375
    int n = settings::gen_per_batch * simulation::n_realizations;
56✔
376
    simulation::keff = simulation::k_sum[0] / n;
56✔
377
  } else {
378
    simulation::keff = simulation::k_generation.back();
×
379
  }
380
}
56✔
381

382
void load_state_point()
56✔
383
{
384
  write_message(
56✔
385
    fmt::format("Loading state point {}...", settings::path_statepoint_c), 5);
56✔
386
  openmc_statepoint_load(settings::path_statepoint.c_str());
56✔
387
}
56✔
388

389
void statepoint_version_check(hid_t file_id)
56✔
390
{
391
  // Read revision number for state point file and make sure it matches with
392
  // current version
393
  array<int, 2> version_array;
56✔
394
  read_attribute(file_id, "version", version_array);
56✔
395
  if (version_array != VERSION_STATEPOINT) {
56!
396
    fatal_error(
×
397
      "State point version does not match current version in OpenMC.");
398
  }
399
}
56✔
400

401
extern "C" int openmc_statepoint_load(const char* filename)
56✔
402
{
403
  // Open file for reading
404
  hid_t file_id = file_open(filename, 'r', true);
56✔
405

406
  // Read filetype
407
  std::string word;
56✔
408
  read_attribute(file_id, "filetype", word);
56✔
409
  if (word != "statepoint") {
56!
410
    fatal_error("OpenMC tried to restart from a non-statepoint file.");
×
411
  }
412

413
  statepoint_version_check(file_id);
56✔
414

415
  // Read and overwrite random number seed
416
  int64_t seed;
56✔
417
  read_dataset(file_id, "seed", seed);
56✔
418
  openmc_set_seed(seed);
56✔
419

420
  // Read and overwrite random number stride
421
  uint64_t stride;
56✔
422
  read_dataset(file_id, "stride", stride);
56✔
423
  openmc_set_stride(stride);
56✔
424

425
  // It is not impossible for a state point to be generated from a CE run but
426
  // to be loaded in to an MG run (or vice versa), check to prevent that.
427
  read_dataset(file_id, "energy_mode", word);
56✔
428
  if (word == "multi-group" && settings::run_CE) {
56!
429
    fatal_error("State point file is from multigroup run but current run is "
×
430
                "continous energy.");
431
  } else if (word == "continuous-energy" && !settings::run_CE) {
56!
432
    fatal_error("State point file is from continuous-energy run but current "
×
433
                "run is multigroup!");
434
  }
435

436
  // Read and overwrite run information except number of batches
437
  read_dataset(file_id, "run_mode", word);
56✔
438
  if (word == "fixed source") {
56!
439
    settings::run_mode = RunMode::FIXED_SOURCE;
×
440
  } else if (word == "eigenvalue") {
56!
441
    settings::run_mode = RunMode::EIGENVALUE;
56✔
442
  }
443
  read_attribute(file_id, "photon_transport", settings::photon_transport);
56✔
444
  read_dataset(file_id, "n_particles", settings::n_particles);
56✔
445
  int temp;
56✔
446
  read_dataset(file_id, "n_batches", temp);
56✔
447

448
  // Take maximum of statepoint n_batches and input n_batches
449
  settings::n_batches = std::max(settings::n_batches, temp);
56✔
450

451
  // Read batch number to restart at
452
  read_dataset(file_id, "current_batch", simulation::restart_batch);
56✔
453

454
  if (settings::restart_run &&
56!
455
      simulation::restart_batch >= settings::n_max_batches) {
56✔
456
    warning(fmt::format(
20✔
457
      "The number of batches specified for simulation ({}) is smaller "
458
      "than or equal to the number of batches in the restart statepoint file "
459
      "({})",
460
      settings::n_max_batches, simulation::restart_batch));
461
  }
462

463
  // Logical flag for source present in statepoint file
464
  bool source_present;
56✔
465
  read_attribute(file_id, "source_present", source_present);
56✔
466

467
  // Read information specific to eigenvalue run
468
  if (settings::run_mode == RunMode::EIGENVALUE) {
56!
469
    read_dataset(file_id, "n_inactive", temp);
56✔
470
    read_eigenvalue_hdf5(file_id);
56✔
471

472
    // Take maximum of statepoint n_inactive and input n_inactive
473
    settings::n_inactive = std::max(settings::n_inactive, temp);
56!
474

475
    // Check to make sure source bank is present
476
    if (settings::path_sourcepoint == settings::path_statepoint &&
56!
477
        !source_present) {
56!
478
      fatal_error("Source bank must be contained in statepoint restart file");
×
479
    }
480
  }
481

482
  // Read number of realizations for global tallies
483
  read_dataset(file_id, "n_realizations", simulation::n_realizations);
56✔
484

485
  // Set k_sum, keff, and current_batch based on whether restart file is part
486
  // of active cycle or inactive cycle
487
  if (settings::run_mode == RunMode::EIGENVALUE) {
56!
488
    restart_set_keff();
56✔
489
  }
490

491
  // Set current batch number
492
  simulation::current_batch = simulation::restart_batch;
56✔
493

494
  // Read tallies to master. If we are using Parallel HDF5, all processes
495
  // need to be included in the HDF5 calls.
496
#ifdef PHDF5
497
  if (true) {
21✔
498
#else
499
  if (mpi::master) {
35!
500
#endif
501
    // Read global tally data
502
    read_dataset_lowlevel(file_id, "global_tallies", H5T_NATIVE_DOUBLE, H5S_ALL,
56✔
503
      false, simulation::global_tallies.data());
56✔
504

505
    // Check if tally results are present
506
    bool present;
56✔
507
    read_attribute(file_id, "tallies_present", present);
56✔
508

509
    // Read in sum and sum squared
510
    if (present) {
56!
511
      hid_t tallies_group = open_group(file_id, "tallies");
56✔
512

513
      for (auto& tally : model::tallies) {
195✔
514
        // Read sum, sum_sq, and N for each bin
515
        std::string name = "tally " + std::to_string(tally->id_);
139✔
516
        hid_t tally_group = open_group(tallies_group, name.c_str());
139✔
517

518
        int internal = 0;
139✔
519
        if (attribute_exists(tally_group, "internal")) {
139!
520
          read_attribute(tally_group, "internal", internal);
139✔
521
        }
522
        if (internal) {
139!
523
          tally->writable_ = false;
×
524
        } else {
525
          auto& results = tally->results_;
139!
526
          read_tally_results(tally_group, results.shape(0), results.shape(1),
417!
527
            results.shape(2), results.data());
139!
528

529
          read_dataset(tally_group, "n_realizations", tally->n_realizations_);
139✔
530
          close_group(tally_group);
139✔
531
        }
532
      }
139✔
533
      close_group(tallies_group);
56✔
534
    }
535
  }
536

537
  // Read source if in eigenvalue mode
538
  if (settings::run_mode == RunMode::EIGENVALUE) {
56!
539

540
    // Check if source was written out separately
541
    if (!source_present) {
56!
542

543
      // Close statepoint file
544
      file_close(file_id);
×
545

546
      // Write message
547
      write_message(
×
548
        "Loading source file " + settings::path_sourcepoint + "...", 5);
×
549

550
      // Open source file
551
      file_id = file_open(settings::path_sourcepoint.c_str(), 'r', true);
×
552
    }
553

554
    // Read source
555
    read_source_bank(file_id, simulation::source_bank, true);
56✔
556
  }
557

558
  // Close file
559
  file_close(file_id);
56✔
560

561
  return 0;
56✔
562
}
56✔
563

564
hid_t h5banktype(bool memory)
9,997✔
565
{
566
  // Create compound type for position
567
  hid_t postype = H5Tcreate(H5T_COMPOUND, sizeof(struct Position));
9,997✔
568
  H5Tinsert(postype, "x", HOFFSET(Position, x), H5T_NATIVE_DOUBLE);
9,997✔
569
  H5Tinsert(postype, "y", HOFFSET(Position, y), H5T_NATIVE_DOUBLE);
9,997✔
570
  H5Tinsert(postype, "z", HOFFSET(Position, z), H5T_NATIVE_DOUBLE);
9,997✔
571

572
  // Create bank datatype
573
  //
574
  // If you make changes to the compound datatype here, make sure you update:
575
  // - openmc/source.py
576
  // - openmc/statepoint.py
577
  // - docs/source/io_formats/statepoint.rst
578
  // - docs/source/io_formats/source.rst
579
  auto n = sizeof(SourceSite);
9,997✔
580
  if (!memory)
9,997✔
581
    n = 2 * sizeof(struct Position) + 3 * sizeof(double) + 3 * sizeof(int);
4,908✔
582
  hid_t banktype = H5Tcreate(H5T_COMPOUND, n);
9,997✔
583
  H5Tinsert(banktype, "r", HOFFSET(SourceSite, r), postype);
9,997✔
584
  H5Tinsert(banktype, "u", HOFFSET(SourceSite, u), postype);
9,997✔
585
  H5Tinsert(banktype, "E", HOFFSET(SourceSite, E), H5T_NATIVE_DOUBLE);
9,997✔
586
  H5Tinsert(banktype, "time", HOFFSET(SourceSite, time), H5T_NATIVE_DOUBLE);
9,997✔
587
  H5Tinsert(banktype, "wgt", HOFFSET(SourceSite, wgt), H5T_NATIVE_DOUBLE);
9,997✔
588
  H5Tinsert(banktype, "delayed_group", HOFFSET(SourceSite, delayed_group),
9,997✔
589
    H5T_NATIVE_INT);
9,997✔
590
  H5Tinsert(banktype, "surf_id", HOFFSET(SourceSite, surf_id), H5T_NATIVE_INT);
9,997✔
591
  H5Tinsert(
9,997✔
592
    banktype, "particle", HOFFSET(SourceSite, particle), H5T_NATIVE_INT);
9,997✔
593

594
  H5Tclose(postype);
9,997✔
595
  return banktype;
9,997✔
596
}
597

598
void write_source_point(std::string filename, span<SourceSite> source_bank,
1,293✔
599
  const vector<int64_t>& bank_index, bool use_mcpl)
600
{
601
  std::string ext = use_mcpl ? "mcpl" : "h5";
2,553✔
602

603
  int total_surf_particles = source_bank.size();
1,293✔
604
#ifdef OPENMC_MPI
605
  int num_particles = source_bank.size();
423✔
606
  MPI_Allreduce(
423✔
607
    &num_particles, &total_surf_particles, 1, MPI_INT, MPI_SUM, mpi::intracomm);
608
#endif
609

610
  write_message("Creating source file {}.{} with {} particles ...", filename,
1,293✔
611
    ext, total_surf_particles, 5);
1,293✔
612

613
  // Dispatch to appropriate function based on file type
614
  if (use_mcpl) {
1,293✔
615
    filename.append(".mcpl");
33✔
616
    write_mcpl_source_point(filename.c_str(), source_bank, bank_index);
33✔
617
  } else {
618
    filename.append(".h5");
1,260✔
619
    write_h5_source_point(filename.c_str(), source_bank, bank_index);
1,260✔
620
  }
621
}
1,293✔
622

623
void write_h5_source_point(const char* filename, span<SourceSite> source_bank,
1,260✔
624
  const vector<int64_t>& bank_index)
625
{
626
  // When using parallel HDF5, the file is written to collectively by all
627
  // processes. With MPI-only, the file is opened and written by the master
628
  // (note that the call to write_source_bank is by all processes since slave
629
  // processes need to send source bank data to the master.
630
#ifdef PHDF5
631
  bool parallel = true;
411✔
632
#else
633
  bool parallel = false;
849✔
634
#endif
635

636
  if (!filename)
1,260!
637
    fatal_error("write_source_point filename needs a nonempty name.");
×
638

639
  std::string filename_(filename);
1,260✔
640
  const auto extension = get_file_extension(filename_);
1,260✔
641
  if (extension != "h5") {
1,260!
642
    warning("write_source_point was passed a file extension differing "
×
643
            "from .h5, but an hdf5 file will be written.");
644
  }
645

646
  hid_t file_id;
1,260✔
647
  if (mpi::master || parallel) {
1,260!
648
    file_id = file_open(filename_.c_str(), 'w', true);
1,260✔
649
    write_attribute(file_id, "filetype", "source");
1,260✔
650
    write_attribute(file_id, "version", VERSION_STATEPOINT);
1,260✔
651
  }
652

653
  // Get pointer to source bank and write to file
654
  write_source_bank(file_id, source_bank, bank_index);
1,260✔
655

656
  if (mpi::master || parallel)
1,260!
657
    file_close(file_id);
1,260✔
658
}
1,260✔
659

660
void write_source_bank(hid_t group_id, span<SourceSite> source_bank,
4,908✔
661
  const vector<int64_t>& bank_index)
662
{
663
  hid_t membanktype = h5banktype(true);
4,908✔
664
  hid_t filebanktype = h5banktype(false);
4,908✔
665

666
#ifdef OPENMC_MPI
667
  write_bank_dataset("source_bank", group_id, source_bank, bank_index,
1,823✔
668
    membanktype, filebanktype, mpi::source_site);
669
#else
670
  write_bank_dataset("source_bank", group_id, source_bank, bank_index,
3,085✔
671
    membanktype, filebanktype);
672
#endif
673

674
  H5Tclose(membanktype);
4,908✔
675
  H5Tclose(filebanktype);
4,908✔
676
}
4,908✔
677

678
// Determine member names of a compound HDF5 datatype
679
std::string dtype_member_names(hid_t dtype_id)
362✔
680
{
681
  int nmembers = H5Tget_nmembers(dtype_id);
362✔
682
  std::string names;
362✔
683
  for (int i = 0; i < nmembers; i++) {
3,213✔
684
    char* name = H5Tget_member_name(dtype_id, i);
2,851✔
685
    names = names.append(name);
2,851✔
686
    H5free_memory(name);
2,851✔
687
    if (i < nmembers - 1)
2,851✔
688
      names += ", ";
2,851✔
689
  }
690
  return names;
362✔
691
}
×
692

693
void read_source_bank(
181✔
694
  hid_t group_id, vector<SourceSite>& sites, bool distribute)
695
{
696
  bool legacy_particle_codes = true;
181✔
697
  if (attribute_exists(group_id, "version")) {
181✔
698
    array<int, 2> version;
172✔
699
    read_attribute(group_id, "version", version);
172✔
700
    if (version[0] > VERSION_STATEPOINT[0] ||
172!
701
        (version[0] == VERSION_STATEPOINT[0] && version[1] >= 2)) {
172!
702
      legacy_particle_codes = false;
703
    }
704
  }
705

706
  hid_t banktype = h5banktype(true);
181✔
707

708
  // Open the dataset
709
  hid_t dset = H5Dopen(group_id, "source_bank", H5P_DEFAULT);
181✔
710

711
  // Make sure number of members matches
712
  hid_t dtype = H5Dget_type(dset);
181✔
713
  auto file_member_names = dtype_member_names(dtype);
181✔
714
  auto bank_member_names = dtype_member_names(banktype);
181✔
715
  if (file_member_names != bank_member_names) {
181✔
716
    fatal_error(fmt::format(
9✔
717
      "Source site attributes in file do not match what is "
718
      "expected for this version of OpenMC. File attributes = ({}). Expected "
719
      "attributes = ({})",
720
      file_member_names, bank_member_names));
721
  }
722

723
  hid_t dspace = H5Dget_space(dset);
172✔
724
  hsize_t n_sites;
172✔
725
  H5Sget_simple_extent_dims(dspace, &n_sites, nullptr);
172✔
726

727
  // Make sure vector is big enough in case where we're reading entire source on
728
  // each process
729
  if (!distribute)
172✔
730
    sites.resize(n_sites);
116✔
731

732
  hid_t memspace;
172✔
733
  if (distribute) {
172✔
734
    if (simulation::work_index[mpi::n_procs] > n_sites) {
56!
735
      fatal_error("Number of source sites in source file is less "
×
736
                  "than number of source particles per generation.");
737
    }
738

739
    // Create another data space but for each proc individually
740
    hsize_t n_sites_local = simulation::work_per_rank;
56✔
741
    memspace = H5Screate_simple(1, &n_sites_local, nullptr);
56✔
742

743
    // Select hyperslab for each process
744
    hsize_t offset = simulation::work_index[mpi::rank];
56✔
745
    H5Sselect_hyperslab(
56✔
746
      dspace, H5S_SELECT_SET, &offset, nullptr, &n_sites_local, nullptr);
747
  } else {
748
    memspace = H5S_ALL;
749
  }
750

751
#ifdef PHDF5
752
  // Read data in parallel
753
  hid_t plist = H5Pcreate(H5P_DATASET_XFER);
60✔
754
  H5Pset_dxpl_mpio(plist, H5FD_MPIO_COLLECTIVE);
60✔
755
  H5Dread(dset, banktype, memspace, dspace, plist, sites.data());
60✔
756
  H5Pclose(plist);
60✔
757
#else
758
  H5Dread(dset, banktype, memspace, dspace, H5P_DEFAULT, sites.data());
112✔
759
#endif
760

761
  // Close all ids
762
  H5Sclose(dspace);
172✔
763
  if (distribute)
172✔
764
    H5Sclose(memspace);
56✔
765
  H5Dclose(dset);
172✔
766
  H5Tclose(banktype);
172✔
767

768
  if (legacy_particle_codes) {
172!
769
    for (auto& site : sites) {
×
770
      site.particle = legacy_particle_index_to_type(site.particle.pdg_number());
×
771
    }
772
  }
773
}
172✔
774

775
void write_unstructured_mesh_results()
1,745✔
776
{
777

778
  for (auto& tally : model::tallies) {
7,515✔
779

780
    vector<std::string> tally_scores;
5,770✔
781
    for (auto filter_idx : tally->filters()) {
16,822✔
782
      auto& filter = model::tally_filters[filter_idx];
11,052!
783
      if (filter->type() != FilterType::MESH)
11,052!
784
        continue;
11,046✔
785

786
      // check if the filter uses an unstructured mesh
787
      auto mesh_filter = dynamic_cast<MeshFilter*>(filter.get());
1,260!
788
      auto mesh_idx = mesh_filter->mesh();
1,260!
789
      auto umesh =
1,260✔
790
        dynamic_cast<UnstructuredMesh*>(model::meshes[mesh_idx].get());
1,260!
791

792
      if (!umesh)
1,260✔
793
        continue;
1,234✔
794

795
      if (!umesh->output_)
26!
796
        continue;
×
797

798
      if (umesh->library() == "moab") {
52!
799
        if (mpi::master)
20✔
800
          warning(fmt::format(
20!
801
            "Output for a MOAB mesh (mesh {}) was "
802
            "requested but will not be written. Please use the Python "
803
            "API to generated the desired VTK tetrahedral mesh.",
804
            umesh->id_));
10!
805
        continue;
20✔
806
      }
807

808
      // if this tally has more than one filter, print
809
      // warning and skip writing the mesh
810
      if (tally->filters().size() > 1) {
6!
811
        warning(fmt::format("Skipping unstructured mesh writing for tally "
×
812
                            "{}. More than one filter is present on the tally.",
813
          tally->id_));
×
814
        break;
×
815
      }
816

817
      int n_realizations = tally->n_realizations_;
6✔
818

819
      for (int score_idx = 0; score_idx < tally->scores_.size(); score_idx++) {
12✔
820
        for (int nuc_idx = 0; nuc_idx < tally->nuclides_.size(); nuc_idx++) {
12✔
821
          // combine the score and nuclide into a name for the value
822
          auto score_str = fmt::format("{}_{}", tally->score_name(score_idx),
12!
823
            tally->nuclide_name(nuc_idx));
18!
824
          // add this score to the mesh
825
          // (this is in a separate loop because all variables need to be added
826
          //  to libMesh's equation system before any are initialized, which
827
          //  happens in set_score_data)
828
          umesh->add_score(score_str);
6!
829
        }
6✔
830
      }
831

832
      for (int score_idx = 0; score_idx < tally->scores_.size(); score_idx++) {
12✔
833
        for (int nuc_idx = 0; nuc_idx < tally->nuclides_.size(); nuc_idx++) {
12✔
834
          // combine the score and nuclide into a name for the value
835
          auto score_str = fmt::format("{}_{}", tally->score_name(score_idx),
12!
836
            tally->nuclide_name(nuc_idx));
18!
837

838
          // index for this nuclide and score
839
          int nuc_score_idx = score_idx + nuc_idx * tally->scores_.size();
6!
840

841
          // construct result vectors
842
          vector<double> mean_vec(umesh->n_bins()),
6!
843
            std_dev_vec(umesh->n_bins());
6!
844
          for (int j = 0; j < tally->results_.shape(0); j++) {
99,868!
845
            // get the volume for this bin
846
            double volume = umesh->volume(j);
49,928!
847
            // compute the mean
848
            double mean = tally->results_(j, nuc_score_idx, TallyResult::SUM) /
49,928!
849
                          n_realizations;
49,928✔
850
            mean_vec.at(j) = mean / volume;
49,928!
851

852
            // compute the standard deviation
853
            double sum_sq =
49,928!
854
              tally->results_(j, nuc_score_idx, TallyResult::SUM_SQ);
49,928✔
855
            double std_dev {0.0};
49,928✔
856
            if (n_realizations > 1) {
49,928!
857
              std_dev = sum_sq / n_realizations - mean * mean;
49,928✔
858
              std_dev = std::sqrt(std_dev / (n_realizations - 1));
49,928✔
859
            }
860
            std_dev_vec[j] = std_dev / volume;
49,928✔
861
          }
862
#ifdef OPENMC_MPI
863
          MPI_Bcast(
×
864
            mean_vec.data(), mean_vec.size(), MPI_DOUBLE, 0, mpi::intracomm);
×
865
          MPI_Bcast(std_dev_vec.data(), std_dev_vec.size(), MPI_DOUBLE, 0,
×
866
            mpi::intracomm);
867
#endif
868
          // set the data for this score
869
          umesh->set_score_data(score_str, mean_vec, std_dev_vec);
6!
870
        }
6✔
871
      }
872

873
      // Generate a file name based on the tally id
874
      // and the current batch number
875
      size_t batch_width {std::to_string(settings::n_max_batches).size()};
6!
876
      std::string filename = fmt::format("tally_{0}.{1:0{2}}", tally->id_,
6!
877
        simulation::current_batch, batch_width);
6!
878

879
      // Write the unstructured mesh and data to file
880
      if (mpi::master) {
6!
881
        umesh->write(filename);
6!
882
      }
883

884
      // remove score data added for this mesh write
885
      umesh->remove_scores();
6!
886
    }
6✔
887
  }
5,770✔
888
}
1,745✔
889

890
void write_tally_results_nr(hid_t file_id)
13✔
891
{
892
  // ==========================================================================
893
  // COLLECT AND WRITE GLOBAL TALLIES
894

895
  hid_t tallies_group;
13✔
896
  if (mpi::master) {
13✔
897
    // Write number of realizations
898
    write_dataset(file_id, "n_realizations", simulation::n_realizations);
10✔
899

900
    tallies_group = open_group(file_id, "tallies");
10✔
901
  }
902

903
  // Get global tallies
904
  auto& gt = simulation::global_tallies;
13✔
905

906
#ifdef OPENMC_MPI
907
  // Reduce global tallies
908
  tensor::Tensor<double> gt_reduced({N_GLOBAL_TALLIES, 3});
6✔
909
  MPI_Reduce(gt.data(), gt_reduced.data(), gt.size(), MPI_DOUBLE, MPI_SUM, 0,
6✔
910
    mpi::intracomm);
911

912
  // Transfer values to value on master
913
  if (mpi::master) {
6✔
914
    if (simulation::current_batch == settings::n_max_batches ||
3!
915
        simulation::satisfy_triggers) {
916
      std::copy(gt_reduced.begin(), gt_reduced.end(), gt.begin());
3✔
917
    }
918
  }
919
#endif
920

921
  // Write out global tallies sum and sum_sq
922
  if (mpi::master) {
13✔
923
    write_dataset(file_id, "global_tallies", gt);
10✔
924
  }
925

926
  for (const auto& t : model::tallies) {
26✔
927
    // Skip any tallies that are not active
928
    if (!t->active_)
13!
929
      continue;
×
930
    if (!t->writable_)
13!
UNCOV
931
      continue;
×
932

933
    if (mpi::master && !attribute_exists(file_id, "tallies_present")) {
13!
934
      write_attribute(file_id, "tallies_present", 1);
10✔
935
    }
936

937
    // Copy the SUM and SUM_SQ columns from the tally results into a
938
    // contiguous array for MPI reduction
939
    const int r_start = static_cast<int>(TallyResult::SUM);
13✔
940
    const int r_end = static_cast<int>(TallyResult::SUM_SQ) + 1;
13✔
941
    const size_t r_count = r_end - r_start;
13✔
942
    const size_t ni = t->results_.shape(0);
13!
943
    const size_t nj = t->results_.shape(1);
13!
944
    tensor::Tensor<double> values({ni, nj, r_count});
13✔
945
    for (size_t i = 0; i < ni; i++)
26✔
946
      for (size_t j = 0; j < nj; j++)
26✔
947
        for (size_t r = 0; r < r_count; r++)
39✔
948
          values(i, j, r) = t->results_(i, j, r_start + r);
26✔
949

950
    if (mpi::master) {
13✔
951
      // Open group for tally
952
      std::string groupname {"tally " + std::to_string(t->id_)};
10✔
953
      hid_t tally_group = open_group(tallies_group, groupname.c_str());
10✔
954

955
      // The MPI_IN_PLACE specifier allows the master to copy values into
956
      // a receive buffer without having a temporary variable
957
#ifdef OPENMC_MPI
958
      MPI_Reduce(MPI_IN_PLACE, values.data(), values.size(), MPI_DOUBLE,
3✔
959
        MPI_SUM, 0, mpi::intracomm);
960
#endif
961

962
      // At the end of the simulation, store the reduced results back
963
      // into the tally results array
964
      if (simulation::current_batch == settings::n_max_batches ||
10!
965
          simulation::satisfy_triggers) {
966
        for (size_t i = 0; i < ni; i++)
20✔
967
          for (size_t j = 0; j < nj; j++)
20✔
968
            for (size_t r = 0; r < r_count; r++)
30✔
969
              t->results_(i, j, r_start + r) = values(i, j, r);
20✔
970
      }
971

972
      // Put reduced values into a full-sized copy for writing to HDF5
973
      tensor::Tensor<double> results_copy = tensor::zeros_like(t->results_);
10✔
974
      for (size_t i = 0; i < ni; i++)
20✔
975
        for (size_t j = 0; j < nj; j++)
20✔
976
          for (size_t r = 0; r < r_count; r++)
30✔
977
            results_copy(i, j, r_start + r) = values(i, j, r);
20✔
978

979
      // Write reduced tally results to file
980
      auto shape = results_copy.shape();
10✔
981
      write_tally_results(
10✔
982
        tally_group, shape[0], shape[1], shape[2], results_copy.data());
10✔
983

984
      close_group(tally_group);
10✔
985
    } else {
20✔
986
      // Receive buffer not significant at other processors
987
#ifdef OPENMC_MPI
988
      MPI_Reduce(values.data(), nullptr, values.size(), MPI_DOUBLE, MPI_SUM, 0,
3✔
989
        mpi::intracomm);
990
#endif
991
    }
992
  }
13✔
993

994
  if (mpi::master) {
13✔
995
    if (!object_exists(file_id, "tallies_present")) {
10!
996
      // Indicate that tallies are off
997
      write_dataset(file_id, "tallies_present", 0);
10✔
998
    }
999

1000
    close_group(tallies_group);
10✔
1001
  }
1002
}
13✔
1003

1004
} // namespace openmc
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc