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

openmc-dev / openmc / 21883244533

10 Feb 2026 09:34PM UTC coverage: 81.91% (+0.005%) from 81.905%
21883244533

Pull #3790

github

web-flow
Merge f540c8373 into 3f20a5e22
Pull Request #3790: Subcritical Multiplication Run Mode

17414 of 24353 branches covered (71.51%)

Branch coverage included in aggregate %.

74 of 85 new or added lines in 14 files covered. (87.06%)

56216 of 65538 relevant lines covered (85.78%)

47169664.29 hits per line

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

85.69
/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 "xtensor/xbuilder.hpp" // for empty_like
8
#include "xtensor/xview.hpp"
9
#include <fmt/core.h>
10

11
#include "openmc/bank.h"
12
#include "openmc/bank_io.h"
13
#include "openmc/capi.h"
14
#include "openmc/constants.h"
15
#include "openmc/eigenvalue.h"
16
#include "openmc/error.h"
17
#include "openmc/file_utils.h"
18
#include "openmc/hdf5_interface.h"
19
#include "openmc/mcpl_interface.h"
20
#include "openmc/mesh.h"
21
#include "openmc/message_passing.h"
22
#include "openmc/mgxs_interface.h"
23
#include "openmc/nuclide.h"
24
#include "openmc/output.h"
25
#include "openmc/particle_type.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)
8,155✔
38
{
39
  simulation::time_statepoint.start();
8,155✔
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_;
8,155✔
44
  if (filename) {
8,155✔
45
    filename_ = filename;
799✔
46
  } else {
47
    // Determine width for zero padding
48
    int w = std::to_string(settings::n_max_batches).size();
7,356✔
49

50
    // Set filename for state point
51
    filename_ = fmt::format("{0}statepoint.{1:0{2}}.h5", settings::path_output,
13,339✔
52
      simulation::current_batch, w);
7,356✔
53
  }
54

55
  // If a file name was specified, ensure it has .h5 file extension
56
  const auto extension = get_file_extension(filename_);
8,155✔
57
  if (extension != "h5") {
8,155!
58
    warning("openmc_statepoint_write was passed a file extension differing "
×
59
            "from .h5, but an hdf5 file will be written.");
60
  }
61

62
  // Determine whether or not to write the source bank
63
  bool write_source_ = write_source ? *write_source : true;
8,155!
64

65
  // Write message
66
  write_message("Creating state point " + filename_ + "...", 5);
8,155✔
67

68
  hid_t file_id;
69
  if (mpi::master) {
8,155✔
70
    // Create statepoint file
71
    file_id = file_open(filename_, 'w');
6,945✔
72

73
    // Write file type
74
    write_attribute(file_id, "filetype", "statepoint");
6,945✔
75

76
    // Write revision number for state point file
77
    write_attribute(file_id, "version", VERSION_STATEPOINT);
6,945✔
78

79
    // Write OpenMC version
80
    write_attribute(file_id, "openmc_version", VERSION);
6,945✔
81
#ifdef GIT_SHA1
82
    write_attribute(file_id, "git_sha1", GIT_SHA1);
83
#endif
84

85
    // Write current date and time
86
    write_attribute(file_id, "date_and_time", time_stamp());
6,945✔
87

88
    // Write path to input
89
    write_attribute(file_id, "path", settings::path_input);
6,945✔
90

91
    // Write out random number seed
92
    write_dataset(file_id, "seed", openmc_get_seed());
6,945✔
93

94
    // Write out random number stride
95
    write_dataset(file_id, "stride", openmc_get_stride());
6,945✔
96

97
    // Write run information
98
    write_dataset(file_id, "energy_mode",
6,945✔
99
      settings::run_CE ? "continuous-energy" : "multi-group");
100
    switch (settings::run_mode) {
6,945!
101
    case RunMode::FIXED_SOURCE:
2,677✔
102
      write_dataset(file_id, "run_mode", "fixed source");
2,677✔
103
      break;
2,677✔
104
    case RunMode::EIGENVALUE:
4,257✔
105
      write_dataset(file_id, "run_mode", "eigenvalue");
4,257✔
106
      break;
4,257✔
107
    case RunMode::SUBCRITICAL_MULTIPLICATION:
11✔
108
      write_dataset(file_id, "run_mode", "subcritical multiplication");
11✔
109
      break;
11✔
110
    default:
×
111
      break;
×
112
    }
113
    write_attribute(file_id, "photon_transport", settings::photon_transport);
6,945✔
114
    write_dataset(file_id, "n_particles", settings::n_particles);
6,945✔
115
    write_dataset(file_id, "n_batches", settings::n_batches);
6,945✔
116

117
    // Write out current batch number
118
    write_dataset(file_id, "current_batch", simulation::current_batch);
6,945✔
119

120
    // Indicate whether source bank is stored in statepoint
121
    write_attribute(file_id, "source_present", write_source_);
6,945✔
122

123
    // Write out information for eigenvalue run
124
    if (settings::eigenvalue_like())
6,945✔
125
      write_eigenvalue_hdf5(file_id);
4,268✔
126

127
    hid_t tallies_group = create_group(file_id, "tallies");
6,945✔
128

129
    // Write meshes
130
    meshes_to_hdf5(tallies_group);
6,945✔
131

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

157
    // Write information for filters
158
    hid_t filters_group = create_group(tallies_group, "filters");
6,945✔
159
    write_attribute(filters_group, "n_filters", model::tally_filters.size());
6,945✔
160
    if (!model::tally_filters.empty()) {
6,945✔
161
      // Write filter IDs
162
      vector<int32_t> filter_ids;
4,316✔
163
      filter_ids.reserve(model::tally_filters.size());
4,316✔
164
      for (const auto& filt : model::tally_filters)
15,577✔
165
        filter_ids.push_back(filt->id());
11,261✔
166
      write_attribute(filters_group, "ids", filter_ids);
4,316✔
167

168
      // Write info for each filter
169
      for (const auto& filt : model::tally_filters) {
15,577✔
170
        hid_t filter_group =
171
          create_group(filters_group, "filter " + std::to_string(filt->id()));
11,261✔
172
        filt->to_statepoint(filter_group);
11,261✔
173
        close_group(filter_group);
11,261✔
174
      }
175
    }
4,316✔
176
    close_group(filters_group);
6,945✔
177

178
    // Write information for tallies
179
    write_attribute(tallies_group, "n_tallies", model::tallies.size());
6,945✔
180
    if (!model::tallies.empty()) {
6,945✔
181
      // Write tally IDs
182
      vector<int32_t> tally_ids;
4,856✔
183
      tally_ids.reserve(model::tallies.size());
4,856✔
184
      for (const auto& tally : model::tallies)
26,898✔
185
        tally_ids.push_back(tally->id_);
22,042✔
186
      write_attribute(tallies_group, "ids", tally_ids);
4,856✔
187

188
      // Write all tally information except results
189
      for (const auto& tally : model::tallies) {
26,898✔
190
        hid_t tally_group =
191
          create_group(tallies_group, "tally " + std::to_string(tally->id_));
22,042✔
192

193
        write_dataset(tally_group, "name", tally->name_);
22,042✔
194

195
        if (tally->writable_) {
22,042✔
196
          write_attribute(tally_group, "internal", 0);
21,000✔
197
        } else {
198
          write_attribute(tally_group, "internal", 1);
1,042✔
199
          close_group(tally_group);
1,042✔
200
          continue;
1,042✔
201
        }
202

203
        if (tally->multiply_density()) {
21,000✔
204
          write_attribute(tally_group, "multiply_density", 1);
20,945✔
205
        } else {
206
          write_attribute(tally_group, "multiply_density", 0);
55✔
207
        }
208

209
        if (tally->higher_moments()) {
21,000✔
210
          write_attribute(tally_group, "higher_moments", 1);
11✔
211
        } else {
212
          write_attribute(tally_group, "higher_moments", 0);
20,989✔
213
        }
214

215
        if (tally->estimator_ == TallyEstimator::ANALOG) {
21,000✔
216
          write_dataset(tally_group, "estimator", "analog");
7,913✔
217
        } else if (tally->estimator_ == TallyEstimator::TRACKLENGTH) {
13,087✔
218
          write_dataset(tally_group, "estimator", "tracklength");
12,223✔
219
        } else if (tally->estimator_ == TallyEstimator::COLLISION) {
864!
220
          write_dataset(tally_group, "estimator", "collision");
864✔
221
        }
222

223
        write_dataset(tally_group, "n_realizations", tally->n_realizations_);
21,000✔
224

225
        // Write the ID of each filter attached to this tally
226
        write_dataset(tally_group, "n_filters", tally->filters().size());
21,000✔
227
        if (!tally->filters().empty()) {
21,000✔
228
          vector<int32_t> filter_ids;
19,807✔
229
          filter_ids.reserve(tally->filters().size());
19,807✔
230
          for (auto i_filt : tally->filters())
60,207✔
231
            filter_ids.push_back(model::tally_filters[i_filt]->id());
40,400✔
232
          write_dataset(tally_group, "filters", filter_ids);
19,807✔
233
        }
19,807✔
234

235
        // Write the nuclides this tally scores
236
        vector<std::string> nuclides;
21,000✔
237
        for (auto i_nuclide : tally->nuclides_) {
49,766✔
238
          if (i_nuclide == -1) {
28,766✔
239
            nuclides.push_back("total");
18,371✔
240
          } else {
241
            if (settings::run_CE) {
10,395✔
242
              nuclides.push_back(data::nuclides[i_nuclide]->name_);
10,285✔
243
            } else {
244
              nuclides.push_back(data::mg.nuclides_[i_nuclide].name);
110✔
245
            }
246
          }
247
        }
248
        write_dataset(tally_group, "nuclides", nuclides);
21,000✔
249

250
        if (tally->deriv_ != C_NONE)
21,000✔
251
          write_dataset(
220✔
252
            tally_group, "derivative", model::tally_derivs[tally->deriv_].id);
220✔
253

254
        // Write the tally score bins
255
        vector<std::string> scores;
21,000✔
256
        for (auto sc : tally->scores_)
51,819✔
257
          scores.push_back(reaction_name(sc));
30,819✔
258
        write_dataset(tally_group, "n_score_bins", scores.size());
21,000✔
259
        write_dataset(tally_group, "score_bins", scores);
21,000✔
260

261
        close_group(tally_group);
21,000✔
262
      }
21,000✔
263
    }
4,856✔
264

265
    if (settings::reduce_tallies) {
6,945✔
266
      // Write global tallies
267
      write_dataset(file_id, "global_tallies", simulation::global_tallies);
6,934✔
268

269
      // Write tallies
270
      if (model::active_tallies.size() > 0) {
6,934✔
271
        // Indicate that tallies are on
272
        write_attribute(file_id, "tallies_present", 1);
4,625✔
273

274
        // Write all tally results
275
        for (const auto& tally : model::tallies) {
26,436✔
276
          if (!tally->writable_)
21,811✔
277
            continue;
822✔
278

279
          // Write results for each bin
280
          std::string name = "tally " + std::to_string(tally->id_);
20,989✔
281
          hid_t tally_group = open_group(tallies_group, name.c_str());
20,989✔
282
          auto& results = tally->results_;
20,989✔
283
          write_tally_results(tally_group, results.shape()[0],
20,989✔
284
            results.shape()[1], results.shape()[2], results.data());
20,989✔
285
          close_group(tally_group);
20,989✔
286
        }
20,989✔
287
      } else {
288
        // Indicate tallies are off
289
        write_attribute(file_id, "tallies_present", 0);
2,309✔
290
      }
291
    }
292

293
    close_group(tallies_group);
6,945✔
294
  }
295

296
  // Check for the no-tally-reduction method
297
  if (!settings::reduce_tallies) {
8,155✔
298
    // If using the no-tally-reduction method, we need to collect tally
299
    // results before writing them to the state point file.
300
    write_tally_results_nr(file_id);
16✔
301

302
  } else if (mpi::master) {
8,139✔
303
    // Write number of global realizations
304
    write_dataset(file_id, "n_realizations", simulation::n_realizations);
6,934✔
305
  }
306

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

337
    file_close(file_id);
6,945✔
338
  }
