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

openmc-dev / openmc / 28975504630

08 Jul 2026 09:02PM UTC coverage: 81.341% (+0.07%) from 81.267%
28975504630

Pull #3971

github

web-flow
Merge af2ecaf51 into 8b15ee391
Pull Request #3971: Delta tracking

18549 of 26870 branches covered (69.03%)

Branch coverage included in aggregate %.

614 of 661 new or added lines in 20 files covered. (92.89%)

545 existing lines in 20 files now uncovered.

59935 of 69618 relevant lines covered (86.09%)

49705850.0 hits per line

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

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

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

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

61
  // If a file name was specified, ensure it has .h5 file extension
62
  const auto extension = get_file_extension(filename_);
8,936✔
63
  if (extension != "h5") {
8,936!
UNCOV
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;
8,936!
70

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

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

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

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

85
    // Write OpenMC version
86
    write_attribute(file_id, "openmc_version", VERSION);
7,844✔
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());
15,688✔
93

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

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

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

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

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

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

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

131
    hid_t tallies_group = create_group(file_id, "tallies");
7,844✔
132

133
    // Write meshes
134
    meshes_to_hdf5(tallies_group);
7,844✔
135

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

161
    // Write information for filters
162
    hid_t filters_group = create_group(tallies_group, "filters");
7,844✔
163
    write_attribute(filters_group, "n_filters", model::tally_filters.size());
7,844✔
164
    if (!model::tally_filters.empty()) {
7,844✔
165
      // Write filter IDs
166
      vector<int32_t> filter_ids;
5,137✔
167
      filter_ids.reserve(model::tally_filters.size());
5,137✔
168
      for (const auto& filt : model::tally_filters)
17,905✔
169
        filter_ids.push_back(filt->id());
12,768✔
170
      write_attribute(filters_group, "ids", filter_ids);
5,137✔
171

172
      // Write info for each filter
173
      for (const auto& filt : model::tally_filters) {
17,905✔
174
        hid_t filter_group =
12,768✔
175
          create_group(filters_group, "filter " + std::to_string(filt->id()));
12,768✔
176
        filt->to_statepoint(filter_group);
12,768✔
177
        close_group(filter_group);
12,768✔
178
      }
179
    }
5,137✔
180
    close_group(filters_group);
7,844✔
181

182
    // Write information for tallies
183
    write_attribute(tallies_group, "n_tallies", model::tallies.size());
