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

openmc-dev / openmc / 23560024828

25 Mar 2026 07:28PM UTC coverage: 80.336% (-1.0%) from 81.326%
23560024828

Pull #3702

github

web-flow
Merge 0f768b2d1 into 6cd39073b
Pull Request #3702: Random Ray Kinetic Simulation Mode

17625 of 25263 branches covered (69.77%)

Branch coverage included in aggregate %.

926 of 1957 new or added lines in 21 files covered. (47.32%)

461 existing lines in 28 files now uncovered.

57995 of 68867 relevant lines covered (84.21%)

50654337.8 hits per line

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

84.85
/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/random_ray_simulation.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,311✔
38
{
39
  simulation::time_statepoint.start();
8,311✔
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,311✔
44
  if (filename) {
8,311✔
45
    filename_ = filename;
730✔
46
  } else {
47
    // Determine width for zero padding
48
    int w = std::to_string(settings::n_max_batches).size();
7,581✔
49

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

55
  // If a file name was specified, ensure it has .h5 file extension
56
  const auto extension = get_file_extension(filename_);
8,311✔
57
  if (extension != "h5") {
8,311!
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,311!
64

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

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

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

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

79
    // Write OpenMC version
80
    write_attribute(file_id, "openmc_version", VERSION);
7,331✔
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());
14,662✔
87

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

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

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

97
    // Write run information
98
    write_dataset(file_id, "energy_mode",
9,061✔
99
      settings::run_CE ? "continuous-energy" : "multi-group");
100
    if (!settings::run_CE) {
7,331✔
101
      write_dataset(file_id, "n_energy_groups", data::mg.num_energy_groups_);
1,730✔
102
      write_dataset(file_id, "n_delay_groups", data::mg.num_delayed_groups_);
1,730✔
103
    }
104
    switch (settings::run_mode) {
7,331!
105
    case RunMode::FIXED_SOURCE:
2,565✔
106
      write_dataset(file_id, "run_mode", "fixed source");
2,565✔
107
      break;
108
    case RunMode::EIGENVALUE:
4,766✔
109
      write_dataset(file_id, "run_mode", "eigenvalue");
4,766✔
110
      break;
111
    default:
112
      break;
113
    }
114
    switch (settings::solver_type) {
7,331!
115
    case SolverType::MONTE_CARLO:
5,931✔
116
      write_dataset(file_id, "solver_type", "monte carlo");
5,931✔
117
      break;
118
    case SolverType::RANDOM_RAY:
1,400✔
119
      write_dataset(file_id, "solver_type", "random ray");
1,400✔
120
      write_random_ray_hdf5(file_id);
1,400✔
121
      break;
122
    default:
123
      break;
124
    }
125
    write_attribute(file_id, "photon_transport", settings::photon_transport);
7,331✔
126
    write_dataset(file_id, "n_particles", settings::n_particles);
7,331✔
127
    write_dataset(file_id, "n_batches", settings::n_batches);
7,331✔
128

129
    write_dataset(file_id, "kinetic_simulation",
7,331✔
130
      settings::kinetic_simulation ? true : false);
131
    if (settings::kinetic_simulation) {
7,331✔
132
      hid_t timestep_group = create_group(file_id, "timestep_data");
840✔
133
      write_dataset(timestep_group, "dt", settings::dt);
840✔
134
      write_dataset(
840✔
135
        timestep_group, "current_timestep", simulation::current_timestep);
136
      write_dataset(timestep_group, "current_time", simulation::current_time);
840✔
137
      close_group(timestep_group);
840✔
138
    }
139

140
    // Write out current batch number
141
    write_dataset(file_id, "current_batch", simulation::current_batch);
7,331✔
142

143
    // Indicate whether source bank is stored in statepoint
144
    write_attribute(file_id, "source_present", write_source_);
7,331✔
145

146
    // Write out information for eigenvalue run
147
    if (settings::run_mode == RunMode::EIGENVALUE)
7,331✔
148
      write_eigenvalue_hdf5(file_id);
4,766✔
149

150
    hid_t tallies_group = create_group(file_id, "tallies");
7,331✔
151

152
    // Write meshes
153
    meshes_to_hdf5(tallies_group);
7,331✔
154

155
    // Write information for derivatives
156
    if (!model::tally_derivs.empty()) {
7,331✔
157
      hid_t derivs_group = create_group(tallies_group, "derivatives");
10✔
158
      for (const auto& deriv : model::tally_derivs) {
60✔
159
        hid_t deriv_group =
50✔
160
          create_group(derivs_group, "derivative " + std::to_string(deriv.id));
50✔
161
        write_dataset(deriv_group, "material", deriv.diff_material);
50✔
162
        if (deriv.variable == DerivativeVariable::DENSITY) {
50✔
163
          write_dataset(deriv_group, "independent variable", "density");
20✔
164
        } else if (deriv.variable == DerivativeVariable::NUCLIDE_DENSITY) {
30✔
165
          write_dataset(deriv_group, "independent variable", "nuclide_density");
20✔
166
          write_dataset(
40✔
167
            deriv_group, "nuclide", data::nuclides[deriv.diff_nuclide]->name_);
20✔
168
        } else if (deriv.variable == DerivativeVariable::TEMPERATURE) {
10!
169
          write_dataset(deriv_group, "independent variable", "temperature");
10✔
170
        } else {
171
          fatal_error("Independent variable for derivative " +
×
172
                      std::to_string(deriv.id) +
×
173
                      " not defined in state_point.cpp");
174
        }
175
        close_group(deriv_group);
50✔
176
      }
177
      close_group(derivs_group);
10✔
178
    }
179

180
    // Write information for filters
181
    hid_t filters_group = create_group(tallies_group, "filters");
7,331✔
182
    write_attribute(filters_group, "n_filters", model::tally_filters.size());
7,331✔
183
    if (!model::tally_filters.empty()) {
7,331✔
184
      // Write filter IDs
185
      vector<int32_t> filter_ids;
4,406✔
186
      filter_ids.reserve(model::tally_filters.size());
4,406✔
187
      for (const auto& filt : model::tally_filters)
16,042✔
188
        filter_ids.push_back(filt->id());
11,636✔
189
      write_attribute(filters_group, "ids", filter_ids);
4,406✔
190

191
      // Write info for each filter
192
      for (const auto& filt : model::tally_filters) {
16,042✔
193
        hid_t filter_group =
11,636✔
194
          create_group(filters_group, "filter " + std::to_string(filt->id()));
11,636✔
195
        filt->to_statepoint(filter_group);
11,636✔
196
        close_group(filter_group);
11,636✔
197
      }
198
    }
4,406✔
199
    close_group(filters_group);
7,331✔
200

201
    // Write information for tallies
202
    write_attribute(tallies_group, "n_tallies", model::tallies.size());
7,331✔
203
    if (!model::tallies.empty()) {
7,331✔
204
      // Write tally IDs
205
      vector<int32_t> tally_ids;
4,896✔
206
      tally_ids.reserve(model::tallies.size());
4,896✔
207
      for (const auto& tally : model::tallies)
26,692✔
208
        tally_ids.push_back(tally->id_);
21,796✔
209
      write_attribute(tallies_group, "ids", tally_ids);
4,896✔
210

211
      // Write all tally information except results
212
      for (const auto& tally : model::tallies) {
26,692✔
213
        hid_t tally_group =
21,796✔
214
          create_group(tallies_group, "tally " + std::to_string(tally->id_));
21,796✔
215

216
        write_dataset(tally_group, "name", tally->name_);
21,796✔
217

218
        if (tally->writable_) {
21,796✔
219
          write_attribute(tally_group, "internal", 0);
20,840✔
220
        } else {
221
          write_attribute(tally_group, "internal", 1);
956✔
222
          close_group(tally_group);
956✔
223
          continue;
956✔
224
        }
225

226
        if (tally->multiply_density()) {
20,840✔
227
          write_attribute(tally_group, "multiply_density", 1);
20,780✔
228
        } else {
229
          write_attribute(tally_group, "multiply_density", 0);
60✔
230
        }
231

232
        if (tally->higher_moments()) {
20,840✔
233
          write_attribute(tally_group, "higher_moments", 1);
10✔
234
        } else {
235
          write_attribute(tally_group, "higher_moments", 0);
20,830✔
236
        }
237

238
        if (tally->estimator_ == TallyEstimator::ANALOG) {
20,840✔
239
          write_dataset(tally_group, "estimator", "analog");
8,270✔
240
        } else if (tally->estimator_ == TallyEstimator::TRACKLENGTH) {
12,570✔
241
          write_dataset(tally_group, "estimator", "tracklength");
11,770✔
242
        } else if (tally->estimator_ == TallyEstimator::COLLISION) {
800!
243
          write_dataset(tally_group, "estimator", "collision");
800✔
244
        }
245

246
        write_dataset(tally_group, "n_realizations", tally->n_realizations_);
20,840✔
247

248
        // Write the ID of each filter attached to this tally
249
        write_dataset(tally_group, "n_filters", tally->filters().size());
20,840✔
250
        if (!tally->filters().empty()) {
20,840✔
251
          vector<int32_t> filter_ids;
19,620✔
252
          filter_ids.reserve(tally->filters().size());
19,620✔
253
          for (auto i_filt : tally->filters())
60,120✔
254
            filter_ids.push_back(model::tally_filters[i_filt]->id());
40,500✔
255
          write_dataset(tally_group, "filters", filter_ids);
19,620✔
256
        }
19,620✔
257

258
        // Write the nuclides this tally scores
259
        vector<std::string> nuclides;
20,840✔
260
        for (auto i_nuclide : tally->nuclides_) {
48,740✔
261
          if (i_nuclide == -1) {
27,900✔
262
            nuclides.push_back("total");
36,880✔
263
          } else {
264
            if (settings::run_CE) {
9,460✔
265
              nuclides.push_back(data::nuclides[i_nuclide]->name_);
9,360✔
266
            } else {
267
              nuclides.push_back(data::mg.nuclides_[i_nuclide].name);
100✔
268
            }
269
          }
270
        }
271
        write_dataset(tally_group, "nuclides", nuclides);
20,840✔
272

273
        if (tally->deriv_ != C_NONE)
20,840✔
274
          write_dataset(
200✔
275
            tally_group, "derivative", model::tally_derivs[tally->deriv_].id);
200✔
276

277
        // Write the tally score bins
278
        vector<std::string> scores;
20,840✔
279
        for (auto sc : tally->scores_)
52,500✔
280
          scores.push_back(reaction_name(sc));
63,320✔
281
        write_dataset(tally_group, "n_score_bins", scores.size());
20,840✔
282
        write_dataset(tally_group, "score_bins", scores);
20,840✔
283

284
        close_group(tally_group);
20,840✔
285
      }
20,840✔
286
    }
4,896✔
287

288
    if (settings::reduce_tallies) {
7,331✔
289
      // Write global tallies
290
      write_dataset(file_id, "global_tallies", simulation::global_tallies);
7,321✔
291

292
      // Write tallies
293
      if (model::active_tallies.size() > 0) {
7,321✔
294
        // Indicate that tallies are on
295
        write_attribute(file_id, "tallies_present", 1);
4,686✔
296

297
        // Write all tally results
298
        for (const auto& tally : model::tallies) {
26,272✔
299
          if (!tally->writable_)
21,586✔
300
            continue;
756✔
301

302
          // Write results for each bin
303
          std::string name = "tally " + std::to_string(tally->id_);
20,830✔
304
          hid_t tally_group = open_group(tallies_group, name.c_str());
20,830✔
305
          auto& results = tally->results_;
20,830!
306
          write_tally_results(tally_group, results.shape(0), results.shape(1),
62,490!
307
            results.shape(2), results.data());
41,660!
308
          close_group(tally_group);
20,830✔
309
        }
20,830✔
310
      } else {
311
        // Indicate tallies are off
312
        write_attribute(file_id, "tallies_present", 0);
2,635✔
313
      }
314
    }
315

316
    close_group(tallies_group);
7,331✔
317
  }
318

319
  // Check for the no-tally-reduction method
320
  if (!settings::reduce_tallies) {
8,311✔
321
    // If using the no-tally-reduction method, we need to collect tally
322
    // results before writing them to the state point file.
323
    write_tally_results_nr(file_id);
13✔
324

325
  } else if (mpi::master) {
8,298✔
326
    // Write number of global realizations
327
    write_dataset(file_id, "n_realizations", simulation::n_realizations);
7,321✔
328
  }
329

330
  if (mpi::master) {
8,311✔
331
    // Write out the runtime metrics.
332
    using namespace simulation;
7,331✔
333
    hid_t runtime_group = create_group(file_id, "runtime");
7,331✔
334
    write_dataset(
7,331✔
335
      runtime_group, "total initialization", time_initialize.elapsed());
336
    write_dataset(
7,331✔
337
      runtime_group, "reading cross sections", time_read_xs.elapsed());
338
    write_dataset(runtime_group, "simulation",
7,331✔
339
      time_inactive.elapsed() + time_active.elapsed());
7,331✔
340
    write_dataset(runtime_group, "transport", time_transport.elapsed());
7,331✔
341
    if (settings::run_mode == RunMode::EIGENVALUE) {
7,331✔
342
      write_dataset(runtime_group, "inactive batches", time_inactive.elapsed());
4,766✔
343
    }
344
    write_dataset(runtime_group, "active batches", time_active.elapsed());
7,331✔
345
    if (settings::solver_type == SolverType::RANDOM_RAY) {
7,331✔
346
      write_dataset(runtime_group, "source_update", time_update_src.elapsed());
1,400✔
347
      if (settings::kinetic_simulation) {
1,400✔
348
        write_dataset(
840✔
349
          runtime_group, "precursor_update", time_compute_precursors.elapsed());
350
      }
351
    }
352
    if (settings::run_mode == RunMode::EIGENVALUE) {
7,331✔
353
      write_dataset(
4,766✔
354
        runtime_group, "synchronizing fission bank", time_bank.elapsed());
355
      write_dataset(
4,766✔
356
        runtime_group, "sampling source sites", time_bank_sample.elapsed());
357
      write_dataset(
4,766✔
358
        runtime_group, "SEND-RECV source sites", time_bank_sendrecv.elapsed());
359
    }
360
    write_dataset(
7,331✔
361
      runtime_group, "accumulating tallies", time_tallies.elapsed());
362
    write_dataset(runtime_group, "total", time_total.elapsed());
7,331✔
363
    write_dataset(
7,331✔
364
      runtime_group, "writing statepoints", time_statepoint.elapsed());
365
    close_group(runtime_group);
7,331✔
366

367
    file_close(file_id);
7,331✔
368
  }
369

370
#ifdef PHDF5
371
  bool parallel = true;
3,170✔
372
#else
373
  bool parallel = false;
5,141✔
374
#endif
375

376
  // Write the source bank if desired
377
  if (write_source_) {
8,311✔
378
    if (mpi::master || parallel)
3,489!
379
      file_id = file_open(filename_, 'a', true);
3,489✔
380
    write_source_bank(file_id, simulation::source_bank, simulation::work_index);
3,489✔
381
    if (mpi::master || parallel)
3,489!
382
      file_close(file_id);
3,489✔
383
  }
384

385
#if defined(OPENMC_LIBMESH_ENABLED) || defined(OPENMC_DAGMC_ENABLED)
386
  // write unstructured mesh tally files
387
  write_unstructured_mesh_results();
1,811✔
388
#endif
389

390
  simulation::time_statepoint.stop();
8,311✔
391

392
  return 0;
8,311✔
393
}
8,311✔
394