339

340
#ifdef PHDF5
341
  bool parallel = true;
4,369✔
342
#else
343
  bool parallel = false;
3,786✔
344
#endif
345

346
  // Write the source bank if desired
347
  if (write_source_) {
8,155✔
348
    if (mpi::master || parallel)
4,044!
349
      file_id = file_open(filename_, 'a', true);
4,044✔
350
    write_source_bank(file_id, simulation::source_bank, simulation::work_index);
4,044✔
351
    if (mpi::master || parallel)
4,044!
352
      file_close(file_id);
4,044✔
353
  }
354

355
#if defined(OPENMC_LIBMESH_ENABLED) || defined(OPENMC_DAGMC_ENABLED)
356
  // write unstructured mesh tally files
357
  write_unstructured_mesh_results();
2,432✔
358
#endif
359

360
  simulation::time_statepoint.stop();
8,155✔
361

362
  return 0;
8,155✔
363
}
8,155✔
364

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

379
void load_state_point()
65✔
380
{
381
  write_message(
65✔
382
    fmt::format("Loading state point {}...", settings::path_statepoint_c), 5);
118✔
383
  openmc_statepoint_load(settings::path_statepoint.c_str());
65✔
384
}
65✔
385

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

398
extern "C" int openmc_statepoint_load(const char* filename)
65✔
399
{
400
  // Open file for reading
401
  hid_t file_id = file_open(filename, 'r', true);
65✔
402

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

410
  statepoint_version_check(file_id);
65✔
411

412
  // Read and overwrite random number seed
413
  int64_t seed;
414
  read_dataset(file_id, "seed", seed);
65✔
415
  openmc_set_seed(seed);
65✔
416

417
  // Read and overwrite random number stride
418
  uint64_t stride;
419
  read_dataset(file_id, "stride", stride);
65✔
420
  openmc_set_stride(stride);
65✔
421

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

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

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

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

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

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

466
  // Read information specific to eigenvalue run
467
  if (settings::eigenvalue_like()) {
65!
468
    read_dataset(file_id, "n_inactive", temp);
65✔
469
    read_eigenvalue_hdf5(file_id);
65✔
470

471
    // Take maximum of statepoint n_inactive and input n_inactive
472
    settings::n_inactive = std::max(settings::n_inactive, temp);
65✔
473

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

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

484
  // Set k_sum, keff, and current_batch based on whether restart file is part
485
  // of active cycle or inactive cycle
486
  if (settings::eigenvalue_like()) {
65!
487
    restart_set_keff();
65✔
488
  }
489

490
  // Set current batch number
491
  simulation::current_batch = simulation::restart_batch;
65✔
492

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

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

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

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

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

528
          read_dataset(tally_group, "n_realizations", tally->n_realizations_);
158✔
529
          close_group(tally_group);
158✔
530
        }
531
      }