7,844✔
184
    if (!model::tallies.empty()) {
7,844✔
185
      // Write tally IDs
186
      vector<int32_t> tally_ids;
5,709✔
187
      tally_ids.reserve(model::tallies.size());
5,709✔
188
      for (const auto& tally : model::tallies)
29,066✔
189
        tally_ids.push_back(tally->id_);
23,357✔
190
      write_attribute(tallies_group, "ids", tally_ids);
5,709✔
191

192
      // Write all tally information except results
193
      for (const auto& tally : model::tallies) {
29,066✔
194
        hid_t tally_group =
23,357✔
195
          create_group(tallies_group, "tally " + std::to_string(tally->id_));
23,357✔
196

197
        write_dataset(tally_group, "name", tally->name_);
23,357✔
198

199
        if (tally->writable_) {
23,357✔
200
          write_attribute(tally_group, "internal", 0);
22,025✔
201
        } else {
202
          write_attribute(tally_group, "internal", 1);
1,332✔
203
          close_group(tally_group);
1,332✔
204
          continue;
1,332✔
205
        }
206

207
        if (tally->multiply_density()) {
22,025✔
208
          write_attribute(tally_group, "multiply_density", 1);
21,893✔
209
        } else {
210
          write_attribute(tally_group, "multiply_density", 0);
132✔
211
        }
212

213
        if (tally->higher_moments()) {
22,025✔
214
          write_attribute(tally_group, "higher_moments", 1);
11✔
215
        } else {
216
          write_attribute(tally_group, "higher_moments", 0);
22,014✔
217
        }
218

219
        if (tally->estimator_ == TallyEstimator::ANALOG) {
22,025✔
220
          write_dataset(tally_group, "estimator", "analog");
8,126✔
221
        } else if (tally->estimator_ == TallyEstimator::TRACKLENGTH) {
13,899✔
222
          write_dataset(tally_group, "estimator", "tracklength");
12,846✔
223
        } else if (tally->estimator_ == TallyEstimator::COLLISION) {
1,053!
224
          write_dataset(tally_group, "estimator", "collision");
1,053✔
225
        }
226

227
        write_dataset(tally_group, "n_realizations", tally->n_realizations_);
22,025✔
228

229
        // Write the ID of each filter attached to this tally
230
        write_dataset(tally_group, "n_filters", tally->filters().size());
22,025✔
231
        if (!tally->filters().empty()) {
22,025✔
232
          vector<int32_t> filter_ids;
20,793✔
233
          filter_ids.reserve(tally->filters().size());
20,793✔
234
          for (auto i_filt : tally->filters())
63,034✔
235
            filter_ids.push_back(model::tally_filters[i_filt]->id());
42,241✔
236
          write_dataset(tally_group, "filters", filter_ids);
20,793✔
237
        }
20,793✔
238

239
        // Write the nuclides this tally scores
240
        vector<std::string> nuclides;
22,025✔
241
        for (auto i_nuclide : tally->nuclides_) {
52,058✔
242
          if (i_nuclide == -1) {
30,033✔
243
            nuclides.push_back("total");
38,638✔
244
          } else {
245
            if (settings::run_CE) {
10,714✔
246
              nuclides.push_back(data::nuclides[i_nuclide]->name_);
10,604✔
247
            } else {
248
              nuclides.push_back(data::mg.nuclides_[i_nuclide].name);
110✔
249
            }
250
          }
251
        }
252
        write_dataset(tally_group, "nuclides", nuclides);
22,025✔
253

254
        if (tally->deriv_ != C_NONE)
22,025✔
255
          write_dataset(
220✔
256
            tally_group, "derivative", model::tally_derivs[tally->deriv_].id);
220✔
257

258
        // Write the tally score bins
259
        vector<std::string> scores;
22,025✔
260
        for (auto sc : tally->scores_)
53,997✔
261
          scores.push_back(reaction_name(sc));
63,944✔
262
        write_dataset(tally_group, "n_score_bins", scores.size());
22,025✔
263
        write_dataset(tally_group, "score_bins", scores);
22,025✔
264

265
        close_group(tally_group);
22,025✔
266
      }
22,025✔
267
    }
5,709✔
268

269
    if (settings::reduce_tallies) {
7,844✔
270
      // Write global tallies
271
      write_dataset(file_id, "global_tallies", simulation::global_tallies);
7,833✔
272

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

278
        // Write all tally results
279
        for (const auto& tally : model::tallies) {
28,604✔
280
          if (!tally->writable_)
23,126✔
281
            continue;
1,112✔
282

283
          // Write results for each bin
284
          std::string name = "tally " + std::to_string(tally->id_);
22,014✔
285
          hid_t tally_group = open_group(tallies_group, name.c_str());
22,014✔
286
          auto& results = tally->results_;
22,014!
287
          write_tally_results(tally_group, results.shape(0), results.shape(1),
66,042!
288
            results.shape(2), results.data());
44,028!
289
          close_group(tally_group);
22,014✔
290
        }
22,014✔
291
      } else {
292
        // Indicate tallies are off
293
        write_attribute(file_id, "tallies_present", 0);
2,355✔
294
      }
295
    }
296

297
    close_group(tallies_group);
7,844✔
298
  }
299

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

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

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

345
    file_close(file_id);
7,844✔
346
  }
347

348
#ifdef PHDF5
349
  bool parallel = true;
3,954✔
350
#else
351
  bool parallel = false;
4,982✔
352
#endif
353

354
  // Write the source bank if desired
355
  if (write_source_) {
8,936✔
356
    if (mpi::master || parallel)
4,240!
357
      file_id = file_open(filename_, 'a', true);
4,240✔
358
    write_source_bank(file_id, simulation::source_bank, simulation::work_index);
4,240✔
359
    if (mpi::master || parallel)
4,240!
360
      file_close(file_id);
4,240✔
361
  }
362

363
#if defined(OPENMC_LIBMESH_ENABLED) || defined(OPENMC_DAGMC_ENABLED)
364
  // write unstructured mesh tally files