395
void restart_set_keff()
56✔
396
{
397
  if (simulation::restart_batch > settings::n_inactive) {
56!
398
    for (int i = settings::n_inactive; i < simulation::restart_batch; ++i) {
267✔
399
      simulation::k_sum[0] += simulation::k_generation[i];
211✔
400
      simulation::k_sum[1] += std::pow(simulation::k_generation[i], 2);
211✔
401
    }
402
    int n = settings::gen_per_batch * simulation::n_realizations;
56✔
403
    simulation::keff = simulation::k_sum[0] / n;
56✔
404
  } else {
405
    simulation::keff = simulation::k_generation.back();
×
406
  }
407
}
56✔
408

409
void load_state_point()
56✔
410
{
411
  write_message(
56✔
412
    fmt::format("Loading state point {}...", settings::path_statepoint_c), 5);
56✔
413
  openmc_statepoint_load(settings::path_statepoint.c_str());
56✔
414
}
56✔
415

416
void statepoint_version_check(hid_t file_id)
56✔
417
{
418
  // Read revision number for state point file and make sure it matches with
419
  // current version
420
  array<int, 2> version_array;
56✔
421
  read_attribute(file_id, "version", version_array);
56✔
422
  if (version_array != VERSION_STATEPOINT) {
56!
423
    fatal_error(
×
424
      "State point version does not match current version in OpenMC.");
425
  }
426
}
56✔
427