158✔
532
      close_group(tallies_group);
65✔
533
    }
534
  }
535

536
  // Read source if in eigenvalue mode
537
  if (settings::eigenvalue_like()) {
65!
538

539
    // Check if source was written out separately
540
    if (!source_present) {
65!
541

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

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

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

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

557
  // Close file
558
  file_close(file_id);
65✔
559

560
  return 0;
65✔
561
}
65✔
562

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

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

593
  H5Tclose(postype);
10,624✔
594
  return banktype;
10,624✔
595
}
596

597
void write_source_point(std::string filename, span<SourceSite> source_bank,
1,242✔
598
  const vector<int64_t>& bank_index, bool use_mcpl)
599
{
600
  std::string ext = use_mcpl ? "mcpl" : "h5";
1,242✔
601
  write_message("Creating source file {}.{} with {} particles ...", filename,
1,242✔
602
    ext, source_bank.size(), 5);
1,242✔
603

604
  // Dispatch to appropriate function based on file type
605
  if (use_mcpl) {
1,242✔
606
    filename.append(".mcpl");
38✔
607
    write_mcpl_source_point(filename.c_str(), source_bank, bank_index);
38✔
608
  } else {
609
    filename.append(".h5");
1,204✔
610
    write_h5_source_point(filename.c_str(), source_bank, bank_index);
1,204✔
611
  }
612
}
1,242✔
613