365
  write_unstructured_mesh_results();
2,733✔
366
#endif
367

368
  simulation::time_statepoint.stop();
8,936✔
369

370
  return 0;
8,936✔
371
}
8,936✔
372

373
void restart_set_keff()
63✔
374
{
375
  if (simulation::restart_batch > settings::n_inactive) {
63!
376
    for (int i = settings::n_inactive; i < simulation::restart_batch; ++i) {
300✔
377
      simulation::k_sum[0] += simulation::k_generation[i];
237✔
378
      simulation::k_sum[1] += std::pow(simulation::k_generation[i], 2);
237✔
379
    }
380
    int n = settings::gen_per_batch * simulation::n_realizations;
63✔
381
    simulation::keff = simulation::k_sum[0] / n;
63✔
382
  } else {
UNCOV
383
    simulation::keff = simulation::k_generation.back();
×
384
  }
385
}
63✔
386

387
void load_state_point()
63✔
388
{
389
  write_message(
63✔
390
    fmt::format("Loading state point {}...", settings::path_statepoint_c), 5);
63✔
391
  openmc_statepoint_load(settings::path_statepoint.c_str());
63✔
392
}
63✔
393

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

406
extern "C" int openmc_statepoint_load(const char* filename)
63✔
407
{
408
  // Open file for reading
409
  hid_t file_id = file_open(filename, 'r', true);
63✔
410

411
  // Read filetype
412
  std::string word;
63✔
413
  read_attribute(file_id, "filetype", word);
63✔
414
  if (word != "statepoint") {
63!
UNCOV
415
    fatal_error("OpenMC tried to restart from a non-statepoint file.");
×
416
  }
417

418
  statepoint_version_check(file_id);
63✔
419

420
  // Read and overwrite random number seed
421
  int64_t seed;
63✔
422
  read_dataset(file_id, "seed", seed);
63✔
423
  openmc_set_seed(seed);
63✔
424

425
  // Read and overwrite random number stride
426
  uint64_t stride;
63✔
427
  read_dataset(file_id, "stride", stride);
63✔
428
  openmc_set_stride(stride);
63✔
429

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

441
  // Read and overwrite run information except number of batches
442
  read_dataset(file_id, "run_mode", word);
63✔
443
  if (word == "fixed source") {
63!
UNCOV
444
    settings::run_mode = RunMode::FIXED_SOURCE;
×
445
  } else if (word == "eigenvalue") {
63!
446
    settings::run_mode = RunMode::EIGENVALUE;
63✔
447
  }
448
  read_attribute(file_id, "photon_transport", settings::photon_transport);
63✔
449
  read_dataset(file_id, "n_particles", settings::n_particles);
63✔
450
  int temp;
63✔
451
  read_dataset(file_id, "n_batches", temp);
63✔
452

453
  // Take maximum of statepoint n_batches and input n_batches
454
  settings::n_batches = std::max(settings::n_batches, temp);
63✔
455

456
  // Read batch number to restart at
457
  read_dataset(file_id, "current_batch", simulation::restart_batch);
63✔
458

459
  if (settings::restart_run &&
63!
460
      simulation::restart_batch >= settings::n_max_batches) {
63✔
461
    warning(fmt::format(
22✔
462
      "The number of batches specified for simulation ({}) is smaller "
463
      "than or equal to the number of batches in the restart statepoint file "
464
      "({})",
465
      settings::n_max_batches, simulation::restart_batch));
466
  }
467

468
  // Logical flag for source present in statepoint file
469
  bool source_present;
63✔
470
  read_attribute(file_id, "source_present", source_present);
63✔
471

472
  // Read information specific to eigenvalue run
473
  if (settings::run_mode == RunMode::EIGENVALUE) {
63!
474
    read_dataset(file_id, "n_inactive", temp);
63✔
475
    read_eigenvalue_hdf5(file_id);
63✔
476

477
    // Take maximum of statepoint n_inactive and input n_inactive
478
    settings::n_inactive = std::max(settings::n_inactive, temp);
63!
479

480
    // Check to make sure source bank is present
481
    if (settings::path_sourcepoint == settings::path_statepoint &&
63!
482
        !source_present) {
63!
UNCOV
483
      fatal_error("Source bank must be contained in statepoint restart file");
×
484
    }
485
  }
486

487
  // Read number of realizations for global tallies
488
  read_dataset(file_id, "n_realizations", simulation::n_realizations);
63✔
489

490
  // Set k_sum, keff, and current_batch based on whether restart file is part
491
  // of active cycle or inactive cycle
492
  if (settings::run_mode == RunMode::EIGENVALUE) {
63!
493
    restart_set_keff();
63✔
494
  }
495

496
  // Set current batch number
497
  simulation::current_batch = simulation::restart_batch;
63✔
498

499
  // Read tallies to master. If we are using Parallel HDF5, all processes
500
  // need to be included in the HDF5 calls.
501
#ifdef PHDF5
502
  if (true) {
28✔
503
#else
504
  if (mpi::master) {
35!
505
#endif
506
    // Read global tally data
507
    read_dataset_lowlevel(file_id, "global_tallies", H5T_NATIVE_DOUBLE, H5S_ALL,
63✔
508
      false, simulation::global_tallies.data());
63✔
509

510
    // Check if tally results are present
511
    bool present;
63✔
512
    read_attribute(file_id, "tallies_present", present);
63✔
513

514
    // Read in sum and sum squared
515
    if (present) {
63!
516
      hid_t tallies_group = open_group(file_id, "tallies");
63✔
517

518
      for (auto& tally : model::tallies) {
218✔
519
        // Read sum, sum_sq, and N for each bin
520
        std::string name = "tally " + std::to_string(tally->id_);
155✔
521
        hid_t tally_group = open_group(tallies_group, name.c_str());
155✔
522

523
        int internal = 0;
155✔
524
        if (attribute_exists(tally_group, "internal")) {
155!
525
          read_attribute(tally_group, "internal", internal);
155✔
526
        }
527
        if (internal) {
155!
UNCOV
528
          tally->writable_ = false;
×
529
        } else {
530
          auto& results = tally->results_;
155!
531
          read_tally_results(tally_group, results.shape(0), results.shape(1),
465!
532
            results.shape(2), results.data());
155!
533

534
          read_dataset(tally_group, "n_realizations", tally->n_realizations_);
155✔
535
          close_group(tally_group);
155✔
536
        }
537
      }
155✔
538
      close_group(tallies_group);
63✔
539
    }
540
  }
541

542
  // Read source if in eigenvalue mode
543
  if (settings::run_mode == RunMode::EIGENVALUE) {
63!
544

545
    // Check if source was written out separately
546
    if (!source_present) {
63!
547

548
      // Close statepoint file
549
      file_close(file_id);
×
550

551
      // Write message
UNCOV
552
      write_message(
×
UNCOV
553
        "Loading source file " + settings::path_sourcepoint + "...", 5);
×
554

555
      // Open source file
UNCOV
556
      file_id = file_open(settings::path_sourcepoint.c_str(), 'r', true);
×
557
    }
558

559
    // Read source
560
    read_source_bank(file_id, simulation::source_bank, true);
63✔
561
  }
562

563
  // Close file
564
  file_close(file_id);
63✔
565

566
  return 0;
63✔
567
}
63✔
568

569
hid_t h5banktype(bool memory)
11,357✔
570
{
571
  // Create compound type for position
572
  hid_t postype = H5Tcreate(H5T_COMPOUND, sizeof(struct Position));
11,357✔
573
  H5Tinsert(postype, "x", HOFFSET(Position, x), H5T_NATIVE_DOUBLE);
11,357✔
574
  H5Tinsert(postype, "y", HOFFSET(Position, y), H5T_NATIVE_DOUBLE);
11,357✔
575
  H5Tinsert(postype, "z", HOFFSET(Position, z), H5T_NATIVE_DOUBLE);
11,357✔
576

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

599
  H5Tclose(postype);
11,357✔
600
  return banktype;
11,357✔
601
}
602

603
void write_source_point(std::string filename, span<SourceSite> source_bank,
1,408✔
604
  const vector<int64_t>& bank_index, bool use_mcpl)
605
{
606
  std::string ext = use_mcpl ? "mcpl" : "h5";
2,779✔
607

608
  int total_surf_particles = source_bank.size();
1,408✔
609
#ifdef OPENMC_MPI
610
  int num_particles = source_bank.size();
552✔
611
  MPI_Allreduce(
552✔
612
    &num_particles, &total_surf_particles, 1, MPI_INT, MPI_SUM, mpi::intracomm);
613
#endif
614

615
  write_message("Creating source file {}.{} with {} particles ...", filename,
1,408✔
616
    ext, total_surf_particles, 5);
1,408✔
617

618
  // Dispatch to appropriate function based on file type
619
  if (use_mcpl) {
1,408✔
620
    filename.append(".mcpl");
37✔
621
    write_mcpl_source_point(filename.c_str(), source_bank, bank_index);
37✔
622
  } else {
623
    filename.append(".h5");
1,371✔
624
    write_h5_source_point(filename.c_str(), source_bank, bank_index);
1,371✔
625
  }
626
}
1,408✔
627

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

641
  if (!filename)
1,371!
UNCOV
642
    fatal_error("write_source_point filename needs a nonempty name.");
×
643

644
  std::string filename_(filename);
1,371✔
645
  const auto extension = get_file_extension(filename_);
1,371✔
646
  if (extension != "h5") {
1,371!
UNCOV
647
    warning("write_source_point was passed a file extension differing "
×
648
            "from .h5, but an hdf5 file will be written.");
649
  }
650

651
  hid_t file_id;
1,371✔
652
  if (mpi::master || parallel) {
1,371!
653
    file_id = file_open(filename_.c_str(), 'w', true);
1,371✔
654
    write_attribute(file_id, "filetype", "source");
1,371✔
655
    write_attribute(file_id, "version", VERSION_STATEPOINT);
1,371✔
656
  }
657

658
  // Get pointer to source bank and write to file
659
  write_source_bank(file_id, source_bank, bank_index);
1,371✔
660

661
  if (mpi::master || parallel)
1,371!
662
    file_close(file_id);
1,371✔
663
}
1,371✔
664