428
extern "C" int openmc_statepoint_load(const char* filename)
56✔
429
{
430
  // Open file for reading
431
  hid_t file_id = file_open(filename, 'r', true);
56✔
432

433
  // Read filetype
434
  std::string word;
56✔
435
  read_attribute(file_id, "filetype", word);
56✔
436
  if (word != "statepoint") {
56!
437
    fatal_error("OpenMC tried to restart from a non-statepoint file.");
×
438
  }
439

440
  statepoint_version_check(file_id);
56✔
441

442
  // Read and overwrite random number seed
443
  int64_t seed;
56✔
444
  read_dataset(file_id, "seed", seed);
56✔
445
  openmc_set_seed(seed);
56✔
446

447
  // Read and overwrite random number stride
448
  uint64_t stride;
56✔
449
  read_dataset(file_id, "stride", stride);
56✔
450
  openmc_set_stride(stride);
56✔
451

452
  // It is not impossible for a state point to be generated from a CE run but
453
  // to be loaded in to an MG run (or vice versa), check to prevent that.
454
  read_dataset(file_id, "energy_mode", word);
56✔
455
  if (word == "multi-group" && settings::run_CE) {
56!
456
    fatal_error("State point file is from multigroup run but current run is "
×
457
                "continous energy.");
458
  } else if (word == "continuous-energy" && !settings::run_CE) {
56!
459
    fatal_error("State point file is from continuous-energy run but current "
×
460
                "run is multigroup!");
461
  }
462

463
  // Read and overwrite run information except number of batches
464
  read_dataset(file_id, "run_mode", word);
56✔
465
  if (word == "fixed source") {
56!
466
    settings::run_mode = RunMode::FIXED_SOURCE;
×
467
  } else if (word == "eigenvalue") {
56!
468
    settings::run_mode = RunMode::EIGENVALUE;
56✔
469
  }
470
  read_attribute(file_id, "photon_transport", settings::photon_transport);
56✔
471
  read_dataset(file_id, "n_particles", settings::n_particles);
56✔
472
  int temp;
56✔
473
  read_dataset(file_id, "n_batches", temp);
56✔
474

475
  // Take maximum of statepoint n_batches and input n_batches
476
  settings::n_batches = std::max(settings::n_batches, temp);
56✔
477

478
  // Read batch number to restart at
479
  read_dataset(file_id, "current_batch", simulation::restart_batch);
56✔
480

481
  if (settings::restart_run &&
56!
482
      simulation::restart_batch >= settings::n_max_batches) {
56✔
483
    warning(fmt::format(
20✔
484
      "The number of batches specified for simulation ({}) is smaller "
485
      "than or equal to the number of batches in the restart statepoint file "
486
      "({})",
487
      settings::n_max_batches, simulation::restart_batch));
488
  }
489

490
  // Logical flag for source present in statepoint file
491
  bool source_present;
56✔
492
  read_attribute(file_id, "source_present", source_present);
56✔
493

494
  // Read information specific to eigenvalue run
495
  if (settings::run_mode == RunMode::EIGENVALUE) {
56!
496
    read_dataset(file_id, "n_inactive", temp);
56✔
497
    read_eigenvalue_hdf5(file_id);
56✔
498

499
    // Take maximum of statepoint n_inactive and input n_inactive
500
    settings::n_inactive = std::max(settings::n_inactive, temp);
56!
501

502
    // Check to make sure source bank is present
503
    if (settings::path_sourcepoint == settings::path_statepoint &&
56!
504
        !source_present) {
56!
505
      fatal_error("Source bank must be contained in statepoint restart file");
×
506
    }
507
  }
508

509
  // Read number of realizations for global tallies
510
  read_dataset(file_id, "n_realizations", simulation::n_realizations);
56✔
511

512
  // Set k_sum, keff, and current_batch based on whether restart file is part
513
  // of active cycle or inactive cycle
514
  if (settings::run_mode == RunMode::EIGENVALUE) {
56!
515
    restart_set_keff();
56✔
516
  }
517

518
  // Set current batch number
519
  simulation::current_batch = simulation::restart_batch;
56✔
520

521
  // Read tallies to master. If we are using Parallel HDF5, all processes
522
  // need to be included in the HDF5 calls.
523
#ifdef PHDF5
524
  if (true) {
21✔
525
#else
526
  if (mpi::master) {
35!
527
#endif
528
    // Read global tally data
529
    read_dataset_lowlevel(file_id, "global_tallies", H5T_NATIVE_DOUBLE, H5S_ALL,
56✔
530
      false, simulation::global_tallies.data());
56✔
531

532
    // Check if tally results are present
533
    bool present;
56✔
534
    read_attribute(file_id, "tallies_present", present);
56✔
535

536
    // Read in sum and sum squared
537
    if (present) {
56!
538
      hid_t tallies_group = open_group(file_id, "tallies");
56✔
539

540
      for (auto& tally : model::tallies) {
195✔
541
        // Read sum, sum_sq, and N for each bin
542
        std::string name = "tally " + std::to_string(tally->id_);
139✔
543
        hid_t tally_group = open_group(tallies_group, name.c_str());
139✔
544

545
        int internal = 0;
139✔
546
        if (attribute_exists(tally_group, "internal")) {
139!
547
          read_attribute(tally_group, "internal", internal);
139✔
548
        }
549
        if (internal) {
139!
550
          tally->writable_ = false;
×
551
        } else {
552
          auto& results = tally->results_;
139!
553
          read_tally_results(tally_group, results.shape(0), results.shape(1),
417!
554
            results.shape(2), results.data());
139!
555

556
          read_dataset(tally_group, "n_realizations", tally->n_realizations_);
139✔
557
          close_group(tally_group);
139✔
558
        }
559
      }
139✔
560
      close_group(tallies_group);
56✔
561
    }
562
  }
563

564
  // Read source if in eigenvalue mode
565
  if (settings::run_mode == RunMode::EIGENVALUE) {
56!
566

567
    // Check if source was written out separately
568
    if (!source_present) {
56!
569

570
      // Close statepoint file
571
      file_close(file_id);
×
572

573
      // Write message
574
      write_message(
×
575
        "Loading source file " + settings::path_sourcepoint + "...", 5);
×
576

577
      // Open source file
578
      file_id = file_open(settings::path_sourcepoint.c_str(), 'r', true);
×
579
    }
580

581
    // Read source
582
    read_source_bank(file_id, simulation::source_bank, true);
56✔
583
  }
584

585
  // Close file
586
  file_close(file_id);
56✔
587

588
  return 0;
56✔
589
}
56✔
590

591
hid_t h5banktype(bool memory)
9,224✔
592
{
593
  // Create compound type for position
594
  hid_t postype = H5Tcreate(H5T_COMPOUND, sizeof(struct Position));
9,224✔
595
  H5Tinsert(postype, "x", HOFFSET(Position, x), H5T_NATIVE_DOUBLE);
9,224✔
596
  H5Tinsert(postype, "y", HOFFSET(Position, y), H5T_NATIVE_DOUBLE);
9,224✔
597
  H5Tinsert(postype, "z", HOFFSET(Position, z), H5T_NATIVE_DOUBLE);
9,224✔
598

599
  // Create bank datatype
600
  //
601
  // If you make changes to the compound datatype here, make sure you update:
602
  // - openmc/source.py
603
  // - openmc/statepoint.py
604
  // - docs/source/io_formats/statepoint.rst
605
  // - docs/source/io_formats/source.rst
606
  auto n = sizeof(SourceSite);
9,224✔
607
  if (!memory)
9,224✔
608
    n = 2 * sizeof(struct Position) + 3 * sizeof(double) + 3 * sizeof(int);
4,557✔
609
  hid_t banktype = H5Tcreate(H5T_COMPOUND, n);
9,224✔
610
  H5Tinsert(banktype, "r", HOFFSET(SourceSite, r), postype);
9,224✔
611
  H5Tinsert(banktype, "u", HOFFSET(SourceSite, u), postype);
9,224✔
612
  H5Tinsert(banktype, "E", HOFFSET(SourceSite, E), H5T_NATIVE_DOUBLE);
9,224✔
613
  H5Tinsert(banktype, "time", HOFFSET(SourceSite, time), H5T_NATIVE_DOUBLE);
9,224✔
614
  H5Tinsert(banktype, "wgt", HOFFSET(SourceSite, wgt), H5T_NATIVE_DOUBLE);
9,224✔
615
  H5Tinsert(banktype, "delayed_group", HOFFSET(SourceSite, delayed_group),
9,224✔
616
    H5T_NATIVE_INT);
9,224✔
617
  H5Tinsert(banktype, "surf_id", HOFFSET(SourceSite, surf_id), H5T_NATIVE_INT);
9,224✔
618
  H5Tinsert(
9,224✔
619
    banktype, "particle", HOFFSET(SourceSite, particle), H5T_NATIVE_INT);
9,224✔
620

621
  H5Tclose(postype);
9,224✔
622
  return banktype;
9,224✔
623
}
624

625
void write_source_point(std::string filename, span<SourceSite> source_bank,
1,101✔
626
  const vector<int64_t>& bank_index, bool use_mcpl)
627
{
628
  std::string ext = use_mcpl ? "mcpl" : "h5";
2,169✔
629
  write_message("Creating source file {}.{} with {} particles ...", filename,
1,101✔
630
    ext, source_bank.size(), 5);
1,101✔
631

632
  // Dispatch to appropriate function based on file type
633
  if (use_mcpl) {
1,101✔
634
    filename.append(".mcpl");
33✔
635
    write_mcpl_source_point(filename.c_str(), source_bank, bank_index);
33✔
636
  } else {
637
    filename.append(".h5");
1,068✔
638
    write_h5_source_point(filename.c_str(), source_bank, bank_index);
1,068✔
639
  }
640
}
1,101✔
641

642
void write_h5_source_point(const char* filename, span<SourceSite> source_bank,
1,068✔
643
  const vector<int64_t>& bank_index)
644
{
645
  // When using parallel HDF5, the file is written to collectively by all
646
  // processes. With MPI-only, the file is opened and written by the master
647
  // (note that the call to write_source_bank is by all processes since slave
648
  // processes need to send source bank data to the master.
649
#ifdef PHDF5
650
  bool parallel = true;
345✔
651
#else
652
  bool parallel = false;
723✔
653
#endif
654

655
  if (!filename)
1,068!
656
    fatal_error("write_source_point filename needs a nonempty name.");
×
657

658
  std::string filename_(filename);
1,068✔
659
  const auto extension = get_file_extension(filename_);
1,068✔
660
  if (extension != "h5") {
1,068!
661
    warning("write_source_point was passed a file extension differing "
×
662
            "from .h5, but an hdf5 file will be written.");
663
  }
664

665
  hid_t file_id;
1,068✔
666
  if (mpi::master || parallel) {
1,068!
667
    file_id = file_open(filename_.c_str(), 'w', true);
1,068✔
668
    write_attribute(file_id, "filetype", "source");
1,068✔
669
    write_attribute(file_id, "version", VERSION_STATEPOINT);
1,068✔
670
  }
671

672
  // Get pointer to source bank and write to file
673
  write_source_bank(file_id, source_bank, bank_index);
1,068✔
674

675
  if (mpi::master || parallel)
1,068!
676
    file_close(file_id);
1,068✔
677
}
1,068✔
678

679
void write_source_bank(hid_t group_id, span<SourceSite> source_bank,
4,557✔
680
  const vector<int64_t>& bank_index)
681
{
682
  hid_t membanktype = h5banktype(true);
4,557✔
683
  hid_t filebanktype = h5banktype(false);
4,557✔
684

685
#ifdef OPENMC_MPI
686
  write_bank_dataset("source_bank", group_id, source_bank, bank_index,
1,690✔
687
    membanktype, filebanktype, mpi::source_site);
688
#else
689
  write_bank_dataset("source_bank", group_id, source_bank, bank_index,
2,867✔
690
    membanktype, filebanktype);
691
#endif
692

693
  H5Tclose(membanktype);
4,557✔
694
  H5Tclose(filebanktype);
4,557✔
695
}
4,557✔
696

697
// Determine member names of a compound HDF5 datatype
698
std::string dtype_member_names(hid_t dtype_id)
220✔
699
{
700
  int nmembers = H5Tget_nmembers(dtype_id);
220✔
701
  std::string names;
220✔
702
  for (int i = 0; i < nmembers; i++) {
1,940✔
703
    char* name = H5Tget_member_name(dtype_id, i);
1,720✔
704
    names = names.append(name);
1,720✔
705
    H5free_memory(name);
1,720✔
706
    if (i < nmembers - 1)
1,720✔
707
      names += ", ";
1,720✔
708
  }
709
  return names;
220✔
710
}
×
711

712
void read_source_bank(
110✔
713
  hid_t group_id, vector<SourceSite>& sites, bool distribute)
714
{
715
  bool legacy_particle_codes = true;
110✔
716
  if (attribute_exists(group_id, "version")) {
110✔
717
    array<int, 2> version;
102✔
718
    read_attribute(group_id, "version", version);
102✔
719
    if (version[0] > VERSION_STATEPOINT[0] ||
102!
720
        (version[0] == VERSION_STATEPOINT[0] && version[1] >= 2)) {
102!
721
      legacy_particle_codes = false;
722
    }
723
  }
724

725
  hid_t banktype = h5banktype(true);
110✔
726

727
  // Open the dataset
728
  hid_t dset = H5Dopen(group_id, "source_bank", H5P_DEFAULT);
110✔
729

730
  // Make sure number of members matches
731
  hid_t dtype = H5Dget_type(dset);
110✔
732
  auto file_member_names = dtype_member_names(dtype);
110✔
733
  auto bank_member_names = dtype_member_names(banktype);
110✔
734
  if (file_member_names != bank_member_names) {
110✔
735
    fatal_error(fmt::format(
8✔
736
      "Source site attributes in file do not match what is "
737
      "expected for this version of OpenMC. File attributes = ({}). Expected "
738
      "attributes = ({})",
739
      file_member_names, bank_member_names));
740
  }
741

742
  hid_t dspace = H5Dget_space(dset);
102✔
743
  hsize_t n_sites;
102✔
744
  H5Sget_simple_extent_dims(dspace, &n_sites, nullptr);
102✔
745

746
  // Make sure vector is big enough in case where we're reading entire source on
747
  // each process
748
  if (!distribute)
102✔
749
    sites.resize(n_sites);
46✔
750

751
  hid_t memspace;
102✔
752
  if (distribute) {
102✔
753
    if (simulation::work_index[mpi::n_procs] > n_sites) {
56!
754
      fatal_error("Number of source sites in source file is less "
×
755
                  "than number of source particles per generation.");
756
    }
757

758
    // Create another data space but for each proc individually
759
    hsize_t n_sites_local = simulation::work_per_rank;
56✔
760
    memspace = H5Screate_simple(1, &n_sites_local, nullptr);
56✔
761

762
    // Select hyperslab for each process
763
    hsize_t offset = simulation::work_index[mpi::rank];
56✔
764
    H5Sselect_hyperslab(
56✔
765
      dspace, H5S_SELECT_SET, &offset, nullptr, &n_sites_local, nullptr);
766
  } else {
767
    memspace = H5S_ALL;
768
  }
769

770
#ifdef PHDF5
771
  // Read data in parallel
772
  hid_t plist = H5Pcreate(H5P_DATASET_XFER);
39✔
773
  H5Pset_dxpl_mpio(plist, H5FD_MPIO_COLLECTIVE);
39✔
774
  H5Dread(dset, banktype, memspace, dspace, plist, sites.data());
39✔
775
  H5Pclose(plist);
39✔
776
#else
777
  H5Dread(dset, banktype, memspace, dspace, H5P_DEFAULT, sites.data());
63✔
778
#endif
779

780
  // Close all ids
781
  H5Sclose(dspace);
102✔
782
  if (distribute)
102✔
783
    H5Sclose(memspace);
56✔
784
  H5Dclose(dset);
102✔
785
  H5Tclose(banktype);
102✔
786

787
  if (legacy_particle_codes) {
102!
788
    for (auto& site : sites) {
×
789
      site.particle = legacy_particle_index_to_type(site.particle.pdg_number());
×
790
    }
791
  }
792
}
102✔
793

794
void write_unstructured_mesh_results()
1,811✔
795
{
796

797
  for (auto& tally : model::tallies) {
7,637✔
798

799
    vector<std::string> tally_scores;
5,826✔
800
    for (auto filter_idx : tally->filters()) {
17,116✔
801
      auto& filter = model::tally_filters[filter_idx];
11,290!
802
      if (filter->type() != FilterType::MESH)
11,290!
803
        continue;
11,275✔
804

805
      // check if the filter uses an unstructured mesh
806
      auto mesh_filter = dynamic_cast<MeshFilter*>(filter.get());
1,283!
807
      auto mesh_idx = mesh_filter->mesh();
1,283!
808
      auto umesh =
1,283✔
809
        dynamic_cast<UnstructuredMesh*>(model::meshes[mesh_idx].get());
1,283!
810

811
      if (!umesh)
1,283✔
812
        continue;
1,268✔
813

814
      if (!umesh->output_)
15!
815
        continue;
×
816

817
      if (umesh->library() == "moab") {
30!
UNCOV
818
        if (mpi::master)
×
UNCOV
819
          warning(fmt::format(
×
820
            "Output for a MOAB mesh (mesh {}) was "
821
            "requested but will not be written. Please use the Python "
822
            "API to generated the desired VTK tetrahedral mesh.",
UNCOV
823
            umesh->id_));
×
UNCOV
824
        continue;
×
825
      }
826

827
      // if this tally has more than one filter, print
828
      // warning and skip writing the mesh
829
      if (tally->filters().size() > 1) {
15!
830
        warning(fmt::format("Skipping unstructured mesh writing for tally "
×
831
                            "{}. More than one filter is present on the tally.",
832
          tally->id_));
×
833
        break;
×
834
      }
835

836
      int n_realizations = tally->n_realizations_;
15✔
837

838
      for (int score_idx = 0; score_idx < tally->scores_.size(); score_idx++) {
30✔
839
        for (int nuc_idx = 0; nuc_idx < tally->nuclides_.size(); nuc_idx++) {
30✔
840
          // combine the score and nuclide into a name for the value
841
          auto score_str = fmt::format("{}_{}", tally->score_name(score_idx),
30!
842
            tally->nuclide_name(nuc_idx));
45!
843
          // add this score to the mesh
844
          // (this is in a separate loop because all variables need to be added
845
          //  to libMesh's equation system before any are initialized, which
846
          //  happens in set_score_data)
847
          umesh->add_score(score_str);
15!
848
        }
15✔
849
      }
850

851
      for (int score_idx = 0; score_idx < tally->scores_.size(); score_idx++) {
30✔
852
        for (int nuc_idx = 0; nuc_idx < tally->nuclides_.size(); nuc_idx++) {
30✔
853
          // combine the score and nuclide into a name for the value
854
          auto score_str = fmt::format("{}_{}", tally->score_name(score_idx),
30!
855
            tally->nuclide_name(nuc_idx));
45!
856

857
          // index for this nuclide and score
858
          int nuc_score_idx = score_idx + nuc_idx * tally->scores_.size();
15!
859

860
          // construct result vectors
861
          vector<double> mean_vec(umesh->n_bins()),
15!
862
            std_dev_vec(umesh->n_bins());
15!
863
          for (int j = 0; j < tally->results_.shape(0); j++) {
293,598!
864
            // get the volume for this bin
865
            double volume = umesh->volume(j);
146,784!
866
            // compute the mean
867
            double mean = tally->results_(j, nuc_score_idx, TallyResult::SUM) /
146,784!
868
                          n_realizations;
146,784✔
869
            mean_vec.at(j) = mean / volume;
146,784!
870

871
            // compute the standard deviation
872
            double sum_sq =
146,784!
873
              tally->results_(j, nuc_score_idx, TallyResult::SUM_SQ);
146,784✔
874
            double std_dev {0.0};
146,784✔
875
            if (n_realizations > 1) {
146,784!
876
              std_dev = sum_sq / n_realizations - mean * mean;
146,784✔
877
              std_dev = std::sqrt(std_dev / (n_realizations - 1));
146,784✔
878
            }
879
            std_dev_vec[j] = std_dev / volume;
146,784✔
880
          }
881
#ifdef OPENMC_MPI
882
          MPI_Bcast(
10!
883
            mean_vec.data(), mean_vec.size(), MPI_DOUBLE, 0, mpi::intracomm);
10!
884
          MPI_Bcast(std_dev_vec.data(), std_dev_vec.size(), MPI_DOUBLE, 0,
10!
885
            mpi::intracomm);
886
#endif
887
          // set the data for this score
888
          umesh->set_score_data(score_str, mean_vec, std_dev_vec);
15!
889
        }
15✔
890
      }
891

892
      // Generate a file name based on the tally id
893
      // and the current batch number
894
      size_t batch_width {std::to_string(settings::n_max_batches).size()};
15!
895
      std::string filename = fmt::format("tally_{0}.{1:0{2}}", tally->id_,
15!
896
        simulation::current_batch, batch_width);
15!
897

898
      // Write the unstructured mesh and data to file
899
      umesh->write(filename);
15!
900

901
      // remove score data added for this mesh write
902
      umesh->remove_scores();
15!
903
    }
15✔
904
  }
5,826✔
905
}
1,811✔
906

907
void write_tally_results_nr(hid_t file_id)
13✔
908
{
909
  // ==========================================================================
910
  // COLLECT AND WRITE GLOBAL TALLIES
911

912
  hid_t tallies_group;
13✔
913
  if (mpi::master) {
13✔
914
    // Write number of realizations
915
    write_dataset(file_id, "n_realizations", simulation::n_realizations);
10✔
916

917
    tallies_group = open_group(file_id, "tallies");
10✔
918
  }
919

920
  // Get global tallies
921
  auto& gt = simulation::global_tallies;
13✔
922

923
#ifdef OPENMC_MPI
924
  // Reduce global tallies
925
  tensor::Tensor<double> gt_reduced({N_GLOBAL_TALLIES, 3});
6✔
926
  MPI_Reduce(gt.data(), gt_reduced.data(), gt.size(), MPI_DOUBLE, MPI_SUM, 0,
6✔
927
    mpi::intracomm);
928

929
  // Transfer values to value on master
930
  if (mpi::master) {
6✔
931
    if (simulation::current_batch == settings::n_max_batches ||
3!
932
        simulation::satisfy_triggers) {
933
      std::copy(gt_reduced.begin(), gt_reduced.end(), gt.begin());
3✔
934
    }
935
  }
936
#endif
937

938
  // Write out global tallies sum and sum_sq
939
  if (mpi::master) {
13✔
940
    write_dataset(file_id, "global_tallies", gt);
10✔
941
  }
942

943
  for (const auto& t : model::tallies) {
26✔
944
    // Skip any tallies that are not active
945
    if (!t->active_)
13!
946
      continue;
×
947
    if (!t->writable_)
13!
948
      continue;
×
949

950
    if (mpi::master && !attribute_exists(file_id, "tallies_present")) {
13!
951
      write_attribute(file_id, "tallies_present", 1);
10✔
952
    }
953

954
    // Copy the SUM and SUM_SQ columns from the tally results into a
955
    // contiguous array for MPI reduction
956
    const int r_start = static_cast<int>(TallyResult::SUM);
13✔
957
    const int r_end = static_cast<int>(TallyResult::SUM_SQ) + 1;
13✔
958
    const size_t r_count = r_end - r_start;
13✔
959
    const size_t ni = t->results_.shape(0);
13!
960
    const size_t nj = t->results_.shape(1);
13!
961
    tensor::Tensor<double> values({ni, nj, r_count});
13✔
962
    for (size_t i = 0; i < ni; i++)
26✔
963
      for (size_t j = 0; j < nj; j++)
26✔
964
        for (size_t r = 0; r < r_count; r++)
39✔
965
          values(i, j, r) = t->results_(i, j, r_start + r);
26✔
966

967
    if (mpi::master) {
13✔
968
      // Open group for tally
969
      std::string groupname {"tally " + std::to_string(t->id_)};
10✔
970
      hid_t tally_group = open_group(tallies_group, groupname.c_str());
10✔
971

972
      // The MPI_IN_PLACE specifier allows the master to copy values into
973
      // a receive buffer without having a temporary variable
974
#ifdef OPENMC_MPI
975
      MPI_Reduce(MPI_IN_PLACE, values.data(), values.size(), MPI_DOUBLE,
3✔
976
        MPI_SUM, 0, mpi::intracomm);
977
#endif
978

979
      // At the end of the simulation, store the reduced results back
980
      // into the tally results array
981
      if (simulation::current_batch == settings::n_max_batches ||
10!
982
          simulation::satisfy_triggers) {
983
        for (size_t i = 0; i < ni; i++)
20✔
984
          for (size_t j = 0; j < nj; j++)
20✔
985
            for (size_t r = 0; r < r_count; r++)
30✔
986
              t->results_(i, j, r_start + r) = values(i, j, r);
20✔
987
      }
988

989
      // Put reduced values into a full-sized copy for writing to HDF5
990
      tensor::Tensor<double> results_copy = tensor::zeros_like(t->results_);
10✔
991
      for (size_t i = 0; i < ni; i++)
20✔
992
        for (size_t j = 0; j < nj; j++)
20✔
993
          for (size_t r = 0; r < r_count; r++)
30✔
994
            results_copy(i, j, r_start + r) = values(i, j, r);
20✔
995

996
      // Write reduced tally results to file
997
      auto shape = results_copy.shape();
10✔
998
      write_tally_results(
10✔
999
        tally_group, shape[0], shape[1], shape[2], results_copy.data());
10✔
1000

1001
      close_group(tally_group);
10✔
1002
    } else {
20✔
1003
      // Receive buffer not significant at other processors
1004
#ifdef OPENMC_MPI
1005
      MPI_Reduce(values.data(), nullptr, values.size(), MPI_DOUBLE, MPI_SUM, 0,
3✔
1006
        mpi::intracomm);
1007
#endif
1008
    }
1009
  }
13✔
1010

1011
  if (mpi::master) {
13✔
1012
    if (!object_exists(file_id, "tallies_present")) {
10!
1013
      // Indicate that tallies are off
1014
      write_dataset(file_id, "tallies_present", 0);
10✔
1015
    }
1016

1017
    close_group(tallies_group);
10✔
1018
  }
1019
}
13✔
1020

1021
} // 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