614
void write_h5_source_point(const char* filename, span<SourceSite> source_bank,
1,204✔
615
  const vector<int64_t>& bank_index)
616
{
617
  // When using parallel HDF5, the file is written to collectively by all
618
  // processes. With MPI-only, the file is opened and written by the master
619
  // (note that the call to write_source_bank is by all processes since slave
620
  // processes need to send source bank data to the master.
621
#ifdef PHDF5
622
  bool parallel = true;
587✔
623
#else
624
  bool parallel = false;
617✔
625
#endif
626

627
  if (!filename)
1,204!
628
    fatal_error("write_source_point filename needs a nonempty name.");
×
629

630
  std::string filename_(filename);
1,204✔
631
  const auto extension = get_file_extension(filename_);
1,204✔
632
  if (extension != "h5") {
1,204!
633
    warning("write_source_point was passed a file extension differing "
×
634
            "from .h5, but an hdf5 file will be written.");
635
  }
636

637
  hid_t file_id;
638
  if (mpi::master || parallel) {
1,204!
639
    file_id = file_open(filename_.c_str(), 'w', true);
1,204✔
640
    write_attribute(file_id, "filetype", "source");
1,204✔
641
    write_attribute(file_id, "version", VERSION_STATEPOINT);
1,204✔
642
  }
643

644
  // Get pointer to source bank and write to file
645
  write_source_bank(file_id, source_bank, bank_index);
1,204✔
646

647
  if (mpi::master || parallel)
1,204!
648
    file_close(file_id);
1,204✔
649
}
1,204✔
650