665
void write_source_bank(hid_t group_id, span<SourceSite> source_bank,
5,611✔
666
  const vector<int64_t>& bank_index)
667
{
668
  hid_t membanktype = h5banktype(true);
5,611✔
669
  hid_t filebanktype = h5banktype(false);
5,611✔
670

671
#ifdef OPENMC_MPI
672
  write_bank_dataset("source_bank", group_id, source_bank, bank_index,
2,484✔
673
    membanktype, filebanktype, mpi::source_site);
674
#else
675
  write_bank_dataset("source_bank", group_id, source_bank, bank_index,
3,127✔
676
    membanktype, filebanktype);
677
#endif
678

679
  H5Tclose(membanktype);
5,611✔
680
  H5Tclose(filebanktype);
5,611✔
681
}
5,611✔
682

683
// Determine member names of a compound HDF5 datatype
684
std::string dtype_member_names(hid_t dtype_id)
270✔
685
{
686
  int nmembers = H5Tget_nmembers(dtype_id);
270✔
687
  std::string names;
270✔
688
  for (int i = 0; i < nmembers; i++) {
2,385✔
689
    char* name = H5Tget_member_name(dtype_id, i);
2,115✔
690
    names = names.append(name);
2,115✔
691
    H5free_memory(name);
2,115✔
692
    if (i < nmembers - 1)
2,115✔
693
      names += ", ";
2,115✔
694
  }
695
  return names;
270✔
UNCOV
696
}
×
697

