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

openmc-dev / openmc / 13712674732

07 Mar 2025 02:48AM UTC coverage: 85.126% (-0.001%) from 85.127%
13712674732

push

github

web-flow
Random Ray Source Region Mesh Subdivision (Cell-Under-Voxel Geometry) (#3333)

Co-authored-by: Paul Romano <paul.k.romano@gmail.com>

550 of 637 new or added lines in 12 files covered. (86.34%)

7 existing lines in 3 files now uncovered.

51572 of 60583 relevant lines covered (85.13%)

36891325.97 hits per line

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

79.51
/src/settings.cpp
1
#include "openmc/settings.h"
2
#include "openmc/random_ray/flat_source_domain.h"
3

4
#include <cmath>  // for ceil, pow
5
#include <limits> // for numeric_limits
6
#include <string>
7

8
#include <fmt/core.h>
9
#ifdef _OPENMP
10
#include <omp.h>
11
#endif
12

13
#include "openmc/capi.h"
14
#include "openmc/constants.h"
15
#include "openmc/container_util.h"
16
#include "openmc/distribution.h"
17
#include "openmc/distribution_multi.h"
18
#include "openmc/distribution_spatial.h"
19
#include "openmc/eigenvalue.h"
20
#include "openmc/error.h"
21
#include "openmc/file_utils.h"
22
#include "openmc/mcpl_interface.h"
23
#include "openmc/mesh.h"
24
#include "openmc/message_passing.h"
25
#include "openmc/output.h"
26
#include "openmc/plot.h"
27
#include "openmc/random_lcg.h"
28
#include "openmc/random_ray/random_ray.h"
29
#include "openmc/simulation.h"
30
#include "openmc/source.h"
31
#include "openmc/string_utils.h"
32
#include "openmc/tallies/trigger.h"
33
#include "openmc/volume_calc.h"
34
#include "openmc/weight_windows.h"
35
#include "openmc/xml_interface.h"
36

37
namespace openmc {
38

39
//==============================================================================
40
// Global variables
41
//==============================================================================
42

43
namespace settings {
44

45
// Default values for boolean flags
46
bool assume_separate {false};
47
bool check_overlaps {false};
48
bool cmfd_run {false};
49
bool confidence_intervals {false};
50
bool create_delayed_neutrons {true};
51
bool create_fission_neutrons {true};
52
bool delayed_photon_scaling {true};
53
bool entropy_on {false};
54
bool event_based {false};
55
bool legendre_to_tabular {true};
56
bool material_cell_offsets {true};
57
bool output_summary {true};
58
bool output_tallies {true};
59
bool particle_restart_run {false};
60
bool photon_transport {false};
61
bool reduce_tallies {true};
62
bool res_scat_on {false};
63
bool restart_run {false};
64
bool run_CE {true};
65
bool source_latest {false};
66
bool source_separate {false};
67
bool source_write {true};
68
bool source_mcpl_write {false};
69
bool surf_source_write {false};
70
bool surf_mcpl_write {false};
71
bool surf_source_read {false};
72
bool survival_biasing {false};
73
bool survival_normalization {false};
74
bool temperature_multipole {false};
75
bool trigger_on {false};
76
bool trigger_predict {false};
77
bool uniform_source_sampling {false};
78
bool ufs_on {false};
79
bool urr_ptables_on {true};
80
bool use_decay_photons {false};
81
bool weight_windows_on {false};
82
bool weight_window_checkpoint_surface {false};
83
bool weight_window_checkpoint_collision {true};
84
bool write_all_tracks {false};
85
bool write_initial_source {false};
86

87
std::string path_cross_sections;
88
std::string path_input;
89
std::string path_output;
90
std::string path_particle_restart;
91
std::string path_sourcepoint;
92
std::string path_statepoint;
93
const char* path_statepoint_c {path_statepoint.c_str()};
94
std::string weight_windows_file;
95

96
int32_t n_inactive {0};
97
int32_t max_lost_particles {10};
98
double rel_max_lost_particles {1.0e-6};
99
int32_t max_write_lost_particles {-1};
100
int32_t gen_per_batch {1};
101
int64_t n_particles {-1};
102

103
int64_t max_particles_in_flight {100000};
104
int max_particle_events {1000000};
105

106
ElectronTreatment electron_treatment {ElectronTreatment::TTB};
107
array<double, 4> energy_cutoff {0.0, 1000.0, 0.0, 0.0};
108
array<double, 4> time_cutoff {INFTY, INFTY, INFTY, INFTY};
109
int legendre_to_tabular_points {C_NONE};
110
int max_order {0};
111
int n_log_bins {8000};
112
int n_batches;
113
int n_max_batches;
114
int max_history_splits {10'000'000};
115
int max_tracks {1000};
116
ResScatMethod res_scat_method {ResScatMethod::rvs};
117
double res_scat_energy_min {0.01};
118
double res_scat_energy_max {1000.0};
119
vector<std::string> res_scat_nuclides;
120
RunMode run_mode {RunMode::UNSET};
121
SolverType solver_type {SolverType::MONTE_CARLO};
122
std::unordered_set<int> sourcepoint_batch;
123
std::unordered_set<int> statepoint_batch;
124
std::unordered_set<int> source_write_surf_id;
125
int64_t ssw_max_particles;
126
int64_t ssw_max_files;
127
int64_t ssw_cell_id {C_NONE};
128
SSWCellType ssw_cell_type {SSWCellType::None};
129
TemperatureMethod temperature_method {TemperatureMethod::NEAREST};
130
double temperature_tolerance {10.0};
131
double temperature_default {293.6};
132
array<double, 2> temperature_range {0.0, 0.0};
133
int trace_batch;
134
int trace_gen;
135
int64_t trace_particle;
136
vector<array<int, 3>> track_identifiers;
137
int trigger_batch_interval {1};
138
int verbosity {7};
139
double weight_cutoff {0.25};
140
double weight_survive {1.0};
141

142
} // namespace settings
143

144
//==============================================================================
145
// Functions
146
//==============================================================================
147

148
void get_run_parameters(pugi::xml_node node_base)
5,852✔
149
{
150
  using namespace settings;
151
  using namespace pugi;
152

153
  // Check number of particles
154
  if (!check_for_node(node_base, "particles")) {
5,852✔
155
    fatal_error("Need to specify number of particles.");
×
156
  }
157

158
  // Get number of particles if it wasn't specified as a command-line argument
159
  if (n_particles == -1) {
5,852✔
160
    n_particles = std::stoll(get_node_value(node_base, "particles"));
5,852✔
161
  }
162

163
  // Get maximum number of in flight particles for event-based mode
164
  if (check_for_node(node_base, "max_particles_in_flight")) {
5,852✔
165
    max_particles_in_flight =
×
166
      std::stoll(get_node_value(node_base, "max_particles_in_flight"));
×
167
  }
168

169
  // Get maximum number of events allowed per particle
170
  if (check_for_node(node_base, "max_particle_events")) {
5,852✔
171
    max_particle_events =
×
172
      std::stoll(get_node_value(node_base, "max_particle_events"));
×
173
  }
174

175
  // Get number of basic batches
176
  if (check_for_node(node_base, "batches")) {
5,852✔
177
    n_batches = std::stoi(get_node_value(node_base, "batches"));
5,852✔
178
  }
179
  if (!trigger_on)
5,852✔
180
    n_max_batches = n_batches;
5,706✔
181

182
  // Get max number of lost particles
183
  if (check_for_node(node_base, "max_lost_particles")) {
5,852✔
184
    max_lost_particles =
16✔
185
      std::stoi(get_node_value(node_base, "max_lost_particles"));
16✔
186
  }
187

188
  // Get relative number of lost particles
189
  if (check_for_node(node_base, "rel_max_lost_particles")) {
5,852✔
190
    rel_max_lost_particles =
×
191
      std::stod(get_node_value(node_base, "rel_max_lost_particles"));
×
192
  }
193

194
  // Get relative number of lost particles
195
  if (check_for_node(node_base, "max_write_lost_particles")) {
5,852✔
196
    max_write_lost_particles =
16✔
197
      std::stoi(get_node_value(node_base, "max_write_lost_particles"));
16✔
198
  }
199

200
  // Get number of inactive batches
201
  if (run_mode == RunMode::EIGENVALUE ||
5,852✔
202
      solver_type == SolverType::RANDOM_RAY) {
2,175✔
203
    if (check_for_node(node_base, "inactive")) {
4,045✔
204
      n_inactive = std::stoi(get_node_value(node_base, "inactive"));
3,959✔
205
    }
206
    if (check_for_node(node_base, "generations_per_batch")) {
4,045✔
207
      gen_per_batch =
16✔
208
        std::stoi(get_node_value(node_base, "generations_per_batch"));
16✔
209
    }
210

211
    // Preallocate space for keff and entropy by generation
212
    int m = settings::n_max_batches * settings::gen_per_batch;
4,045✔
213
    simulation::k_generation.reserve(m);
4,045✔
214
    simulation::entropy.reserve(m);
4,045✔
215

216
    // Get the trigger information for keff
217
    if (check_for_node(node_base, "keff_trigger")) {
4,045✔
218
      xml_node node_keff_trigger = node_base.child("keff_trigger");
107✔
219

220
      if (check_for_node(node_keff_trigger, "type")) {
107✔
221
        auto temp = get_node_value(node_keff_trigger, "type", true, true);
107✔
222
        if (temp == "std_dev") {
107✔
223
          keff_trigger.metric = TriggerMetric::standard_deviation;
107✔
224
        } else if (temp == "variance") {
×
225
          keff_trigger.metric = TriggerMetric::variance;
×
226
        } else if (temp == "rel_err") {
×
227
          keff_trigger.metric = TriggerMetric::relative_error;
×
228
        } else {
229
          fatal_error("Unrecognized keff trigger type " + temp);
×
230
        }
231
      } else {
107✔
232
        fatal_error("Specify keff trigger type in settings XML");
×
233
      }
234

235
      if (check_for_node(node_keff_trigger, "threshold")) {
107✔
236
        keff_trigger.threshold =
107✔
237
          std::stod(get_node_value(node_keff_trigger, "threshold"));
107✔
238
        if (keff_trigger.threshold <= 0) {
107✔
239
          fatal_error("keff trigger threshold must be positive");
×
240
        }
241
      } else {
242
        fatal_error("Specify keff trigger threshold in settings XML");
×
243
      }
244
    }
245
  }
246

247
  // Random ray variables
248
  if (solver_type == SolverType::RANDOM_RAY) {
5,852✔
249
    xml_node random_ray_node = node_base.child("random_ray");
480✔
250
    if (check_for_node(random_ray_node, "distance_active")) {
480✔
251
      RandomRay::distance_active_ =
480✔
252
        std::stod(get_node_value(random_ray_node, "distance_active"));
480✔
253
      if (RandomRay::distance_active_ <= 0.0) {
480✔
254
        fatal_error("Random ray active distance must be greater than 0");
×
255
      }
256
    } else {
257
      fatal_error("Specify random ray active distance in settings XML");
×
258
    }
259
    if (check_for_node(random_ray_node, "distance_inactive")) {
480✔
260
      RandomRay::distance_inactive_ =
480✔
261
        std::stod(get_node_value(random_ray_node, "distance_inactive"));
480✔
262
      if (RandomRay::distance_inactive_ < 0) {
480✔
263
        fatal_error(
×
264
          "Random ray inactive distance must be greater than or equal to 0");
265
      }
266
    } else {
267
      fatal_error("Specify random ray inactive distance in settings XML");
×
268
    }
269
    if (check_for_node(random_ray_node, "source")) {
480✔
270
      xml_node source_node = random_ray_node.child("source");
480✔
271
      // Get point to list of <source> elements and make sure there is at least
272
      // one
273
      RandomRay::ray_source_ = Source::create(source_node);
480✔
274
    } else {
275
      fatal_error("Specify random ray source in settings XML");
×
276
    }
277
    if (check_for_node(random_ray_node, "volume_estimator")) {
480✔
278
      std::string temp_str =
279
        get_node_value(random_ray_node, "volume_estimator", true, true);
128✔
280
      if (temp_str == "simulation_averaged") {
128✔
281
        FlatSourceDomain::volume_estimator_ =
32✔
282
          RandomRayVolumeEstimator::SIMULATION_AVERAGED;
283
      } else if (temp_str == "naive") {
96✔
284
        FlatSourceDomain::volume_estimator_ = RandomRayVolumeEstimator::NAIVE;
64✔
285
      } else if (temp_str == "hybrid") {
32✔
286
        FlatSourceDomain::volume_estimator_ = RandomRayVolumeEstimator::HYBRID;
32✔
287
      } else {
288
        fatal_error("Unrecognized volume estimator: " + temp_str);
×
289
      }
290
    }
128✔
291
    if (check_for_node(random_ray_node, "source_shape")) {
480✔
292
      std::string temp_str =
293
        get_node_value(random_ray_node, "source_shape", true, true);
240✔
294
      if (temp_str == "flat") {
240✔
295
        RandomRay::source_shape_ = RandomRaySourceShape::FLAT;
64✔
296
      } else if (temp_str == "linear") {
176✔
297
        RandomRay::source_shape_ = RandomRaySourceShape::LINEAR;
128✔
298
      } else if (temp_str == "linear_xy") {
48✔
299
        RandomRay::source_shape_ = RandomRaySourceShape::LINEAR_XY;
48✔
300
      } else {
301
        fatal_error("Unrecognized source shape: " + temp_str);
×
302
      }
303
    }
240✔
304
    if (check_for_node(random_ray_node, "volume_normalized_flux_tallies")) {
480✔
305
      FlatSourceDomain::volume_normalized_flux_tallies_ =
464✔
306
        get_node_value_bool(random_ray_node, "volume_normalized_flux_tallies");
464✔
307
    }
308
    if (check_for_node(random_ray_node, "adjoint")) {
480✔
309
      FlatSourceDomain::adjoint_ =
32✔
310
        get_node_value_bool(random_ray_node, "adjoint");
32✔
311
    }
312
    if (check_for_node(random_ray_node, "sample_method")) {
480✔
313
      std::string temp_str =
314
        get_node_value(random_ray_node, "sample_method", true, true);
16✔
315
      if (temp_str == "prng") {
16✔
316
        RandomRay::sample_method_ = RandomRaySampleMethod::PRNG;
×
317
      } else if (temp_str == "halton") {
16✔
318
        RandomRay::sample_method_ = RandomRaySampleMethod::HALTON;
16✔
319
      } else {
320
        fatal_error("Unrecognized sample method: " + temp_str);
×
321
      }
322
    }
16✔
323
    if (check_for_node(random_ray_node, "source_region_meshes")) {
480✔
324
      pugi::xml_node node_source_region_meshes =
325
        random_ray_node.child("source_region_meshes");
80✔
326
      for (pugi::xml_node node_mesh :
80✔
327
        node_source_region_meshes.children("mesh")) {
304✔
328
        int mesh_id = std::stoi(node_mesh.attribute("id").value());
144✔
329
        for (pugi::xml_node node_domain : node_mesh.children("domain")) {
288✔
330
          int domain_id = std::stoi(node_domain.attribute("id").value());
144✔
331
          std::string domain_type = node_domain.attribute("type").value();
144✔
332
          Source::DomainType type;
333
          if (domain_type == "material") {
144✔
334
            type = Source::DomainType::MATERIAL;
32✔
335
          } else if (domain_type == "cell") {
112✔
336
            type = Source::DomainType::CELL;
32✔
337
          } else if (domain_type == "universe") {
80✔
338
            type = Source::DomainType::UNIVERSE;
80✔
339
          } else {
NEW
340
            throw std::runtime_error("Unknown domain type: " + domain_type);
×
341
          }
342
          FlatSourceDomain::mesh_domain_map_[mesh_id].emplace_back(
144✔
343
            type, domain_id);
344
          RandomRay::mesh_subdivision_enabled_ = true;
144✔
345
        }
144✔
346
      }
347
    }
348
  }
349
}
5,852✔
350

351
void read_settings_xml()
1,324✔
352
{
353
  using namespace settings;
354
  using namespace pugi;
355
  // Check if settings.xml exists
356
  std::string filename = settings::path_input + "settings.xml";
1,324✔
357
  if (!file_exists(filename)) {
1,324✔
358
    if (run_mode != RunMode::PLOTTING) {
22✔
359
      fatal_error("Could not find any XML input files! In order to run OpenMC, "
×
360
                  "you first need a set of input files; at a minimum, this "
361
                  "includes settings.xml, geometry.xml, and materials.xml or a "
362
                  "single model XML file. Please consult the user's guide at "
363
                  "https://docs.openmc.org for further information.");
364
    } else {
365
      // The settings.xml file is optional if we just want to make a plot.
366
      return;
22✔
367
    }
368
  }
369

370
  // Parse settings.xml file
371
  xml_document doc;
1,302✔
372
  auto result = doc.load_file(filename.c_str());
1,302✔
373
  if (!result) {
1,302✔
374
    fatal_error("Error processing settings.xml file.");
×
375
  }
376

377
  // Get root element
378
  xml_node root = doc.document_element();
1,302✔
379

380
  // Verbosity
381
  if (check_for_node(root, "verbosity")) {
1,302✔
382
    verbosity = std::stoi(get_node_value(root, "verbosity"));
187✔
383
  }
384

385
  // To this point, we haven't displayed any output since we didn't know what
386
  // the verbosity is. Now that we checked for it, show the title if necessary
387
  if (mpi::master) {
1,302✔
388
    if (verbosity >= 2)
1,072✔
389
      title();
895✔
390
  }
391

392
  write_message("Reading settings XML file...", 5);
1,302✔
393

394
  read_settings_xml(root);
1,302✔
395
}
1,314✔
396

397
void read_settings_xml(pugi::xml_node root)
6,371✔
398
{
399
  using namespace settings;
400
  using namespace pugi;
401

402
  // Find if a multi-group or continuous-energy simulation is desired
403
  if (check_for_node(root, "energy_mode")) {
6,371✔
404
    std::string temp_str = get_node_value(root, "energy_mode", true, true);
1,014✔
405
    if (temp_str == "mg" || temp_str == "multi-group") {
1,014✔
406
      run_CE = false;
1,014✔
407
    } else if (temp_str == "ce" || temp_str == "continuous-energy") {
×
408
      run_CE = true;
×
409
    }
410
  }
1,014✔
411

412
  // Check for user meshes and allocate
413
  read_meshes(root);
6,371✔
414

415
  // Look for deprecated cross_sections.xml file in settings.xml
416
  if (check_for_node(root, "cross_sections")) {
6,371✔
417
    warning(
×
418
      "Setting cross_sections in settings.xml has been deprecated."
419
      " The cross_sections are now set in materials.xml and the "
420
      "cross_sections input to materials.xml and the OPENMC_CROSS_SECTIONS"
421
      " environment variable will take precendent over setting "
422
      "cross_sections in settings.xml.");
423
    path_cross_sections = get_node_value(root, "cross_sections");
×
424
  }
425

426
  if (!run_CE) {
6,371✔
427
    // Scattering Treatments
428
    if (check_for_node(root, "max_order")) {
1,014✔
429
      max_order = std::stoi(get_node_value(root, "max_order"));
16✔
430
    } else {
431
      // Set to default of largest int - 1, which means to use whatever is
432
      // contained in library. This is largest int - 1 because for legendre
433
      // scattering, a value of 1 is added to the order; adding 1 to the largest
434
      // int gets you the largest negative integer, which is not what we want.
435
      max_order = std::numeric_limits<int>::max() - 1;
998✔
436
    }
437
  }
438

439
  // Check for a trigger node and get trigger information
440
  if (check_for_node(root, "trigger")) {
6,371✔
441
    xml_node node_trigger = root.child("trigger");
162✔
442

443
    // Check if trigger(s) are to be turned on
444
    trigger_on = get_node_value_bool(node_trigger, "active");
162✔
445

446
    if (trigger_on) {
162✔
447
      if (check_for_node(node_trigger, "max_batches")) {
146✔
448
        n_max_batches = std::stoi(get_node_value(node_trigger, "max_batches"));
146✔
449
      } else {
450
        fatal_error("<max_batches> must be specified with triggers");
×
451
      }
452

453
      // Get the batch interval to check triggers
454
      if (!check_for_node(node_trigger, "batch_interval")) {
146✔
455
        trigger_predict = true;
16✔
456
      } else {
457
        trigger_batch_interval =
130✔
458
          std::stoi(get_node_value(node_trigger, "batch_interval"));
130✔
459
        if (trigger_batch_interval <= 0) {
130✔
460
          fatal_error("Trigger batch interval must be greater than zero");
×
461
        }
462
      }
463
    }
464
  }
465

466
  // Check run mode if it hasn't been set from the command line
467
  xml_node node_mode;
6,371✔
468
  if (run_mode == RunMode::UNSET) {
6,371✔
469
    if (check_for_node(root, "run_mode")) {
5,884✔
470
      std::string temp_str = get_node_value(root, "run_mode", true, true);
5,820✔
471
      if (temp_str == "eigenvalue") {
5,820✔
472
        run_mode = RunMode::EIGENVALUE;
3,613✔
473
      } else if (temp_str == "fixed source") {
2,207✔
474
        run_mode = RunMode::FIXED_SOURCE;
2,175✔
475
      } else if (temp_str == "plot") {
32✔
476
        run_mode = RunMode::PLOTTING;
×
477
      } else if (temp_str == "particle restart") {
32✔
478
        run_mode = RunMode::PARTICLE;
×
479
      } else if (temp_str == "volume") {
32✔
480
        run_mode = RunMode::VOLUME;
32✔
481
      } else {
482
        fatal_error("Unrecognized run mode: " + temp_str);
×
483
      }
484

485
      // Assume XML specifies <particles>, <batches>, etc. directly
486
      node_mode = root;
5,820✔
487
    } else {
5,820✔
488
      warning("<run_mode> should be specified.");
64✔
489

490
      // Make sure that either eigenvalue or fixed source was specified
491
      node_mode = root.child("eigenvalue");
64✔
492
      if (node_mode) {
64✔
493
        run_mode = RunMode::EIGENVALUE;
64✔
494
      } else {
495
        node_mode = root.child("fixed_source");
×
496
        if (node_mode) {
×
497
          run_mode = RunMode::FIXED_SOURCE;
×
498
        } else {
499
          fatal_error("<eigenvalue> or <fixed_source> not specified.");
×
500
        }
501
      }
502
    }
503
  }
504

505
  // Check solver type
506
  if (check_for_node(root, "random_ray")) {
6,371✔
507
    solver_type = SolverType::RANDOM_RAY;
480✔
508
    if (run_CE)
480✔
509
      fatal_error("multi-group energy mode must be specified in settings XML "
×
510
                  "when using the random ray solver.");
511
  }
512

513
  if (run_mode == RunMode::EIGENVALUE || run_mode == RunMode::FIXED_SOURCE) {
6,371✔
514
    // Read run parameters
515
    get_run_parameters(node_mode);
5,852✔
516

517
    // Check number of active batches, inactive batches, max lost particles and
518
    // particles
519
    if (n_batches <= n_inactive) {
5,852✔
520
      fatal_error("Number of active batches must be greater than zero.");
×
521
    } else if (n_inactive < 0) {
5,852✔
522
      fatal_error("Number of inactive batches must be non-negative.");
×
523
    } else if (n_particles <= 0) {
5,852✔
524
      fatal_error("Number of particles must be greater than zero.");
×
525
    } else if (max_lost_particles <= 0) {
5,852✔
526
      fatal_error("Number of max lost particles must be greater than zero.");
×
527
    } else if (rel_max_lost_particles <= 0.0 || rel_max_lost_particles >= 1.0) {
5,852✔
528
      fatal_error("Relative max lost particles must be between zero and one.");
×
529
    }
530
  }
531

532
  // Copy plotting random number seed if specified
533
  if (check_for_node(root, "plot_seed")) {
6,371✔
534
    auto seed = std::stoll(get_node_value(root, "plot_seed"));
×
535
    model::plotter_seed = seed;
×
536
  }
537

538
  // Copy random number seed if specified
539
  if (check_for_node(root, "seed")) {
6,371✔
540
    auto seed = std::stoll(get_node_value(root, "seed"));
401✔
541
    openmc_set_seed(seed);
401✔
542
  }
543

544
  // Copy random number stride if specified
545
  if (check_for_node(root, "stride")) {
6,371✔
546
    auto stride = std::stoull(get_node_value(root, "stride"));
16✔
547
    openmc_set_stride(stride);
16✔
548
  }
549

550
  // Check for electron treatment
551
  if (check_for_node(root, "electron_treatment")) {
6,371✔
552
    auto temp_str = get_node_value(root, "electron_treatment", true, true);
48✔
553
    if (temp_str == "led") {
48✔
554
      electron_treatment = ElectronTreatment::LED;
×
555
    } else if (temp_str == "ttb") {
48✔
556
      electron_treatment = ElectronTreatment::TTB;
48✔
557
    } else {
558
      fatal_error("Unrecognized electron treatment: " + temp_str + ".");
×
559
    }
560
  }
48✔
561

562
  // Check for photon transport
563
  if (check_for_node(root, "photon_transport")) {
6,371✔
564
    photon_transport = get_node_value_bool(root, "photon_transport");
188✔
565

566
    if (!run_CE && photon_transport) {
188✔
567
      fatal_error("Photon transport is not currently supported in "
×
568
                  "multigroup mode");
569
    }
570
  }
571

572
  // Number of bins for logarithmic grid
573
  if (check_for_node(root, "log_grid_bins")) {
6,371✔
574
    n_log_bins = std::stoi(get_node_value(root, "log_grid_bins"));
16✔
575
    if (n_log_bins < 1) {
16✔
576
      fatal_error("Number of bins for logarithmic grid must be greater "
×
577
                  "than zero.");
578
    }
579
  }
580

581
  // Number of OpenMP threads
582
  if (check_for_node(root, "threads")) {
6,371✔
583
    if (mpi::master)
×
584
      warning("The <threads> element has been deprecated. Use "
×
585
              "the OMP_NUM_THREADS environment variable to set the number of "
586
              "threads.");
587
  }
588

589
  // ==========================================================================
590
  // EXTERNAL SOURCE
591

592
  // Get point to list of <source> elements and make sure there is at least one
593
  for (pugi::xml_node node : root.children("source")) {
12,487✔
594
    model::external_sources.push_back(Source::create(node));
6,126✔
595
  }
596

597
  // Check if the user has specified to read surface source
598
  if (check_for_node(root, "surf_source_read")) {
6,361✔
599
    surf_source_read = true;
16✔
600
    // Get surface source read node
601
    xml_node node_ssr = root.child("surf_source_read");
16✔
602

603
    std::string path = "surface_source.h5";
16✔
604
    // Check if the user has specified different file for surface source reading
605
    if (check_for_node(node_ssr, "path")) {
16✔
606
      path = get_node_value(node_ssr, "path", false, true);
16✔
607
    }
608
    model::external_sources.push_back(make_unique<FileSource>(path));
16✔
609
  }
16✔
610

611
  // Build probability mass function for sampling external sources
612
  vector<double> source_strengths;
6,361✔
613
  for (auto& s : model::external_sources) {
12,493✔
614
    source_strengths.push_back(s->strength());
6,132✔
615
  }
616
  model::external_sources_probability.assign(source_strengths);
6,361✔
617

618
  // If no source specified, default to isotropic point source at origin with
619
  // Watt spectrum. No default source is needed in random ray mode.
620
  if (model::external_sources.empty() &&
8,011✔
621
      settings::solver_type != SolverType::RANDOM_RAY) {
1,650✔
622
    double T[] {0.0};
1,538✔
623
    double p[] {1.0};
1,538✔
624
    model::external_sources.push_back(make_unique<IndependentSource>(
1,538✔
625
      UPtrSpace {new SpatialPoint({0.0, 0.0, 0.0})},
3,076✔
626
      UPtrAngle {new Isotropic()}, UPtrDist {new Watt(0.988e6, 2.249e-6)},
3,076✔
627
      UPtrDist {new Discrete(T, p, 1)}));
3,076✔
628
  }
629

630
  // Check if we want to write out source
631
  if (check_for_node(root, "write_initial_source")) {
6,361✔
632
    write_initial_source = get_node_value_bool(root, "write_initial_source");
×
633
  }
634

635
  // Survival biasing
636
  if (check_for_node(root, "survival_biasing")) {
6,361✔
637
    survival_biasing = get_node_value_bool(root, "survival_biasing");
175✔
638
  }
639

640
  // Probability tables
641
  if (check_for_node(root, "ptables")) {
6,361✔
642
    urr_ptables_on = get_node_value_bool(root, "ptables");
16✔
643
  }
644

645
  // Cutoffs
646
  if (check_for_node(root, "cutoff")) {
6,361✔
647
    xml_node node_cutoff = root.child("cutoff");
107✔
648
    if (check_for_node(node_cutoff, "weight")) {
107✔
649
      weight_cutoff = std::stod(get_node_value(node_cutoff, "weight"));
16✔
650
    }
651
    if (check_for_node(node_cutoff, "weight_avg")) {
107✔
652
      weight_survive = std::stod(get_node_value(node_cutoff, "weight_avg"));
16✔
653
    }
654
    if (check_for_node(node_cutoff, "survival_normalization")) {
107✔
655
      survival_normalization =
×
656
        get_node_value_bool(node_cutoff, "survival_normalization");
×
657
    }
658
    if (check_for_node(node_cutoff, "energy_neutron")) {
107✔
659
      energy_cutoff[0] =
32✔
660
        std::stod(get_node_value(node_cutoff, "energy_neutron"));
16✔
661
    } else if (check_for_node(node_cutoff, "energy")) {
91✔
662
      warning("The use of an <energy> cutoff is deprecated and should "
×
663
              "be replaced by <energy_neutron>.");
664
      energy_cutoff[0] = std::stod(get_node_value(node_cutoff, "energy"));
×
665
    }
666
    if (check_for_node(node_cutoff, "energy_photon")) {
107✔
667
      energy_cutoff[1] =
118✔
668
        std::stod(get_node_value(node_cutoff, "energy_photon"));
59✔
669
    }
670
    if (check_for_node(node_cutoff, "energy_electron")) {
107✔
671
      energy_cutoff[2] =
×
672
        std::stof(get_node_value(node_cutoff, "energy_electron"));
×
673
    }
674
    if (check_for_node(node_cutoff, "energy_positron")) {
107✔
675
      energy_cutoff[3] =
×
676
        std::stod(get_node_value(node_cutoff, "energy_positron"));
×
677
    }
678
    if (check_for_node(node_cutoff, "time_neutron")) {
107✔
679
      time_cutoff[0] = std::stod(get_node_value(node_cutoff, "time_neutron"));
16✔
680
    }
681
    if (check_for_node(node_cutoff, "time_photon")) {
107✔
682
      time_cutoff[1] = std::stod(get_node_value(node_cutoff, "time_photon"));
×
683
    }
684
    if (check_for_node(node_cutoff, "time_electron")) {
107✔
685
      time_cutoff[2] = std::stod(get_node_value(node_cutoff, "time_electron"));
×
686
    }
687
    if (check_for_node(node_cutoff, "time_positron")) {
107✔
688
      time_cutoff[3] = std::stod(get_node_value(node_cutoff, "time_positron"));
×
689
    }
690
  }
691

692
  // Particle trace
693
  if (check_for_node(root, "trace")) {
6,361✔
694
    auto temp = get_node_array<int64_t>(root, "trace");
16✔
695
    if (temp.size() != 3) {
16✔
696
      fatal_error("Must provide 3 integers for <trace> that specify the "
×
697
                  "batch, generation, and particle number.");
698
    }
699
    trace_batch = temp.at(0);
16✔
700
    trace_gen = temp.at(1);
16✔
701
    trace_particle = temp.at(2);
16✔
702
  }
16✔
703

704
  // Particle tracks
705
  if (check_for_node(root, "track")) {
6,361✔
706
    // Get values and make sure there are three per particle
707
    auto temp = get_node_array<int>(root, "track");
48✔
708
    if (temp.size() % 3 != 0) {
48✔
709
      fatal_error(
×
710
        "Number of integers specified in 'track' is not "
711
        "divisible by 3.  Please provide 3 integers per particle to be "
712
        "tracked.");
713
    }
714

715
    // Reshape into track_identifiers
716
    int n_tracks = temp.size() / 3;
48✔
717
    for (int i = 0; i < n_tracks; ++i) {
192✔
718
      track_identifiers.push_back(
144✔
719
        {temp[3 * i], temp[3 * i + 1], temp[3 * i + 2]});
144✔
720
    }
721
  }
48✔
722

723
  // Shannon entropy
724
  if (solver_type == SolverType::RANDOM_RAY) {
6,361✔
725
    if (check_for_node(root, "entropy_mesh")) {
480✔
726
      fatal_error("Random ray uses FSRs to compute the Shannon entropy. "
×
727
                  "No user-defined entropy mesh is supported.");
728
    }
729
    entropy_on = true;
480✔
730
  } else if (solver_type == SolverType::MONTE_CARLO) {
5,881✔
731
    if (check_for_node(root, "entropy_mesh")) {
5,881✔
732
      int temp = std::stoi(get_node_value(root, "entropy_mesh"));
335✔
733
      if (model::mesh_map.find(temp) == model::mesh_map.end()) {
335✔
734
        fatal_error(fmt::format(
×
735
          "Mesh {} specified for Shannon entropy does not exist.", temp));
736
      }
737

738
      auto* m = dynamic_cast<RegularMesh*>(
335✔
739
        model::meshes[model::mesh_map.at(temp)].get());
335✔
740
      if (!m)
335✔
741
        fatal_error("Only regular meshes can be used as an entropy mesh");
×
742
      simulation::entropy_mesh = m;
335✔
743

744
      // Turn on Shannon entropy calculation
745
      entropy_on = true;
335✔
746

747
    } else if (check_for_node(root, "entropy")) {
5,546✔
748
      fatal_error(
×
749
        "Specifying a Shannon entropy mesh via the <entropy> element "
750
        "is deprecated. Please create a mesh using <mesh> and then reference "
751
        "it by specifying its ID in an <entropy_mesh> element.");
752
    }
753
  }
754
  // Uniform fission source weighting mesh
755
  if (check_for_node(root, "ufs_mesh")) {
6,361✔
756
    auto temp = std::stoi(get_node_value(root, "ufs_mesh"));
16✔
757
    if (model::mesh_map.find(temp) == model::mesh_map.end()) {
16✔
758
      fatal_error(fmt::format("Mesh {} specified for uniform fission site "
×
759
                              "method does not exist.",
760
        temp));
761
    }
762

763
    auto* m =
764
      dynamic_cast<RegularMesh*>(model::meshes[model::mesh_map.at(temp)].get());
16✔
765
    if (!m)
16✔
766
      fatal_error("Only regular meshes can be used as a UFS mesh");
×
767
    simulation::ufs_mesh = m;
16✔
768

769
    // Turn on uniform fission source weighting
770
    ufs_on = true;
16✔
771

772
  } else if (check_for_node(root, "uniform_fs")) {
6,345✔
773
    fatal_error(
×
774
      "Specifying a UFS mesh via the <uniform_fs> element "
775
      "is deprecated. Please create a mesh using <mesh> and then reference "
776
      "it by specifying its ID in a <ufs_mesh> element.");
777
  }
778

779
  // Check if the user has specified to write state points
780
  if (check_for_node(root, "state_point")) {
6,361✔
781

782
    // Get pointer to state_point node
783
    auto node_sp = root.child("state_point");
172✔
784

785
    // Determine number of batches at which to store state points
786
    if (check_for_node(node_sp, "batches")) {
172✔
787
      // User gave specific batches to write state points
788
      auto temp = get_node_array<int>(node_sp, "batches");
172✔
789
      for (const auto& b : temp) {
532✔
790
        statepoint_batch.insert(b);
360✔
791
      }
792
    } else {
172✔
793
      // If neither were specified, write state point at last batch
794
      statepoint_batch.insert(n_batches);
×
795
    }
796
  } else {
797
    // If no <state_point> tag was present, by default write state point at
798
    // last batch only
799
    statepoint_batch.insert(n_batches);
6,189✔
800
  }
801

802
  // Check if the user has specified to write source points
803
  if (check_for_node(root, "source_point")) {
6,361✔
804
    // Get source_point node
805
    xml_node node_sp = root.child("source_point");
96✔
806

807
    // Determine batches at which to store source points
808
    if (check_for_node(node_sp, "batches")) {
96✔
809
      // User gave specific batches to write source points
810
      auto temp = get_node_array<int>(node_sp, "batches");
48✔
811
      for (const auto& b : temp) {
128✔
812
        sourcepoint_batch.insert(b);
80✔
813
      }
814
    } else {
48✔
815
      // If neither were specified, write source points with state points
816
      sourcepoint_batch = statepoint_batch;
48✔
817
    }
818

819
    // Check if the user has specified to write binary source file
820
    if (check_for_node(node_sp, "separate")) {
96✔
821
      source_separate = get_node_value_bool(node_sp, "separate");
64✔
822
    }
823
    if (check_for_node(node_sp, "write")) {
96✔
824
      source_write = get_node_value_bool(node_sp, "write");
×
825
    }
826
    if (check_for_node(node_sp, "mcpl")) {
96✔
827
      source_mcpl_write = get_node_value_bool(node_sp, "mcpl");
16✔
828

829
      // Make sure MCPL support is enabled
830
      if (source_mcpl_write && !MCPL_ENABLED) {
16✔
831
        fatal_error(
×
832
          "Your build of OpenMC does not support writing MCPL source files.");
833
      }
834
    }
835
    if (check_for_node(node_sp, "overwrite_latest")) {
96✔
836
      source_latest = get_node_value_bool(node_sp, "overwrite_latest");
16✔
837
      source_separate = source_latest;
16✔
838
    }
839
  } else {
840
    // If no <source_point> tag was present, by default we keep source bank in
841
    // statepoint file and write it out at statepoints intervals
842
    source_separate = false;
6,265✔
843
    sourcepoint_batch = statepoint_batch;
6,265✔
844
  }
845

846
  // Check is the user specified to convert strength to statistical weight
847
  if (check_for_node(root, "uniform_source_sampling")) {
6,361✔
848
    uniform_source_sampling =
55✔
849
      get_node_value_bool(root, "uniform_source_sampling");
55✔
850
  }
851

852
  // Check if the user has specified to write surface source
853
  if (check_for_node(root, "surf_source_write")) {
6,361✔
854
    surf_source_write = true;
401✔
855
    // Get surface source write node
856
    xml_node node_ssw = root.child("surf_source_write");
401✔
857

858
    // Determine surface ids at which crossing particles are to be banked.
859
    // If no surfaces are specified, all surfaces in the model will be used
860
    // to bank source points.
861
    if (check_for_node(node_ssw, "surface_ids")) {
401✔
862
      auto temp = get_node_array<int>(node_ssw, "surface_ids");
191✔
863
      for (const auto& b : temp) {
972✔
864
        source_write_surf_id.insert(b);
781✔
865
      }
866
    }
191✔
867

868
    // Get maximum number of particles to be banked per surface
869
    if (check_for_node(node_ssw, "max_particles")) {
401✔
870
      ssw_max_particles = std::stoll(get_node_value(node_ssw, "max_particles"));
392✔
871
    } else {
872
      fatal_error("A maximum number of particles needs to be specified "
9✔
873
                  "using the 'max_particles' parameter to store surface "
874
                  "source points.");
875
    }
876

877
    // Get maximum number of surface source files to be created
878
    if (check_for_node(node_ssw, "max_source_files")) {
392✔
879
      ssw_max_files = std::stoll(get_node_value(node_ssw, "max_source_files"));
33✔
880
    } else {
881
      ssw_max_files = 1;
359✔
882
    }
883

884
    if (check_for_node(node_ssw, "mcpl")) {
392✔
885
      surf_mcpl_write = get_node_value_bool(node_ssw, "mcpl");
×
886

887
      // Make sure MCPL support is enabled
888
      if (surf_mcpl_write && !MCPL_ENABLED) {
×
889
        fatal_error("Your build of OpenMC does not support writing MCPL "
×
890
                    "surface source files.");
891
      }
892
    }
893
    // Get cell information
894
    if (check_for_node(node_ssw, "cell")) {
392✔
895
      ssw_cell_id = std::stoll(get_node_value(node_ssw, "cell"));
104✔
896
      ssw_cell_type = SSWCellType::Both;
104✔
897
    }
898
    if (check_for_node(node_ssw, "cellfrom")) {
392✔
899
      if (ssw_cell_id != C_NONE) {
90✔
900
        fatal_error(
18✔
901
          "'cell', 'cellfrom' and 'cellto' cannot be used at the same time.");
902
      }
903
      ssw_cell_id = std::stoll(get_node_value(node_ssw, "cellfrom"));
72✔
904
      ssw_cell_type = SSWCellType::From;
72✔
905
    }
906
    if (check_for_node(node_ssw, "cellto")) {
374✔
907
      if (ssw_cell_id != C_NONE) {
71✔
908
        fatal_error(
18✔
909
          "'cell', 'cellfrom' and 'cellto' cannot be used at the same time.");
910
      }
911
      ssw_cell_id = std::stoll(get_node_value(node_ssw, "cellto"));
53✔
912
      ssw_cell_type = SSWCellType::To;
53✔
913
    }
914
  }
915

916
  // If source is not separate and is to be written out in the statepoint file,
917
  // make sure that the sourcepoint batch numbers are contained in the
918
  // statepoint list
919
  if (!source_separate) {
6,316✔
920
    for (const auto& b : sourcepoint_batch) {
12,612✔
921
      if (!contains(statepoint_batch, b)) {
6,376✔
922
        fatal_error(
×
923
          "Sourcepoint batches are not a subset of statepoint batches.");
924
      }
925
    }
926
  }
927

928
  // Check if the user has specified to not reduce tallies at the end of every
929
  // batch
930
  if (check_for_node(root, "no_reduce")) {
6,316✔
931
    reduce_tallies = !get_node_value_bool(root, "no_reduce");
×
932
  }
933

934
  // Check if the user has specified to use confidence intervals for
935
  // uncertainties rather than standard deviations
936
  if (check_for_node(root, "confidence_intervals")) {
6,316✔
937
    confidence_intervals = get_node_value_bool(root, "confidence_intervals");
16✔
938
  }
939

940
  // Check for output options
941
  if (check_for_node(root, "output")) {
6,316✔
942
    // Get pointer to output node
943
    pugi::xml_node node_output = root.child("output");
372✔
944

945
    // Check for summary option
946
    if (check_for_node(node_output, "summary")) {
372✔
947
      output_summary = get_node_value_bool(node_output, "summary");
356✔
948
    }
949

950
    // Check for ASCII tallies output option
951
    if (check_for_node(node_output, "tallies")) {
372✔
952
      output_tallies = get_node_value_bool(node_output, "tallies");
16✔
953
    }
954

955
    // Set output directory if a path has been specified
956
    if (check_for_node(node_output, "path")) {
372✔
957
      path_output = get_node_value(node_output, "path");
×
958
      if (!ends_with(path_output, "/")) {
×
959
        path_output += "/";
×
960
      }
961
    }
962
  }
963

964
  // Resonance scattering parameters
965
  if (check_for_node(root, "resonance_scattering")) {
6,316✔
966
    xml_node node_res_scat = root.child("resonance_scattering");
16✔
967

968
    // See if resonance scattering is enabled
969
    if (check_for_node(node_res_scat, "enable")) {
16✔
970
      res_scat_on = get_node_value_bool(node_res_scat, "enable");
16✔
971
    } else {
972
      res_scat_on = true;
×
973
    }
974

975
    // Determine what method is used
976
    if (check_for_node(node_res_scat, "method")) {
16✔
977
      auto temp = get_node_value(node_res_scat, "method", true, true);
16✔
978
      if (temp == "rvs") {
16✔
979
        res_scat_method = ResScatMethod::rvs;
16✔
980
      } else if (temp == "dbrc") {
×
981
        res_scat_method = ResScatMethod::dbrc;
×
982
      } else {
983
        fatal_error(
×
984
          "Unrecognized resonance elastic scattering method: " + temp + ".");
×
985
      }
986
    }
16✔
987

988
    // Minimum energy for resonance scattering
989
    if (check_for_node(node_res_scat, "energy_min")) {
16✔
990
      res_scat_energy_min =
16✔
991
        std::stod(get_node_value(node_res_scat, "energy_min"));
16✔
992
    }
993
    if (res_scat_energy_min < 0.0) {
16✔
994
      fatal_error("Lower resonance scattering energy bound is negative");
×
995
    }
996

997
    // Maximum energy for resonance scattering
998
    if (check_for_node(node_res_scat, "energy_max")) {
16✔
999
      res_scat_energy_max =
16✔
1000
        std::stod(get_node_value(node_res_scat, "energy_max"));
16✔
1001
    }
1002
    if (res_scat_energy_max < res_scat_energy_min) {
16✔
1003
      fatal_error("Upper resonance scattering energy bound is below the "
×
1004
                  "lower resonance scattering energy bound.");
1005
    }
1006

1007
    // Get resonance scattering nuclides
1008
    if (check_for_node(node_res_scat, "nuclides")) {
16✔
1009
      res_scat_nuclides =
1010
        get_node_array<std::string>(node_res_scat, "nuclides");
16✔
1011
    }
1012
  }
1013

1014
  // Get volume calculations
1015
  for (pugi::xml_node node_vol : root.children("volume_calc")) {
6,642✔
1016
    model::volume_calcs.emplace_back(node_vol);
326✔
1017
  }
1018

1019
  // Get temperature settings
1020
  if (check_for_node(root, "temperature_default")) {
6,316✔
1021
    temperature_default =
172✔
1022
      std::stod(get_node_value(root, "temperature_default"));
172✔
1023
  }
1024
  if (check_for_node(root, "temperature_method")) {
6,316✔
1025
    auto temp = get_node_value(root, "temperature_method", true, true);
325✔
1026
    if (temp == "nearest") {
325✔
1027
      temperature_method = TemperatureMethod::NEAREST;
140✔
1028
    } else if (temp == "interpolation") {
185✔
1029
      temperature_method = TemperatureMethod::INTERPOLATION;
185✔
1030
    } else {
1031
      fatal_error("Unknown temperature method: " + temp);
×
1032
    }
1033
  }
325✔
1034
  if (check_for_node(root, "temperature_tolerance")) {
6,316✔
1035
    temperature_tolerance =
171✔
1036
      std::stod(get_node_value(root, "temperature_tolerance"));
171✔
1037
  }
1038
  if (check_for_node(root, "temperature_multipole")) {
6,316✔
1039
    temperature_multipole = get_node_value_bool(root, "temperature_multipole");
32✔
1040

1041
    // Multipole currently doesn't work with photon transport
1042
    if (temperature_multipole && photon_transport) {
32✔
1043
      fatal_error("Multipole data cannot currently be used in conjunction with "
×
1044
                  "photon transport.");
1045
    }
1046
  }
1047
  if (check_for_node(root, "temperature_range")) {
6,316✔
1048
    auto range = get_node_array<double>(root, "temperature_range");
×
1049
    temperature_range[0] = range.at(0);
×
1050
    temperature_range[1] = range.at(1);
×
1051
  }
1052

1053
  // Check for tabular_legendre options
1054
  if (check_for_node(root, "tabular_legendre")) {
6,316✔
1055
    // Get pointer to tabular_legendre node
1056
    xml_node node_tab_leg = root.child("tabular_legendre");
96✔
1057

1058
    // Check for enable option
1059
    if (check_for_node(node_tab_leg, "enable")) {
96✔
1060
      legendre_to_tabular = get_node_value_bool(node_tab_leg, "enable");
96✔
1061
    }
1062

1063
    // Check for the number of points
1064
    if (check_for_node(node_tab_leg, "num_points")) {
96✔
1065
      legendre_to_tabular_points =
×
1066
        std::stoi(get_node_value(node_tab_leg, "num_points"));
×
1067
      if (legendre_to_tabular_points <= 1 && !run_CE) {
×
1068
        fatal_error(
×
1069
          "The 'num_points' subelement/attribute of the "
1070
          "<tabular_legendre> element must contain a value greater than 1");
1071
      }
1072
    }
1073
  }
1074

1075
  // Check whether create delayed neutrons in fission
1076
  if (check_for_node(root, "create_delayed_neutrons")) {
6,316✔
1077
    create_delayed_neutrons =
×
1078
      get_node_value_bool(root, "create_delayed_neutrons");
×
1079
  }
1080

1081
  // Check whether create fission sites
1082
  if (run_mode == RunMode::FIXED_SOURCE) {
6,316✔
1083
    if (check_for_node(root, "create_fission_neutrons")) {
2,129✔
1084
      create_fission_neutrons =
16✔
1085
        get_node_value_bool(root, "create_fission_neutrons");
16✔
1086
    }
1087
  }
1088

1089
  // Check whether to scale fission photon yields
1090
  if (check_for_node(root, "delayed_photon_scaling")) {
6,316✔
1091
    delayed_photon_scaling =
×
1092
      get_node_value_bool(root, "delayed_photon_scaling");
×
1093
  }
1094

1095
  // Check whether to use event-based parallelism
1096
  if (check_for_node(root, "event_based")) {
6,316✔
1097
    event_based = get_node_value_bool(root, "event_based");
×
1098
  }
1099

1100
  // Check whether material cell offsets should be generated
1101
  if (check_for_node(root, "material_cell_offsets")) {
6,316✔
1102
    material_cell_offsets = get_node_value_bool(root, "material_cell_offsets");
×
1103
  }
1104

1105
  // Weight window information
1106
  for (pugi::xml_node node_ww : root.children("weight_windows")) {
6,395✔
1107
    variance_reduction::weight_windows.emplace_back(
79✔
1108
      std::make_unique<WeightWindows>(node_ww));
158✔
1109
  }
1110

1111
  // Enable weight windows by default if one or more are present
1112
  if (variance_reduction::weight_windows.size() > 0)
6,316✔
1113
    settings::weight_windows_on = true;
52✔
1114

1115
  // read weight windows from file
1116
  if (check_for_node(root, "weight_windows_file")) {
6,316✔
1117
    weight_windows_file = get_node_value(root, "weight_windows_file");
×
1118
  }
1119

1120
  // read settings for weight windows value, this will override
1121
  // the automatic setting even if weight windows are present
1122
  if (check_for_node(root, "weight_windows_on")) {
6,316✔
1123
    weight_windows_on = get_node_value_bool(root, "weight_windows_on");
36✔
1124
  }
1125

1126
  if (check_for_node(root, "max_history_splits")) {
6,316✔
1127
    settings::max_history_splits =
214✔
1128
      std::stoi(get_node_value(root, "max_history_splits"));
214✔
1129
  }
1130

1131
  if (check_for_node(root, "max_tracks")) {
6,316✔
1132
    settings::max_tracks = std::stoi(get_node_value(root, "max_tracks"));
48✔
1133
  }
1134

1135
  // Create weight window generator objects
1136
  if (check_for_node(root, "weight_window_generators")) {
6,316✔
1137
    auto wwgs_node = root.child("weight_window_generators");
81✔
1138
    for (pugi::xml_node node_wwg :
81✔
1139
      wwgs_node.children("weight_windows_generator")) {
243✔
1140
      variance_reduction::weight_windows_generators.emplace_back(
81✔
1141
        std::make_unique<WeightWindowsGenerator>(node_wwg));
162✔
1142
    }
1143
    // if any of the weight windows are intended to be generated otf, make sure
1144
    // they're applied
1145
    for (const auto& wwg : variance_reduction::weight_windows_generators) {
81✔
1146
      if (wwg->on_the_fly_) {
81✔
1147
        settings::weight_windows_on = true;
81✔
1148
        break;
81✔
1149
      }
1150
    }
1151
  }
1152

1153
  // Set up weight window checkpoints
1154
  if (check_for_node(root, "weight_window_checkpoints")) {
6,316✔
1155
    xml_node ww_checkpoints = root.child("weight_window_checkpoints");
×
1156
    if (check_for_node(ww_checkpoints, "collision")) {
×
1157
      weight_window_checkpoint_collision =
×
1158
        get_node_value_bool(ww_checkpoints, "collision");
×
1159
    }
1160
    if (check_for_node(ww_checkpoints, "surface")) {
×
1161
      weight_window_checkpoint_surface =
×
1162
        get_node_value_bool(ww_checkpoints, "surface");
×
1163
    }
1164
  }
1165

1166
  if (check_for_node(root, "use_decay_photons")) {
6,316✔
1167
    settings::use_decay_photons =
11✔
1168
      get_node_value_bool(root, "use_decay_photons");
11✔
1169
  }
1170
}
6,316✔
1171

1172
void free_memory_settings()
6,462✔
1173
{
1174
  settings::statepoint_batch.clear();
6,462✔
1175
  settings::sourcepoint_batch.clear();
6,462✔
1176
  settings::source_write_surf_id.clear();
6,462✔
1177
  settings::res_scat_nuclides.clear();
6,462✔
1178
}
6,462✔
1179

1180
//==============================================================================
1181
// C API functions
1182
//==============================================================================
1183

1184
extern "C" int openmc_set_n_batches(
55✔
1185
  int32_t n_batches, bool set_max_batches, bool add_statepoint_batch)
1186
{
1187
  if (settings::n_inactive >= n_batches) {
55✔
1188
    set_errmsg("Number of active batches must be greater than zero.");
11✔
1189
    return OPENMC_E_INVALID_ARGUMENT;
11✔
1190
  }
1191

1192
  if (simulation::current_batch >= n_batches) {
44✔
1193
    set_errmsg("Number of batches must be greater than current batch.");
11✔
1194
    return OPENMC_E_INVALID_ARGUMENT;
11✔
1195
  }
1196

1197
  if (!settings::trigger_on) {
33✔
1198
    // Set n_batches and n_max_batches to same value
1199
    settings::n_batches = n_batches;
11✔
1200
    settings::n_max_batches = n_batches;
11✔
1201
  } else {
1202
    // Set n_batches and n_max_batches based on value of set_max_batches
1203
    if (set_max_batches) {
22✔
1204
      settings::n_max_batches = n_batches;
11✔
1205
    } else {
1206
      settings::n_batches = n_batches;
11✔
1207
    }
1208
  }
1209

1210
  // Update size of k_generation and entropy
1211
  int m = settings::n_max_batches * settings::gen_per_batch;
33✔
1212
  simulation::k_generation.reserve(m);
33✔
1213
  simulation::entropy.reserve(m);
33✔
1214

1215
  // Add value of n_batches to statepoint_batch
1216
  if (add_statepoint_batch &&
55✔
1217
      !(contains(settings::statepoint_batch, n_batches)))
22✔
1218
    settings::statepoint_batch.insert(n_batches);
22✔
1219

1220
  return 0;
33✔
1221
}
1222

1223
extern "C" int openmc_get_n_batches(int* n_batches, bool get_max_batches)
2,530✔
1224
{
1225
  *n_batches = get_max_batches ? settings::n_max_batches : settings::n_batches;
2,530✔
1226

1227
  return 0;
2,530✔
1228
}
1229

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

© 2025 Coveralls, Inc