651
void write_source_bank(hid_t group_id, span<SourceSite> source_bank,
5,248✔
652
  const vector<int64_t>& bank_index)
653
{
654
  hid_t membanktype = h5banktype(true);
5,248✔
655
  hid_t filebanktype = h5banktype(false);
5,248✔
656

657
#ifdef OPENMC_MPI
658
  write_bank_dataset("source_bank", group_id, source_bank, bank_index,
2,826✔
659
    membanktype, filebanktype, mpi::source_site);
660
#else
661
  write_bank_dataset("source_bank", group_id, source_bank, bank_index,
2,422✔
662
    membanktype, filebanktype);
663
#endif
664

665
  H5Tclose(membanktype);
5,248✔
666
  H5Tclose(filebanktype);
5,248✔
667
}
5,248✔
668

669
// Determine member names of a compound HDF5 datatype
670
std::string dtype_member_names(hid_t dtype_id)
256✔
671
{
672
  int nmembers = H5Tget_nmembers(dtype_id);
256✔
673
  std::string names;
256✔
674
  for (int i = 0; i < nmembers; i++) {
2,259✔
675
    char* name = H5Tget_member_name(dtype_id, i);
2,003✔
676
    names = names.append(name);
2,003✔
677
    H5free_memory(name);
2,003✔
678
    if (i < nmembers - 1)
2,003✔
679
      names += ", ";
1,747✔
680
  }
681
  return names;
256✔
682
}
×
683

684
void read_source_bank(
128✔
685
  hid_t group_id, vector<SourceSite>& sites, bool distribute)