698
void read_source_bank(
135✔
699
  hid_t group_id, vector<SourceSite>& sites, bool distribute)
700
{
701
  bool legacy_particle_codes = true;
135✔
702
  if (attribute_exists(group_id, "version")) {
135✔
703
    array<int, 2> version;
126✔
704
    read_attribute(group_id, "version", version);
126✔
705
    if (version[0] > VERSION_STATEPOINT[0] ||
126!
706
        (version[0] == VERSION_STATEPOINT[0] && version[1] >= 2)) {
126!
707
      legacy_particle_codes = false;
708
    }
709
  }
710

711
  hid_t banktype = h5banktype(true);
135✔
712

713
  // Open the dataset
714
  hid_t dset = H5Dopen(group_id, "source_bank", H5P_DEFAULT);
135✔
715

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

728
  hid_t dspace = H5Dget_space(dset);
126✔
729
  hsize_t n_sites;
126✔
730
  H5Sget_simple_extent_dims(dspace, &n_sites, nullptr);
126✔
731

732
  // Make sure vector is big enough in case where we're reading entire source on
733
  // each process
734
  if (!distribute)
126✔
735
    sites.resize(n_sites);
63✔
736

737
  hid_t memspace;
126✔
738
  if (distribute) {
126✔
739
    if (simulation::work_index[mpi::n_procs] > n_sites) {
63!
UNCOV
740
      fatal_error("Number of source sites in source file is less "
×
741
                  "than number of source particles per generation.");
742
    }
743

744
    // Create another data space but for each proc individually
745
    hsize_t n_sites_local = simulation::work_per_rank;
63✔
746
    memspace = H5Screate_simple(1, &n_sites_local, nullptr);
63✔
747

748
    // Select hyperslab for each process
749
    hsize_t offset = simulation::work_index[mpi::rank];
63✔
750
    H5Sselect_hyperslab(
63✔
751
      dspace, H5S_SELECT_SET, &offset, nullptr, &n_sites_local, nullptr);
752
  } else {
753
    memspace = H5S_ALL;
754
  }
755

756
#ifdef PHDF5
757
  // Read data in parallel
758
  hid_t plist = H5Pcreate(H5P_DATASET_XFER);
56✔
759
  H5Pset_dxpl_mpio(plist, H5FD_MPIO_COLLECTIVE);
56✔
760
  H5Dread(dset, banktype, memspace, dspace, plist, sites.data());
56✔
761
  H5Pclose(plist);
56✔
762
#else
763
  H5Dread(dset, banktype, memspace, dspace, H5P_DEFAULT, sites.data());
70✔
764
#endif
765

766
  // Close all ids
767
  H5Sclose(dspace);
126✔
768
  if (distribute)
126✔
769
    H5Sclose(memspace);
63✔
770
  H5Dclose(dset);
126✔
771
  H5Tclose(banktype);
126✔
772

773
  if (legacy_particle_codes) {
126!
UNCOV
774
    for (auto& site : sites) {
×
UNCOV
775
      site.particle = legacy_particle_index_to_type(site.particle.pdg_number());
×
776
    }
777
  }
778
}
126✔
779