686
{
687
  bool legacy_particle_codes = true;
128✔
688
  if (attribute_exists(group_id, "version")) {
128✔
689
    array<int, 2> version;
690
    read_attribute(group_id, "version", version);
119✔
691
    if (version[0] > VERSION_STATEPOINT[0] ||
238!
692
        (version[0] == VERSION_STATEPOINT[0] && version[1] >= 2)) {
119!
693
      legacy_particle_codes = false;
119✔
694
    }
695
  }
696

697
  hid_t banktype = h5banktype(true);
128✔
698

699
  // Open the dataset
700
  hid_t dset = H5Dopen(group_id, "source_bank", H5P_DEFAULT);
128✔
701

702
  // Make sure number of members matches
703
  hid_t dtype = H5Dget_type(dset);
128✔
704
  auto file_member_names = dtype_member_names(dtype);
128✔
705
  auto bank_member_names = dtype_member_names(banktype);
128✔
706
  if (file_member_names != bank_member_names) {
128✔
707
    fatal_error(fmt::format(
9✔
708
      "Source site attributes in file do not match what is "
709
      "expected for this version of OpenMC. File attributes = ({}). Expected "
710
      "attributes = ({})",
711
      file_member_names, bank_member_names));
712
  }
713

714
  hid_t dspace = H5Dget_space(dset);
119✔
715
  hsize_t n_sites;
716
  H5Sget_simple_extent_dims(dspace, &n_sites, nullptr);
119✔
717

718
  // Make sure vector is big enough in case where we're reading entire source on
719
  // each process
720
  if (!distribute)
119✔
721
    sites.resize(n_sites);
54✔
722

723
  hid_t memspace;
724
  if (distribute) {
119✔
725
    if (simulation::work_index[mpi::n_procs] > n_sites) {
65!
726
      fatal_error("Number of source sites in source file is less "
×
727
                  "than number of source particles per generation.");
728
    }
729

730
    // Create another data space but for each proc individually
731
    hsize_t n_sites_local = simulation::work_per_rank;
65✔
732
    memspace = H5Screate_simple(1, &n_sites_local, nullptr);
65✔
733

734
    // Select hyperslab for each process
735
    hsize_t offset = simulation::work_index[mpi::rank];
65✔
736
    H5Sselect_hyperslab(
65✔
737
      dspace, H5S_SELECT_SET, &offset, nullptr, &n_sites_local, nullptr);
738
  } else {
739
    memspace = H5S_ALL;
54✔
740
  }
741

742
#ifdef PHDF5
743
  // Read data in parallel
744
  hid_t plist = H5Pcreate(H5P_DATASET_XFER);
65✔
745
  H5Pset_dxpl_mpio(plist, H5FD_MPIO_COLLECTIVE);
65✔
746
  H5Dread(dset, banktype, memspace, dspace, plist, sites.data());
65✔
747
  H5Pclose(plist);
65✔
748
#else
749
  H5Dread(dset, banktype, memspace, dspace, H5P_DEFAULT, sites.data());
54✔
750
#endif
751

752
  // Close all ids
753
  H5Sclose(dspace);
119✔
754
  if (distribute)
119✔
755
    H5Sclose(memspace);
65✔
756
  H5Dclose(dset);
119✔
757
  H5Tclose(banktype);
119✔
758

759
  if (legacy_particle_codes) {
119!
760
    for (auto& site : sites) {
×
761
      site.particle = legacy_particle_index_to_type(site.particle.pdg_number());
×
762
    }
763
  }
764
}
119✔
765

766
void write_unstructured_mesh_results()
2,432✔
767
{
768

769
  for (auto& tally : model::tallies) {
11,284✔
770

771
    vector<std::string> tally_scores;
8,852✔
772
    for (auto filter_idx : tally->filters()) {
26,020✔
773
      auto& filter = model::tally_filters[filter_idx];
17,168✔
774
      if (filter->type() != FilterType::MESH)
17,168!
775
        continue;
17,153✔
776

777
      // check if the filter uses an unstructured mesh
778
      auto mesh_filter = dynamic_cast<MeshFilter*>(filter.get());
1,947!
779
      auto mesh_idx = mesh_filter->mesh();
1,947!
780
      auto umesh =
781
        dynamic_cast<UnstructuredMesh*>(model::meshes[mesh_idx].get());
1,947!
782

783
      if (!umesh)
1,947✔
784
        continue;
1,912✔
785

786
      if (!umesh->output_)
35!
787
        continue;
×
788

789
      if (umesh->library() == "moab") {
35!
790
        if (mpi::master)
20✔
791
          warning(fmt::format(
10!
792
            "Output for a MOAB mesh (mesh {}) was "
793
            "requested but will not be written. Please use the Python "
794
            "API to generated the desired VTK tetrahedral mesh.",
795
            umesh->id_));
10✔
796
        continue;
20✔
797
      }
798

799
      // if this tally has more than one filter, print
800
      // warning and skip writing the mesh
801
      if (tally->filters().size() > 1) {
15!
802
        warning(fmt::format("Skipping unstructured mesh writing for tally "
×
803
                            "{}. More than one filter is present on the tally.",
804
          tally->id_));
×
805
        break;
×
806
      }
807

808
      int n_realizations = tally->n_realizations_;
15✔
809

810
      for (int score_idx = 0; score_idx < tally->scores_.size(); score_idx++) {
30✔
811
        for (int nuc_idx = 0; nuc_idx < tally->nuclides_.size(); nuc_idx++) {
30✔
812
          // combine the score and nuclide into a name for the value
813
          auto score_str = fmt::format("{}_{}", tally->score_name(score_idx),
30!
814
            tally->nuclide_name(nuc_idx));
30!
815
          // add this score to the mesh
816
          // (this is in a separate loop because all variables need to be added
817
          //  to libMesh's equation system before any are initialized, which
818
          //  happens in set_score_data)
819
          umesh->add_score(score_str);
15!
820
        }
15✔
821
      }
822

823
      for (int score_idx = 0; score_idx < tally->scores_.size(); score_idx++) {
30✔
824
        for (int nuc_idx = 0; nuc_idx < tally->nuclides_.size(); nuc_idx++) {
30✔
825
          // combine the score and nuclide into a name for the value
826
          auto score_str = fmt::format("{}_{}", tally->score_name(score_idx),
30!
827
            tally->nuclide_name(nuc_idx));
30!
828

829
          // index for this nuclide and score
830
          int nuc_score_idx = score_idx + nuc_idx * tally->scores_.size();
15✔
831

832
          // construct result vectors
833
          vector<double> mean_vec(umesh->n_bins()),
15!
834
            std_dev_vec(umesh->n_bins());
15!
835
          for (int j = 0; j < tally->results_.shape()[0]; j++) {
146,799✔
836
            // get the volume for this bin
837
            double volume = umesh->volume(j);
146,784!
838
            // compute the mean
839
            double mean = tally->results_(j, nuc_score_idx, TallyResult::SUM) /
146,784!
840
                          n_realizations;
146,784✔
841
            mean_vec.at(j) = mean / volume;
146,784!
842

843
            // compute the standard deviation
844
            double sum_sq =
845
              tally->results_(j, nuc_score_idx, TallyResult::SUM_SQ);
146,784!
846
            double std_dev {0.0};
146,784✔
847
            if (n_realizations > 1) {
146,784!
848
              std_dev = sum_sq / n_realizations - mean * mean;
146,784✔
849
              std_dev = std::sqrt(std_dev / (n_realizations - 1));
146,784✔
850
            }
851
            std_dev_vec[j] = std_dev / volume;
146,784✔
852
          }
853
#ifdef OPENMC_MPI
854
          MPI_Bcast(
10!
855
            mean_vec.data(), mean_vec.size(), MPI_DOUBLE, 0, mpi::intracomm);
10✔
856
          MPI_Bcast(std_dev_vec.data(), std_dev_vec.size(), MPI_DOUBLE, 0,
10!
857
            mpi::intracomm);
858
#endif
859
          // set the data for this score
860
          umesh->set_score_data(score_str, mean_vec, std_dev_vec);
15!
861
        }
15✔
862
      }
863

864
      // Generate a file name based on the tally id
865
      // and the current batch number
866
      size_t batch_width {std::to_string(settings::n_max_batches).size()};
15!
867
      std::string filename = fmt::format("tally_{0}.{1:0{2}}", tally->id_,
15✔
868
        simulation::current_batch, batch_width);
×
869

870
      // Write the unstructured mesh and data to file
871
      umesh->write(filename);
15!
872

873
      // remove score data added for this mesh write
874
      umesh->remove_scores();
15!
875
    }
15✔
876
  }
8,852✔
877
}
2,432✔
878