780
void write_unstructured_mesh_results()
2,733✔
781
{
782

783
  for (auto& tally : model::tallies) {
12,066✔
784

785
    vector<std::string> tally_scores;
9,333✔
786
    for (auto filter_idx : tally->filters()) {
27,332✔
787
      auto& filter = model::tally_filters[filter_idx];
17,999!
788
      if (filter->type() != FilterType::MESH)
17,999!
789
        continue;
17,982✔
790

791
      // check if the filter uses an unstructured mesh
792
      auto mesh_filter = dynamic_cast<MeshFilter*>(filter.get());
2,078!
793
      auto mesh_idx = mesh_filter->mesh();
2,078!
794
      auto umesh =
2,078✔
795
        dynamic_cast<UnstructuredMesh*>(model::meshes[mesh_idx].get());
2,078!
796

797
      if (!umesh)
2,078✔
798
        continue;
2,041✔
799

800
      if (!umesh->output_)
37!
UNCOV
801
        continue;
×
802

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

813
      // if this tally has more than one filter, print
814
      // warning and skip writing the mesh
815
      if (tally->filters().size() > 1) {
17!
UNCOV
816
        warning(fmt::format("Skipping unstructured mesh writing for tally "
×
817
                            "{}. More than one filter is present on the tally.",
UNCOV
818
          tally->id_));
×
UNCOV
819
        break;
×
820
      }
821

822
      int n_realizations = tally->n_realizations_;
17✔
823

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

837
      for (int score_idx = 0; score_idx < tally->scores_.size(); score_idx++) {
34✔
838
        for (int nuc_idx = 0; nuc_idx < tally->nuclides_.size(); nuc_idx++) {
34✔
839
          // combine the score and nuclide into a name for the value
840
          auto score_str = fmt::format("{}_{}", tally->score_name(score_idx),
34!
841
            tally->nuclide_name(nuc_idx));
51!
842

843
          // index for this nuclide and score
844
          int nuc_score_idx = score_idx + nuc_idx * tally->scores_.size();
17!
845

846
          // construct result vectors
847
          vector<double> mean_vec(umesh->n_bins()),
17!
848
            std_dev_vec(umesh->n_bins());
17!
849
          for (int j = 0; j < tally->results_.shape(0); j++) {
297,602!
850
            // get the volume for this bin
851
            double volume = umesh->volume(j);
148,784!
852
            // compute the mean
853
            double mean = tally->results_(j, nuc_score_idx, TallyResult::SUM) /
148,784!
854
                          n_realizations;
148,784✔
855
            mean_vec.at(j) = mean / volume;
148,784!
856

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

878
      // Generate a file name based on the tally id
879
      // and the current batch number
880
      size_t batch_width {std::to_string(settings::n_max_batches).size()};
17!
881
      std::string filename = fmt::format("tally_{0}.{1:0{2}}", tally->id_,
17!
882
        simulation::current_batch, batch_width);
17!
883

884
      // Write the unstructured mesh and data to file
885
      umesh->write(filename);
17!
886

887
      // remove score data added for this mesh write
888
      umesh->remove_scores();
17!
889
    }
17✔
890
  }
9,333✔
891
}
2,733✔
892

893
void write_tally_results_nr(hid_t file_id)
15✔
894
{
895
  // ==========================================================================
896
  // COLLECT AND WRITE GLOBAL TALLIES
897

898
  hid_t tallies_group;
15✔
899
  if (mpi::master) {
15✔
900
    // Write number of realizations
901
    write_dataset(file_id, "n_realizations", simulation::n_realizations);
11✔
902

903
    tallies_group = open_group(file_id, "tallies");
11✔
904
  }
905

906
  // Get global tallies
907
  auto& gt = simulation::global_tallies;
15✔
908

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

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

924
  // Write out global tallies sum and sum_sq
925
  if (mpi::master) {
15✔
926
    write_dataset(file_id, "global_tallies", gt);
11✔
927
  }
928

929
  for (const auto& t : model::tallies) {
30✔
930
    // Skip any tallies that are not active
931
    if (!t->active_)
15!
UNCOV
932
      continue;
×
933
    if (!t->writable_)
15!
UNCOV
934
      continue;
×
935

936
    if (mpi::master && !attribute_exists(file_id, "tallies_present")) {
15!
937
      write_attribute(file_id, "tallies_present", 1);
11✔
938
    }
939

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

953
    if (mpi::master) {
15✔
954
      // Open group for tally
955
      std::string groupname {"tally " + std::to_string(t->id_)};
11✔
956
      hid_t tally_group = open_group(tallies_group, groupname.c_str());
11✔
957

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

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

975
      // Put reduced values into a full-sized copy for writing to HDF5
976
      tensor::Tensor<double> results_copy = tensor::zeros_like(t->results_);
11✔
977
      for (size_t i = 0; i < ni; i++)
22✔
978
        for (size_t j = 0; j < nj; j++)
22✔
979
          for (size_t r = 0; r < r_count; r++)
33✔
980
            results_copy(i, j, r_start + r) = values(i, j, r);
22✔
981

982
      // Write reduced tally results to file
983
      auto shape = results_copy.shape();
11✔
984
      write_tally_results(
11✔
985
        tally_group, shape[0], shape[1], shape[2], results_copy.data());
11✔
986

987
      close_group(tally_group);
11✔
988
    } else {
22✔
989
      // Receive buffer not significant at other processors
990
#ifdef OPENMC_MPI
991
      MPI_Reduce(values.data(), nullptr, values.size(), MPI_DOUBLE, MPI_SUM, 0,
4✔
992
        mpi::intracomm);
993
#endif
994
    }
995
  }
15✔
996

997
  if (mpi::master) {
15✔
998
    if (!object_exists(file_id, "tallies_present")) {
11!
999
      // Indicate that tallies are off
1000
      write_dataset(file_id, "tallies_present", 0);
11✔
1001
    }
1002

1003
    close_group(tallies_group);
11✔
1004
  }
1005
}
15✔
1006

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