879
void write_tally_results_nr(hid_t file_id)
16✔
880
{
881
  // ==========================================================================
882
  // COLLECT AND WRITE GLOBAL TALLIES
883

884
  hid_t tallies_group;
885
  if (mpi::master) {
16✔
886
    // Write number of realizations
887
    write_dataset(file_id, "n_realizations", simulation::n_realizations);
11✔
888

889
    tallies_group = open_group(file_id, "tallies");
11✔
890
  }
891

892
  // Get global tallies
893
  auto& gt = simulation::global_tallies;
16✔
894

895
#ifdef OPENMC_MPI
896
  // Reduce global tallies
897
  xt::xtensor<double, 2> gt_reduced = xt::empty_like(gt);
10✔
898
  MPI_Reduce(gt.data(), gt_reduced.data(), gt.size(), MPI_DOUBLE, MPI_SUM, 0,
10✔
899
    mpi::intracomm);
900

901
  // Transfer values to value on master
902
  if (mpi::master) {
10✔
903
    if (simulation::current_batch == settings::n_max_batches ||
5!
904
        simulation::satisfy_triggers) {
905
      std::copy(gt_reduced.begin(), gt_reduced.end(), gt.begin());
5✔
906
    }
907
  }
908
#endif
909

910
  // Write out global tallies sum and sum_sq
911
  if (mpi::master) {
16✔
912
    write_dataset(file_id, "global_tallies", gt);
11✔
913
  }
914

915
  for (const auto& t : model::tallies) {
32✔
916
    // Skip any tallies that are not active
917
    if (!t->active_)
16!
918
      continue;
×
919
    if (!t->writable_)
16!
920
      continue;
×
921

922
    if (mpi::master && !attribute_exists(file_id, "tallies_present")) {
16!
923
      write_attribute(file_id, "tallies_present", 1);
11✔
924
    }
925

926
    // Get view of accumulated tally values
927
    auto values_view = xt::view(t->results_, xt::all(), xt::all(),
16✔
928
      xt::range(static_cast<int>(TallyResult::SUM),
16✔
929
        static_cast<int>(TallyResult::SUM_SQ) + 1));
16✔
930

931
    // Make copy of tally values in contiguous array
932
    xt::xtensor<double, 3> values = values_view;
16✔
933

934
    if (mpi::master) {
16✔
935
      // Open group for tally
936
      std::string groupname {"tally " + std::to_string(t->id_)};
11✔
937
      hid_t tally_group = open_group(tallies_group, groupname.c_str());
11✔
938

939
      // The MPI_IN_PLACE specifier allows the master to copy values into
940
      // a receive buffer without having a temporary variable
941
#ifdef OPENMC_MPI
942
      MPI_Reduce(MPI_IN_PLACE, values.data(), values.size(), MPI_DOUBLE,
5✔
943
        MPI_SUM, 0, mpi::intracomm);
944
#endif
945

946
      // At the end of the simulation, store the results back in the
947
      // regular TallyResults array
948
      if (simulation::current_batch == settings::n_max_batches ||
11!
949
          simulation::satisfy_triggers) {
950
        values_view = values;
11✔
951
      }
952

953
      // Put in temporary tally result
954
      xt::xtensor<double, 3> results_copy = xt::zeros_like(t->results_);
11✔
955
      auto copy_view = xt::view(results_copy, xt::all(), xt::all(),
11✔
956
        xt::range(static_cast<int>(TallyResult::SUM),
11✔
957
          static_cast<int>(TallyResult::SUM_SQ) + 1));
11✔
958
      copy_view = values;
11✔
959

960
      // Write reduced tally results to file
961
      auto shape = results_copy.shape();
11✔
962
      write_tally_results(
11✔
963
        tally_group, shape[0], shape[1], shape[2], results_copy.data());
11✔
964

965
      close_group(tally_group);
11✔
966
    } else {
11✔
967
      // Receive buffer not significant at other processors
968
#ifdef OPENMC_MPI
969
      MPI_Reduce(values.data(), nullptr, values.size(), MPI_DOUBLE, MPI_SUM, 0,
5✔
970
        mpi::intracomm);
971
#endif
972
    }
973
  }
16✔
974

975
  if (mpi::master) {
16✔
976
    if (!object_exists(file_id, "tallies_present")) {
11!
977
      // Indicate that tallies are off
978
      write_dataset(file_id, "tallies_present", 0);
11✔
979
    }
980

981
    close_group(tallies_group);
11✔
982
  }
983
}
16✔
984

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

© 2026 Coveralls, Inc