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

openmc-dev / openmc / 35915463369

23 Sep 2026 08:22PM UTC coverage: 81.584% (+0.1%) from 81.485%
35915463369

Pull #4141

github

web-flow
Merge cec81d375 into 1d75981db
Pull Request #4141: Adding multigroup photon transport capability in MC mode

20033 of 29057 branches covered (68.94%)

Branch coverage included in aggregate %.

185 of 215 new or added lines in 10 files covered. (86.05%)

4 existing lines in 4 files now uncovered.

62689 of 72338 relevant lines covered (86.66%)

49541697.52 hits per line

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

75.86
/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 <cstring> // for strcmp
6
#include <limits>  // for numeric_limits
7
#include <string>
8

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

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

40
namespace openmc {
41

42
//==============================================================================
43
// Global variables
44
//==============================================================================
45

46
namespace settings {
47

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

94
std::string path_cross_sections;
95
std::string path_input;
96
std::string path_output;
97
std::string path_particle_restart;
98
std::string path_sourcepoint;
99
std::string path_statepoint;
100
std::string weight_windows_file;
101
std::string properties_file;
102

103
int32_t n_inactive {0};
104
int32_t max_lost_particles {10};
105
double rel_max_lost_particles {1.0e-6};
106
int32_t max_write_lost_particles {-1};
107
int32_t gen_per_batch {1};
108
int64_t n_particles {-1};
109

110
int64_t max_particles_in_flight {100000};
111
int max_particle_events {1000000};
112

113
ElectronTreatment electron_treatment {ElectronTreatment::TTB};
114
array<double, 4> energy_cutoff {0.0, 1000.0, 0.0, 0.0};
115
array<double, 4> time_cutoff {INFTY, INFTY, INFTY, INFTY};
116
int ifp_n_generation {-1};
117
int legendre_to_tabular_points {C_NONE};
118
int max_order {0};
119
int n_log_bins {8000};
120
int n_batches;
121
int n_max_batches;
122
int max_secondaries {10000};
123
int max_history_splits {10'000'000};
124
int max_tracks {1000};
125
ResScatMethod res_scat_method {ResScatMethod::rvs};
126
double res_scat_energy_min {0.01};
127
double res_scat_energy_max {1000.0};
128
vector<std::string> res_scat_nuclides;
129
RunMode run_mode {RunMode::UNSET};
130
SolverType solver_type {SolverType::MONTE_CARLO};
131
std::unordered_set<int> sourcepoint_batch;
132
std::unordered_set<int> statepoint_batch;
133
double source_rejection_fraction {0.05};
134
double free_gas_threshold {400.0};
135
std::unordered_set<int> source_write_surf_id;
136
CollisionTrackConfig collision_track_config {};
137
int64_t ssw_max_particles;
138
int64_t ssw_max_files;
139
int64_t ssw_cell_id {C_NONE};
140
SSWCellType ssw_cell_type {SSWCellType::None};
141
double surface_grazing_cutoff {0.001};
142
double surface_grazing_ratio {0.5};
143
TemperatureMethod temperature_method {TemperatureMethod::NEAREST};
144
double temperature_tolerance {10.0};
145
double temperature_default {293.6};
146
array<double, 2> temperature_range {0.0, 0.0};
147
int trace_batch;
148
int trace_gen;
149
int64_t trace_particle;
150
vector<array<int, 3>> track_identifiers;
151
int trigger_batch_interval {1};
152
int verbosity {-1};
153
double weight_cutoff {0.25};
154
double weight_survive {1.0};
155

156
} // namespace settings
157

158
//==============================================================================
159
// Functions
160
//==============================================================================
161

162
void get_run_parameters(pugi::xml_node node_base)
8,571 ✔
163
{
164
  using namespace settings;
8,571 ✔
165
  using namespace pugi;
8,571 ✔
166

167
  // Check number of particles
168
  if (!check_for_node(node_base, "particles")) {
8,571 !
169
    fatal_error("Need to specify number of particles.");
×
170
  }
171

172
  // Get number of particles if it wasn't specified as a command-line argument
173
  if (n_particles == -1) {
8,571 ✔
174
    n_particles = std::stoll(get_node_value(node_base, "particles"));
8,560 ✔
175
  }
176

177
  // Get maximum number of in flight particles for event-based mode
178
  if (check_for_node(node_base, "max_particles_in_flight")) {
8,571 !
179
    max_particles_in_flight =
×
180
      std::stoll(get_node_value(node_base, "max_particles_in_flight"));
×
181
  }
182

183
  // Get maximum number of events allowed per particle
184
  if (check_for_node(node_base, "max_particle_events")) {
8,571 !
185
    max_particle_events =
×
186
      std::stoll(get_node_value(node_base, "max_particle_events"));
×
187
  }
188

189
  // Get number of basic batches
190
  if (check_for_node(node_base, "batches")) {
8,571 !
191
    n_batches = std::stoi(get_node_value(node_base, "batches"));
8,571 ✔
192
  }
193
  if (!trigger_on)
8,571 ✔
194
    n_max_batches = n_batches;
8,430 ✔
195

196
  // Get max number of lost particles
197
  if (check_for_node(node_base, "max_lost_particles")) {
8,571 ✔
198
    max_lost_particles =
92 ✔
199
      std::stoi(get_node_value(node_base, "max_lost_particles"));
46 ✔
200
  }
201

202
  // Get relative number of lost particles
203
  if (check_for_node(node_base, "rel_max_lost_particles")) {
8,571 !
204
    rel_max_lost_particles =
×
205
      std::stod(get_node_value(node_base, "rel_max_lost_particles"));
×
206
  }
207

208
  // Get relative number of lost particles
209
  if (check_for_node(node_base, "max_write_lost_particles")) {
8,571 ✔
210
    max_write_lost_particles =
30 ✔
211
      std::stoi(get_node_value(node_base, "max_write_lost_particles"));
15 ✔
212
  }
213

214
  // Get number of inactive batches
215
  if (run_mode == RunMode::EIGENVALUE ||
8,571 ✔
216
      solver_type == SolverType::RANDOM_RAY) {
3,734 ✔
217
    if (check_for_node(node_base, "inactive")) {
5,467 ✔
218
      n_inactive = std::stoi(get_node_value(node_base, "inactive"));
5,176 ✔
219
    }
220
    if (check_for_node(node_base, "generations_per_batch")) {
5,467 ✔
221
      gen_per_batch =
30 ✔
222
        std::stoi(get_node_value(node_base, "generations_per_batch"));
15 ✔
223

224
      // The random ray solver runs a single generation per batch. The rest of
225
      // the code has to see that, since overall_generation() strides by
226
      // gen_per_batch while only one generation per batch is ever recorded.
227
      if (gen_per_batch != 1 && solver_type == SolverType::RANDOM_RAY) {
15 !
228
        warning("The 'generations_per_batch' setting does not apply to the "
×
229
                "random ray solver and is ignored.");
230
        gen_per_batch = 1;
×
231
      }
232
    }
233

234
    // Preallocate space for keff and entropy by generation
235
    int m = settings::n_max_batches * settings::gen_per_batch;
5,467 ✔
236
    simulation::k_generation.reserve(m);
5,467 ✔
237
    simulation::entropy.reserve(m);
5,467 ✔
238

239
    // Get the trigger information for keff
240
    if (check_for_node(node_base, "keff_trigger")) {
5,467 ✔
241
      xml_node node_keff_trigger = node_base.child("keff_trigger");
101 ✔
242

243
      if (check_for_node(node_keff_trigger, "type")) {
101 !
244
        auto temp = get_node_value(node_keff_trigger, "type", true, true);
101 ✔
245
        if (temp == "std_dev") {
101 !
246
          keff_trigger.metric = TriggerMetric::standard_deviation;
101 ✔
247
        } else if (temp == "variance") {
×
248
          keff_trigger.metric = TriggerMetric::variance;
×
249
        } else if (temp == "rel_err") {
×
250
          keff_trigger.metric = TriggerMetric::relative_error;
×
251
        } else {
252
          fatal_error("Unrecognized keff trigger type " + temp);
×
253
        }
254
      } else {
×
255
        fatal_error("Specify keff trigger type in settings XML");
×
256
      }
257

258
      if (check_for_node(node_keff_trigger, "threshold")) {
101 !
259
        keff_trigger.threshold =
202 ✔
260
          std::stod(get_node_value(node_keff_trigger, "threshold"));
202 ✔
261
        if (keff_trigger.threshold <= 0) {
101 !
262
          fatal_error("keff trigger threshold must be positive");
×
263
        }
264
      } else {
265
        fatal_error("Specify keff trigger threshold in settings XML");
×
266
      }
267
    }
268
  }
269

270
  // Random ray variables
271
  if (solver_type == SolverType::RANDOM_RAY) {
8,571 ✔
272
    xml_node random_ray_node = node_base.child("random_ray");
1,061 ✔
273
    if (check_for_node(random_ray_node, "distance_active")) {
1,061 !
274
      RandomRay::distance_active_ =
2,122 ✔
275
        std::stod(get_node_value(random_ray_node, "distance_active"));
2,122 ✔
276
      if (RandomRay::distance_active_ <= 0.0) {
1,061 !
277
        fatal_error("Random ray active distance must be greater than 0");
×
278
      }
279
    } else {
280
      fatal_error("Specify random ray active distance in settings XML");
×
281
    }
282
    if (check_for_node(random_ray_node, "distance_inactive")) {
1,061 !
283
      RandomRay::distance_inactive_ =
2,122 ✔
284
        std::stod(get_node_value(random_ray_node, "distance_inactive"));
2,122 ✔
285
      if (RandomRay::distance_inactive_ < 0) {
1,061 !
286
        fatal_error(
×
287
          "Random ray inactive distance must be greater than or equal to 0");
288
      }
289
    } else {
290
      fatal_error("Specify random ray inactive distance in settings XML");
×
291
    }
292
    if (check_for_node(random_ray_node, "ray_source")) {
1,061 !
293
      xml_node ray_source_node = random_ray_node.child("ray_source");
1,061 ✔
294
      xml_node source_node = ray_source_node.child("source");
1,061 ✔
295
      // Get point to list of <source> elements and make sure there is at least
296
      // one
297
      RandomRay::ray_source_ = Source::create(source_node);
2,122 ✔
298
    } else {
299
      fatal_error("Specify random ray source in settings XML");
×
300
    }
301
    if (check_for_node(random_ray_node, "volume_estimator")) {
1,061 ✔
302
      std::string temp_str =
664 ✔
303
        get_node_value(random_ray_node, "volume_estimator", true, true);
664 ✔
304
      if (temp_str == "simulation_averaged") {
664 ✔
305
        FlatSourceDomain::volume_estimator_ =
30 ✔
306
          RandomRayVolumeEstimator::SIMULATION_AVERAGED;
307
      } else if (temp_str == "naive") {
634 ✔
308
        FlatSourceDomain::volume_estimator_ = RandomRayVolumeEstimator::NAIVE;
132 ✔
309
      } else if (temp_str == "hybrid") {
502 ✔
310
        FlatSourceDomain::volume_estimator_ = RandomRayVolumeEstimator::HYBRID;
367 ✔
311
      } else if (temp_str == "adaptive") {
135 ✔
312
        FlatSourceDomain::volume_estimator_ =
90 ✔
313
          RandomRayVolumeEstimator::ADAPTIVE;
314
      } else if (temp_str == "strict_adaptive") {
45 !
315
        FlatSourceDomain::volume_estimator_ =
45 ✔
316
          RandomRayVolumeEstimator::STRICT_ADAPTIVE;
317
      } else if (temp_str == "auto") {
×
318
        FlatSourceDomain::volume_estimator_ = RandomRayVolumeEstimator::AUTO;
×
319
      } else {
320
        fatal_error("Unrecognized volume estimator: " + temp_str);
×
321
      }
322
    }
664 ✔
323
    if (check_for_node(random_ray_node, "source_shape")) {
1,061 ✔
324
      std::string temp_str =
547 ✔
325
        get_node_value(random_ray_node, "source_shape", true, true);
547 ✔
326
      if (temp_str == "flat") {
547 ✔
327
        RandomRay::source_shape_ = RandomRaySourceShape::FLAT;
97 ✔
328
      } else if (temp_str == "linear") {
450 ✔
329
        RandomRay::source_shape_ = RandomRaySourceShape::LINEAR;
405 ✔
330
      } else if (temp_str == "linear_xy") {
45 !
331
        RandomRay::source_shape_ = RandomRaySourceShape::LINEAR_XY;
45 ✔
332
      } else {
333
        fatal_error("Unrecognized source shape: " + temp_str);
×
334
      }
335
    }
547 ✔
336
    if (check_for_node(random_ray_node, "volume_normalized_flux_tallies")) {
1,061 ✔
337
      FlatSourceDomain::volume_normalized_flux_tallies_ =
738 ✔
338
        get_node_value_bool(random_ray_node, "volume_normalized_flux_tallies");
738 ✔
339
    }
340
    if (check_for_node(random_ray_node, "adjoint")) {
1,061 ✔
341
      FlatSourceDomain::adjoint_requested_ =
86 ✔
342
        get_node_value_bool(random_ray_node, "adjoint");
86 ✔
343
    }
344
    if (check_for_node(random_ray_node, "source_gradient_limiter")) {
1,061 ✔
345
      FlatSourceDomain::source_gradient_limiter_ =
15 ✔
346
        get_node_value_bool(random_ray_node, "source_gradient_limiter");
15 ✔
347
    }
348
    if (check_for_node(random_ray_node, "sample_method")) {
1,061 ✔
349
      std::string temp_str =
52 ✔
350
        get_node_value(random_ray_node, "sample_method", true, true);
52 ✔
351
      if (temp_str == "prng") {
52 !
352
        RandomRay::sample_method_ = RandomRaySampleMethod::PRNG;
×
353
      } else if (temp_str == "halton") {
52 ✔
354
        RandomRay::sample_method_ = RandomRaySampleMethod::HALTON;
37 ✔
355
      } else if (temp_str == "s2") {
15 !
356
        RandomRay::sample_method_ = RandomRaySampleMethod::S2;
15 ✔
357
      } else {
358
        fatal_error("Unrecognized sample method: " + temp_str);
×
359
      }
360
    }
52 ✔
361
    if (check_for_node(random_ray_node, "source_region_meshes")) {
1,061 ✔
362
      pugi::xml_node node_source_region_meshes =
458 ✔
363
        random_ray_node.child("source_region_meshes");
458 ✔
364
      for (pugi::xml_node node_mesh :
976 ✔
365
        node_source_region_meshes.children("mesh")) {
976 ✔
366
        int mesh_id = std::stoi(node_mesh.attribute("id").value());
1,036 ✔
367
        for (pugi::xml_node node_domain : node_mesh.children("domain")) {
1,036 ✔
368
          int domain_id = std::stoi(node_domain.attribute("id").value());
1,036 ✔
369
          std::string domain_type = node_domain.attribute("type").value();
518 ✔
370
          Source::DomainType type;
518 ✔
371
          if (domain_type == "material") {
518 ✔
372
            type = Source::DomainType::MATERIAL;
30 ✔
373
          } else if (domain_type == "cell") {
488 ✔
374
            type = Source::DomainType::CELL;
30 ✔
375
          } else if (domain_type == "universe") {
458 !
376
            type = Source::DomainType::UNIVERSE;
458 ✔
377
          } else {
378
            throw std::runtime_error("Unknown domain type: " + domain_type);
×
379
          }
380
          FlatSourceDomain::mesh_domain_map_[mesh_id].emplace_back(
518 ✔
381
            type, domain_id);
382
        }
518 ✔
383
      }
384
    }
385
    if (check_for_node(random_ray_node, "diagonal_stabilization_rho")) {
1,061 ✔
386
      FlatSourceDomain::diagonal_stabilization_rho_ = std::stod(
30 ✔
387
        get_node_value(random_ray_node, "diagonal_stabilization_rho"));
30 ✔
388
      if (FlatSourceDomain::diagonal_stabilization_rho_ < 0.0 ||
30 !
389
          FlatSourceDomain::diagonal_stabilization_rho_ > 1.0) {
390
        fatal_error("Random ray diagonal stabilization rho factor must be "
×
391
                    "between 0 and 1");
392
      }
393
    }
394
    if (check_for_node(random_ray_node, "adjoint_source")) {
1,061 ✔
395
      pugi::xml_node adj_source_node = random_ray_node.child("adjoint_source");
15 ✔
396
      for (pugi::xml_node source_node : adj_source_node.children("source")) {
30 ✔
397
        // Find any local adjoint sources
398
        model::adjoint_sources.push_back(Source::create(source_node));
30 ✔
399
      }
400
    }
401
  }
402
}
8,571 ✔
403

404
void read_settings_xml()
1,423 ✔
405
{
406
  using namespace settings;
1,423 ✔
407
  using namespace pugi;
1,423 ✔
408
  // Check if settings.xml exists
409
  std::string filename = settings::path_input + "settings.xml";
1,423 ✔
410
  if (!file_exists(filename)) {
1,423 ✔
411
    if (run_mode != RunMode::PLOTTING) {
22 !
412
      fatal_error("Could not find any XML input files! In order to run OpenMC, "
×
413
                  "you first need a set of input files; at a minimum, this "
414
                  "includes settings.xml, geometry.xml, and materials.xml or a "
415
                  "single model XML file. Please consult the user's guide at "
416
                  "https://docs.openmc.org for further information.");
417
    } else {
418
      // The settings.xml file is optional if we just want to make a plot.
419
      return;
22 ✔
420
    }
421
  }
422

423
  // Parse settings.xml file
424
  xml_document doc;
1,401 ✔
425
  auto result = doc.load_file(filename.c_str());
1,401 ✔
426
  if (!result) {
1,401 !
427
    fatal_error("Error processing settings.xml file.");
×
428
  }
429

430
  // Get root element
431
  xml_node root = doc.document_element();
1,401 ✔
432

433
  // Verbosity
434
  if (check_for_node(root, "verbosity") && verbosity == -1) {
1,401 !
435
    verbosity = std::stoi(get_node_value(root, "verbosity"));
428 ✔
436
  } else if (verbosity == -1) {
1,187 !
437
    verbosity = 7;
1,187 ✔
438
  }
439

440
  // To this point, we haven't displayed any output since we didn't know what
441
  // the verbosity is. Now that we checked for it, show the title if necessary
442
  if (mpi::master) {
1,401 ✔
443
    if (verbosity >= 2)
1,213 ✔
444
      title();
1,007 ✔
445
  }
446

447
  write_message("Reading settings XML file...", 5);
1,401 ✔
448

449
  read_settings_xml(root);
1,401 ✔
450
}
1,413 ✔
451

452
void read_settings_xml(pugi::xml_node root)
9,666 ✔
453
{
454
  using namespace settings;
9,666 ✔
455
  using namespace pugi;
9,666 ✔
456

457
  // Find if a multi-group or continuous-energy simulation is desired
458
  if (check_for_node(root, "energy_mode")) {
9,666 ✔
459
    std::string temp_str = get_node_value(root, "energy_mode", true, true);
1,665 ✔
460
    if (temp_str == "mg" || temp_str == "multi-group") {
3,330 !
461
      run_CE = false;
1,665 ✔
462
    } else if (temp_str == "ce" || temp_str == "continuous-energy") {
×
463
      run_CE = true;
×
464
    }
465
  }
1,665 ✔
466

467
  // Check for user meshes and allocate
468
  read_meshes(root);
9,666 ✔
469

470
  // Look for deprecated cross_sections.xml file in settings.xml
471
  if (check_for_node(root, "cross_sections")) {
9,666 !
472
    warning(
×
473
      "Setting cross_sections in settings.xml has been deprecated."
474
      " The cross_sections are now set in materials.xml and the "
475
      "cross_sections input to materials.xml and the OPENMC_CROSS_SECTIONS"
476
      " environment variable will take precendent over setting "
477
      "cross_sections in settings.xml.");
478
    path_cross_sections = get_node_value(root, "cross_sections");
×
479
  }
480

481
  if (!run_CE) {
9,666 ✔
482
    // Scattering Treatments
483
    if (check_for_node(root, "max_order")) {
1,665 ✔
484
      max_order = std::stoi(get_node_value(root, "max_order"));
30 ✔
485
    } else {
486
      // Set to default of largest int - 1, which means to use whatever is
487
      // contained in library. This is largest int - 1 because for legendre
488
      // scattering, a value of 1 is added to the order; adding 1 to the largest
489
      // int gets you the largest negative integer, which is not what we want.
490
      max_order = std::numeric_limits<int>::max() - 1;
1,650 ✔
491
    }
492
  }
493

494
  // Check for a trigger node and get trigger information
495
  if (check_for_node(root, "trigger")) {
9,666 ✔
496
    xml_node node_trigger = root.child("trigger");
156 ✔
497

498
    // Check if trigger(s) are to be turned on
499
    trigger_on = get_node_value_bool(node_trigger, "active");
156 ✔
500

501
    if (trigger_on) {
156 ✔
502
      if (check_for_node(node_trigger, "max_batches")) {
141 !
503
        n_max_batches = std::stoi(get_node_value(node_trigger, "max_batches"));
282 ✔
504
      } else {
505
        fatal_error("<max_batches> must be specified with triggers");
×
506
      }
507

508
      // Get the batch interval to check triggers
509
      if (!check_for_node(node_trigger, "batch_interval")) {
141 ✔
510
        trigger_predict = true;
15 ✔
511
      } else {
512
        trigger_batch_interval =
252 ✔
513
          std::stoi(get_node_value(node_trigger, "batch_interval"));
252 ✔
514
        if (trigger_batch_interval <= 0) {
126 !
515
          fatal_error("Trigger batch interval must be greater than zero");
×
516
        }
517
      }
518
    }
519
  }
520

521
  // Check run mode if it hasn't been set from the command line
522
  xml_node node_mode;
9,666 ✔
523
  if (run_mode == RunMode::UNSET) {
9,666 ✔
524
    if (check_for_node(root, "run_mode")) {
8,603 ✔
525
      std::string temp_str = get_node_value(root, "run_mode", true, true);
8,573 ✔
526
      if (temp_str == "eigenvalue") {
8,573 ✔
527
        run_mode = RunMode::EIGENVALUE;
4,807 ✔
528
      } else if (temp_str == "fixed source") {
3,766 ✔
529
        run_mode = RunMode::FIXED_SOURCE;
3,734 ✔
530
      } else if (temp_str == "plot") {
32 !
531
        run_mode = RunMode::PLOTTING;
×
532
      } else if (temp_str == "particle restart") {
32 !
533
        run_mode = RunMode::PARTICLE;
×
534
      } else if (temp_str == "volume") {
32 !
535
        run_mode = RunMode::VOLUME;
32 ✔
536
      } else {
537
        fatal_error("Unrecognized run mode: " + temp_str);
×
538
      }
539

540
      // Assume XML specifies <particles>, <batches>, etc. directly
541
      node_mode = root;
8,573 ✔
542
    } else {
8,573 ✔
543
      warning("<run_mode> should be specified.");
30 ✔
544

545
      // Make sure that either eigenvalue or fixed source was specified
546
      node_mode = root.child("eigenvalue");
30 ✔
547
      if (node_mode) {
30 !
548
        run_mode = RunMode::EIGENVALUE;
30 ✔
549
      } else {
550
        node_mode = root.child("fixed_source");
×
551
        if (node_mode) {
×
552
          run_mode = RunMode::FIXED_SOURCE;
×
553
        } else {
554
          fatal_error("<eigenvalue> or <fixed_source> not specified.");
×
555
        }
556
      }
557
    }
558
  }
559

560
  // Check solver type
561
  if (check_for_node(root, "random_ray")) {
9,666 ✔
562
    solver_type = SolverType::RANDOM_RAY;
1,061 ✔
563
    if (run_CE)
1,061 !
564
      fatal_error("multi-group energy mode must be specified in settings XML "
×
565
                  "when using the random ray solver.");
566
  }
567

568
  if (run_mode == RunMode::EIGENVALUE || run_mode == RunMode::FIXED_SOURCE) {
9,666 ✔
569
    // Read run parameters
570
    get_run_parameters(node_mode);
8,571 ✔
571

572
    // Check number of active batches, inactive batches, max lost particles and
573
    // particles
574
    if (n_batches <= n_inactive) {
8,571 !
575
      fatal_error("Number of active batches must be greater than zero.");
×
576
    } else if (n_inactive < 0) {
8,571 !
577
      fatal_error("Number of inactive batches must be non-negative.");
×
578
    } else if (n_particles <= 0) {
8,571 !
579
      fatal_error("Number of particles must be greater than zero.");
×
580
    } else if (max_lost_particles <= 0) {
8,571 !
581
      fatal_error("Number of max lost particles must be greater than zero.");
×
582
    } else if (rel_max_lost_particles <= 0.0 || rel_max_lost_particles >= 1.0) {
8,571 !
583
      fatal_error("Relative max lost particles must be between zero and one.");
×
584
    }
585

586
    // Check for user value for the number of generation of the Iterated Fission
587
    // Probability (IFP) method
588
    if (check_for_node(root, "ifp_n_generation")) {
8,571 ✔
589
      ifp_n_generation = std::stoi(get_node_value(root, "ifp_n_generation"));
210 ✔
590
      if (ifp_n_generation <= 0) {
105 !
591
        fatal_error("'ifp_n_generation' must be greater than 0.");
×
592
      }
593
      // Avoid tallying 0 if IFP logs are not complete when active cycles start
594
      if (ifp_n_generation > n_inactive) {
105 ✔
595
        fatal_error("'ifp_n_generation' must be lower than or equal to the "
9 ✔
596
                    "number of inactive cycles.");
597
      }
598
    }
599
  }
600

601
  // Copy plotting random number seed if specified
602
  if (check_for_node(root, "plot_seed")) {
9,657 !
603
    auto seed = std::stoll(get_node_value(root, "plot_seed"));
×
604
    model::plotter_seed = seed;
×
605
  }
606

607
  // Copy random number seed if specified
608
  if (check_for_node(root, "seed")) {
9,657 ✔
609
    auto seed = std::stoll(get_node_value(root, "seed"));
1,200 ✔
610
    openmc_set_seed(seed);
600 ✔
611
  }
612

613
  // Copy random number stride if specified
614
  if (check_for_node(root, "stride")) {
9,657 ✔
615
    auto stride = std::stoull(get_node_value(root, "stride"));
30 ✔
616
    openmc_set_stride(stride);
15 ✔
617
  }
618

619
  // Check for electron treatment
620
  if (check_for_node(root, "electron_treatment")) {
9,657 ✔
621
    auto temp_str = get_node_value(root, "electron_treatment", true, true);
108 ✔
622
    if (temp_str == "led") {
108 ✔
623
      electron_treatment = ElectronTreatment::LED;
41 ✔
624
    } else if (temp_str == "ttb") {
67 !
625
      electron_treatment = ElectronTreatment::TTB;
67 ✔
626
    } else {
UNCOV
627
      fatal_error("Unrecognized electron treatment: " + temp_str + ".");
×
628
    }
629
  }
108 ✔
630

631
  // Check for photon transport
632
  if (check_for_node(root, "photon_transport")) {
9,657 ✔
633
    photon_transport = get_node_value_bool(root, "photon_transport");
540 ✔
634
  }
635

636
  // Check for atomic relaxation
637
  if (check_for_node(root, "atomic_relaxation")) {
9,657 ✔
638
    atomic_relaxation = get_node_value_bool(root, "atomic_relaxation");
30 ✔
639
  }
640

641
  // Number of bins for logarithmic grid
642
  if (check_for_node(root, "log_grid_bins")) {
9,657 ✔
643
    n_log_bins = std::stoi(get_node_value(root, "log_grid_bins"));
30 ✔
644
    if (n_log_bins < 1) {
15 !
645
      fatal_error("Number of bins for logarithmic grid must be greater "
×
646
                  "than zero.");
647
    }
648
  }
649

650
  // Number of OpenMP threads
651
  if (check_for_node(root, "threads")) {
9,657 !
652
    if (mpi::master)
×
653
      warning("The <threads> element has been deprecated. Use "
×
654
              "the OMP_NUM_THREADS environment variable to set the number of "
655
              "threads.");
656
  }
657

658
  // ==========================================================================
659
  // EXTERNAL SOURCE
660

661
  // Get point to list of <source> elements and make sure there is at least one
662
  for (pugi::xml_node node : root.children("source")) {
18,436 ✔
663
    model::external_sources.push_back(Source::create(node));
17,568 ✔
664
  }
665

666
  // Check if the user has specified to read surface source
667
  if (check_for_node(root, "surf_source_read")) {
9,647 ✔
668
    if (mpi::master)
30 ✔
669
      warning("The <surf_source_read> element has been deprecated. Use a file "
44 ✔
670
              "source instead, i.e., <source type=\"file\" "
671
              "file=\"surface_source.h5\"/>, which additionally supports a "
672
              "source strength and source constraints.");
673

674
    // Get surface source read node
675
    xml_node node_ssr = root.child("surf_source_read");
30 ✔
676

677
    std::string path = "surface_source.h5";
30 ✔
678
    // Check if the user has specified different file for surface source reading
679
    if (check_for_node(node_ssr, "path")) {
30 !
680
      path = get_node_value(node_ssr, "path", false, true);
30 ✔
681
    }
682
    model::external_sources.push_back(make_unique<FileSource>(path));
30 ✔
683
  }
30 ✔
684

685
  // If no source specified, default to isotropic point source at origin with
686
  // Watt spectrum. No default source is needed in random ray mode.
687
  if (model::external_sources.empty() &&
9,647 ✔
688
      settings::solver_type != SolverType::RANDOM_RAY) {
2,627 ✔
689
    double T[] {0.0};
2,421 ✔
690
    double p[] {1.0};
2,421 ✔
691
    model::external_sources.push_back(make_unique<IndependentSource>(
2,421 ✔
692
      UPtrSpace {new SpatialPoint({0.0, 0.0, 0.0})},
4,842 ✔
693
      UPtrAngle {new Isotropic()}, UPtrDist {new Watt(0.988e6, 2.249e-6)},
4,842 ✔
694
      UPtrDist {new Discrete(T, p, 1)}));
4,842 ✔
695
  }
696

697
  // Build probability mass function for sampling external sources
698
  vector<double> source_strengths;
9,647 ✔
699
  for (auto& s : model::external_sources) {
20,877 ✔
700
    source_strengths.push_back(s->strength());
11,230 ✔
701
  }
702
  model::external_sources_probability.assign(source_strengths);
9,647 ✔
703

704
  // Check if we want to write out source
705
  if (check_for_node(root, "write_initial_source")) {
9,647 !
706
    write_initial_source = get_node_value_bool(root, "write_initial_source");
×
707
  }
708

709
  // Get relative number of lost particles
710
  if (check_for_node(root, "source_rejection_fraction")) {
9,647 ✔
711
    source_rejection_fraction =
14 ✔
712
      std::stod(get_node_value(root, "source_rejection_fraction"));
14 !
713
  }
714

715
  if (check_for_node(root, "free_gas_threshold")) {
9,647 !
716
    free_gas_threshold = std::stod(get_node_value(root, "free_gas_threshold"));
×
717
  }
718

719
  // Surface grazing
720
  if (check_for_node(root, "surface_grazing_cutoff"))
9,647 !
721
    surface_grazing_cutoff =
×
722
      std::stod(get_node_value(root, "surface_grazing_cutoff"));
×
723
  if (check_for_node(root, "surface_grazing_ratio"))
9,647 !
724
    surface_grazing_ratio =
×
725
      std::stod(get_node_value(root, "surface_grazing_ratio"));
×
726

727
  // Survival biasing
728
  if (check_for_node(root, "survival_biasing")) {
9,647 ✔
729
    survival_biasing = get_node_value_bool(root, "survival_biasing");
227 ✔
730
  }
731

732
  // Probability tables
733
  if (check_for_node(root, "ptables")) {
9,647 ✔
734
    urr_ptables_on = get_node_value_bool(root, "ptables");
15 ✔
735
  }
736

737
  // Cutoffs
738
  if (check_for_node(root, "cutoff")) {
9,647 ✔
739
    xml_node node_cutoff = root.child("cutoff");
138 ✔
740
    if (check_for_node(node_cutoff, "weight")) {
138 ✔
741
      weight_cutoff = std::stod(get_node_value(node_cutoff, "weight"));
30 ✔
742
    }
743
    if (check_for_node(node_cutoff, "weight_avg")) {
138 ✔
744
      weight_survive = std::stod(get_node_value(node_cutoff, "weight_avg"));
30 ✔
745
    }
746
    if (check_for_node(node_cutoff, "survival_normalization")) {
138 !
747
      survival_normalization =
×
748
        get_node_value_bool(node_cutoff, "survival_normalization");
×
749
    }
750
    if (check_for_node(node_cutoff, "energy_neutron")) {
138 ✔
751
      energy_cutoff[0] =
15 ✔
752
        std::stod(get_node_value(node_cutoff, "energy_neutron"));
30 ✔
753
    } else if (check_for_node(node_cutoff, "energy")) {
123 !
754
      warning("The use of an <energy> cutoff is deprecated and should "
×
755
              "be replaced by <energy_neutron>.");
756
      energy_cutoff[0] = std::stod(get_node_value(node_cutoff, "energy"));
×
757
    }
758
    if (check_for_node(node_cutoff, "energy_photon")) {
138 ✔
759
      energy_cutoff[1] =
82 ✔
760
        std::stod(get_node_value(node_cutoff, "energy_photon"));
164 ✔
761
    }
762
    if (check_for_node(node_cutoff, "energy_electron")) {
138 !
763
      energy_cutoff[2] =
×
764
        std::stof(get_node_value(node_cutoff, "energy_electron"));
×
765
    }
766
    if (check_for_node(node_cutoff, "energy_positron")) {
138 !
767
      energy_cutoff[3] =
×
768
        std::stod(get_node_value(node_cutoff, "energy_positron"));
×
769
    }
770
    if (check_for_node(node_cutoff, "time_neutron")) {
138 ✔
771
      time_cutoff[0] = std::stod(get_node_value(node_cutoff, "time_neutron"));
26 ✔
772
    }
773
    if (check_for_node(node_cutoff, "time_photon")) {
138 !
774
      time_cutoff[1] = std::stod(get_node_value(node_cutoff, "time_photon"));
×
775
    }
776
    if (check_for_node(node_cutoff, "time_electron")) {
138 !
777
      time_cutoff[2] = std::stod(get_node_value(node_cutoff, "time_electron"));
×
778
    }
779
    if (check_for_node(node_cutoff, "time_positron")) {
138 !
780
      time_cutoff[3] = std::stod(get_node_value(node_cutoff, "time_positron"));
×
781
    }
782
  }
783

784
  // read properties from file
785
  if (check_for_node(root, "properties_file")) {
9,647 ✔
786
    properties_file = get_node_value(root, "properties_file");
11 ✔
787
    if (!file_exists(properties_file)) {
11 !
788
      fatal_error(fmt::format("File '{}' does not exist.", properties_file));
×
789
    }
790
  }
791

792
  // Particle trace
793
  if (check_for_node(root, "trace")) {
9,647 ✔
794
    auto temp = get_node_array<int64_t>(root, "trace");
15 ✔
795
    if (temp.size() != 3) {
15 !
796
      fatal_error("Must provide 3 integers for <trace> that specify the "
×
797
                  "batch, generation, and particle number.");
798
    }
799
    trace_batch = temp.at(0);
15 ✔
800
    trace_gen = temp.at(1);
15 ✔
801
    trace_particle = temp.at(2);
15 ✔
802
  }
15 ✔
803

804
  // Particle tracks
805
  if (check_for_node(root, "track")) {
9,647 ✔
806
    // Get values and make sure there are three per particle
807
    auto temp = get_node_array<int>(root, "track");
45 ✔
808
    if (temp.size() % 3 != 0) {
45 !
809
      fatal_error(
×
810
        "Number of integers specified in 'track' is not "
811
        "divisible by 3.  Please provide 3 integers per particle to be "
812
        "tracked.");
813
    }
814

815
    // Reshape into track_identifiers
816
    int n_tracks = temp.size() / 3;
45 ✔
817
    for (int i = 0; i < n_tracks; ++i) {
180 ✔
818
      track_identifiers.push_back(
135 ✔
819
        {temp[3 * i], temp[3 * i + 1], temp[3 * i + 2]});
135 ✔
820
    }
821
  }
45 ✔
822

823
  // Shannon entropy
824
  if (solver_type == SolverType::RANDOM_RAY) {
9,647 ✔
825
    if (check_for_node(root, "entropy_mesh")) {
1,061 !
826
      fatal_error("Random ray uses FSRs to compute the Shannon entropy. "
×
827
                  "No user-defined entropy mesh is supported.");
828
    }
829
    entropy_on = true;
1,061 ✔
830
  } else if (solver_type == SolverType::MONTE_CARLO) {
8,586 !
831
    if (check_for_node(root, "entropy_mesh")) {
8,586 ✔
832
      int temp = std::stoi(get_node_value(root, "entropy_mesh"));
668 ✔
833
      if (model::mesh_map.find(temp) == model::mesh_map.end()) {
334 !
834
        fatal_error(fmt::format(
×
835
          "Mesh {} specified for Shannon entropy does not exist.", temp));
836
      }
837

838
      auto* m = dynamic_cast<RegularMesh*>(
334 !
839
        model::meshes[model::mesh_map.at(temp)].get());
334 !
840
      if (!m)
334 !
841
        fatal_error("Only regular meshes can be used as an entropy mesh");
×
842
      simulation::entropy_mesh = m;
334 ✔
843

844
      // Turn on Shannon entropy calculation
845
      entropy_on = true;
334 ✔
846

847
    } else if (check_for_node(root, "entropy")) {
8,252 !
848
      fatal_error(
×
849
        "Specifying a Shannon entropy mesh via the <entropy> element "
850
        "is deprecated. Please create a mesh using <mesh> and then reference "
851
        "it by specifying its ID in an <entropy_mesh> element.");
852
    }
853
  }
854
  // Uniform fission source weighting mesh
855
  if (check_for_node(root, "ufs_mesh")) {
9,647 ✔
856
    auto temp = std::stoi(get_node_value(root, "ufs_mesh"));
30 ✔
857
    if (model::mesh_map.find(temp) == model::mesh_map.end()) {
15 !
858
      fatal_error(fmt::format("Mesh {} specified for uniform fission site "
×
859
                              "method does not exist.",
860
        temp));
861
    }
862

863
    auto* m =
15 ✔
864
      dynamic_cast<RegularMesh*>(model::meshes[model::mesh_map.at(temp)].get());
15 !
865
    if (!m)
15 !
866
      fatal_error("Only regular meshes can be used as a UFS mesh");
×
867
    simulation::ufs_mesh = m;
15 ✔
868

869
    // Turn on uniform fission source weighting
870
    ufs_on = true;
15 ✔
871

872
  } else if (check_for_node(root, "uniform_fs")) {
9,632 !
873
    fatal_error(
×
874
      "Specifying a UFS mesh via the <uniform_fs> element "
875
      "is deprecated. Please create a mesh using <mesh> and then reference "
876
      "it by specifying its ID in a <ufs_mesh> element.");
877
  }
878

879
  // Check if the user has specified to write state points
880
  if (check_for_node(root, "state_point")) {
9,647 ✔
881

882
    // Get pointer to state_point node
883
    auto node_sp = root.child("state_point");
160 ✔
884

885
    // Determine number of batches at which to store state points
886
    if (check_for_node(node_sp, "batches")) {
160 !
887
      // User gave specific batches to write state points
888
      auto temp = get_node_array<int>(node_sp, "batches");
160 ✔
889
      for (const auto& b : temp) {
491 ✔
890
        statepoint_batch.insert(b);
331 ✔
891
      }
892
    } else {
160 ✔
893
      // If neither were specified, write state point at last batch
894
      statepoint_batch.insert(n_batches);
×
895
    }
896
  } else {
897
    // If no <state_point> tag was present, by default write state point at
898
    // last batch only
899
    statepoint_batch.insert(n_batches);
9,487 ✔
900
  }
901

902
  // Check if the user has specified to write source points
903
  if (check_for_node(root, "source_point")) {
9,647 ✔
904
    // Get source_point node
905
    xml_node node_sp = root.child("source_point");
101 ✔
906

907
    // Determine batches at which to store source points
908
    if (check_for_node(node_sp, "batches")) {
101 ✔
909
      // User gave specific batches to write source points
910
      auto temp = get_node_array<int>(node_sp, "batches");
45 ✔
911
      for (const auto& b : temp) {
120 ✔
912
        sourcepoint_batch.insert(b);
75 ✔
913
      }
914
    } else {
45 ✔
915
      // If neither were specified, write source points with state points
916
      sourcepoint_batch = statepoint_batch;
56 !
917
    }
918

919
    // Check if the user has specified to write binary source file
920
    if (check_for_node(node_sp, "separate")) {
101 ✔
921
      source_separate = get_node_value_bool(node_sp, "separate");
71 ✔
922
    }
923
    if (check_for_node(node_sp, "write")) {
101 !
924
      source_write = get_node_value_bool(node_sp, "write");
×
925
    }
926
    if (check_for_node(node_sp, "mcpl")) {
101 ✔
927
      source_mcpl_write = get_node_value_bool(node_sp, "mcpl");
26 ✔
928
    }
929
    if (check_for_node(node_sp, "overwrite_latest")) {
101 ✔
930
      source_latest = get_node_value_bool(node_sp, "overwrite_latest");
15 ✔
931
      source_separate = source_latest;
15 ✔
932
    }
933
  } else {
934
    // If no <source_point> tag was present, by default we keep source bank in
935
    // statepoint file and write it out at statepoints intervals
936
    source_separate = false;
9,546 ✔
937
    sourcepoint_batch = statepoint_batch;
9,546 !
938
  }
939

940
  // Check is the user specified to convert strength to statistical weight
941
  if (check_for_node(root, "uniform_source_sampling")) {
9,647 ✔
942
    uniform_source_sampling =
55 ✔
943
      get_node_value_bool(root, "uniform_source_sampling");
55 ✔
944
  }
945

946
  // Check if the user has specified to write surface source
947
  if (check_for_node(root, "surf_source_write")) {
9,647 ✔
948
    surf_source_write = true;
412 ✔
949
    // Get surface source write node
950
    xml_node node_ssw = root.child("surf_source_write");
412 ✔
951

952
    // Determine surface ids at which crossing particles are to be banked.
953
    // If no surfaces are specified, all surfaces in the model will be used
954
    // to bank source points.
955
    if (check_for_node(node_ssw, "surface_ids")) {
412 ✔
956
      auto temp = get_node_array<int>(node_ssw, "surface_ids");
202 ✔
957
      for (const auto& b : temp) {
994 ✔
958
        source_write_surf_id.insert(b);
792 ✔
959
      }
960
    }
202 ✔
961

962
    // Get maximum number of particles to be banked per surface
963
    if (check_for_node(node_ssw, "max_particles")) {
412 ✔
964
      ssw_max_particles = std::stoll(get_node_value(node_ssw, "max_particles"));
806 ✔
965
    } else {
966
      fatal_error("A maximum number of particles needs to be specified "
9 ✔
967
                  "using the 'max_particles' parameter to store surface "
968
                  "source points.");
969
    }
970

971
    // Get maximum number of surface source files to be created
972
    if (check_for_node(node_ssw, "max_source_files")) {
403 ✔
973
      ssw_max_files = std::stoll(get_node_value(node_ssw, "max_source_files"));
66 ✔
974
    } else {
975
      ssw_max_files = 1;
370 ✔
976
    }
977

978
    if (check_for_node(node_ssw, "mcpl")) {
403 ✔
979
      surf_mcpl_write = get_node_value_bool(node_ssw, "mcpl");
11 ✔
980
    }
981
    // Get cell information
982
    if (check_for_node(node_ssw, "cell")) {
403 ✔
983
      ssw_cell_id = std::stoll(get_node_value(node_ssw, "cell"));
208 ✔
984
      ssw_cell_type = SSWCellType::Both;
104 ✔
985
    }
986
    if (check_for_node(node_ssw, "cellfrom")) {
403 ✔
987
      if (ssw_cell_id != C_NONE) {
90 ✔
988
        fatal_error(
18 ✔
989
          "'cell', 'cellfrom' and 'cellto' cannot be used at the same time.");
990
      }
991
      ssw_cell_id = std::stoll(get_node_value(node_ssw, "cellfrom"));
144 ✔
992
      ssw_cell_type = SSWCellType::From;
72 ✔
993
    }
994
    if (check_for_node(node_ssw, "cellto")) {
385 ✔
995
      if (ssw_cell_id != C_NONE) {
71 ✔
996
        fatal_error(
18 ✔
997
          "'cell', 'cellfrom' and 'cellto' cannot be used at the same time.");
998
      }
999
      ssw_cell_id = std::stoll(get_node_value(node_ssw, "cellto"));
106 ✔
1000
      ssw_cell_type = SSWCellType::To;
53 ✔
1001
    }
1002
  }
1003

1004
  // Check if the user has specified to write specific collisions
1005
  if (check_for_node(root, "collision_track")) {
9,602 ✔
1006
    settings::collision_track = true;
160 ✔
1007
    // Get collision track node
1008
    xml_node node_ct = root.child("collision_track");
160 ✔
1009
    collision_track_config = CollisionTrackConfig {};
160 ✔
1010

1011
    // Determine cell ids at which crossing particles are to be banked
1012
    if (check_for_node(node_ct, "cell_ids")) {
160 ✔
1013
      auto temp = get_node_array<int>(node_ct, "cell_ids");
89 ✔
1014
      for (const auto& b : temp) {
237 ✔
1015
        collision_track_config.cell_ids.insert(b);
148 ✔
1016
      }
1017
    }
89 ✔
1018
    if (check_for_node(node_ct, "reactions")) {
160 ✔
1019
      auto temp = get_node_array<std::string>(node_ct, "reactions");
63 ✔
1020
      for (const auto& b : temp) {
171 ✔
1021
        int reaction_int = reaction_mt(b);
108 ✔
1022
        if (reaction_int > 0) {
108 !
1023
          collision_track_config.mt_numbers.insert(reaction_int);
108 ✔
1024
        }
1025
      }
1026
    }
63 ✔
1027
    if (check_for_node(node_ct, "universe_ids")) {
160 ✔
1028
      auto temp = get_node_array<int>(node_ct, "universe_ids");
30 ✔
1029
      for (const auto& b : temp) {
60 ✔
1030
        collision_track_config.universe_ids.insert(b);
30 ✔
1031
      }
1032
    }
30 ✔
1033
    if (check_for_node(node_ct, "material_ids")) {
160 ✔
1034
      auto temp = get_node_array<int>(node_ct, "material_ids");
30 ✔
1035
      for (const auto& b : temp) {
75 ✔
1036
        collision_track_config.material_ids.insert(b);
45 ✔
1037
      }
1038
    }
30 ✔
1039
    if (check_for_node(node_ct, "nuclides")) {
160 ✔
1040
      auto temp = get_node_array<std::string>(node_ct, "nuclides");
30 ✔
1041
      for (const auto& b : temp) {
120 ✔
1042
        collision_track_config.nuclides.insert(b);
90 ✔
1043
      }
1044
    }
30 ✔
1045
    if (check_for_node(node_ct, "deposited_E_threshold")) {
160 ✔
1046
      collision_track_config.deposited_energy_threshold =
60 ✔
1047
        std::stod(get_node_value(node_ct, "deposited_E_threshold"));
60 ✔
1048
    }
1049
    // Get maximum number of particles to be banked per collision
1050
    if (check_for_node(node_ct, "max_collisions")) {
160 !
1051
      collision_track_config.max_collisions =
320 ✔
1052
        std::stoll(get_node_value(node_ct, "max_collisions"));
320 ✔
1053
    } else {
1054
      warning("A maximum number of collisions needs to be specified. "
×
1055
              "By default the code sets 'max_collisions' parameter equals to "
1056
              "1000.");
1057
    }
1058
    // Get maximum number of collision_track files to be created
1059
    if (check_for_node(node_ct, "max_collision_track_files")) {
160 !
1060
      collision_track_config.max_files =
×
1061
        std::stoll(get_node_value(node_ct, "max_collision_track_files"));
×
1062
    }
1063
    if (check_for_node(node_ct, "mcpl")) {
160 ✔
1064
      collision_track_config.mcpl_write = get_node_value_bool(node_ct, "mcpl");
22 ✔
1065
    }
1066
  }
1067

1068
  // If source is not separate and is to be written out in the statepoint
1069
  // file, make sure that the sourcepoint batch numbers are contained in the
1070
  // statepoint list
1071
  if (!source_separate) {
9,602 ✔
1072
    for (const auto& b : sourcepoint_batch) {
19,158 ✔
1073
      if (!contains(statepoint_batch, b)) {
19,284 !
1074
        fatal_error(
×
1075
          "Sourcepoint batches are not a subset of statepoint batches.");
1076
      }
1077
    }
1078
  }
1079

1080
  // Check if the user has specified to not reduce tallies at the end of every
1081
  // batch
1082
  if (check_for_node(root, "no_reduce")) {
9,602 ✔
1083
    reduce_tallies = !get_node_value_bool(root, "no_reduce");
30 ✔
1084
  }
1085

1086
  // Check if the user has specified to use confidence intervals for
1087
  // uncertainties rather than standard deviations
1088
  if (check_for_node(root, "confidence_intervals")) {
9,602 ✔
1089
    confidence_intervals = get_node_value_bool(root, "confidence_intervals");
15 ✔
1090
  }
1091

1092
  // Check for output options
1093
  if (check_for_node(root, "output")) {
9,602 ✔
1094
    // Get pointer to output node
1095
    pugi::xml_node node_output = root.child("output");
892 ✔
1096

1097
    // Check for summary option
1098
    if (check_for_node(node_output, "summary")) {
892 ✔
1099
      output_summary = get_node_value_bool(node_output, "summary");
866 ✔
1100
    }
1101

1102
    // Check for ASCII tallies output option
1103
    if (check_for_node(node_output, "tallies")) {
892 ✔
1104
      output_tallies = get_node_value_bool(node_output, "tallies");
382 ✔
1105
    }
1106

1107
    // Set output directory if a path has been specified
1108
    if (check_for_node(node_output, "path")) {
892 !
1109
      path_output = get_node_value(node_output, "path");
×
1110
      if (!ends_with(path_output, "/")) {
×
1111
        path_output += "/";
892 !
1112
      }
1113
    }
1114
  }
1115

1116
  // Resonance scattering parameters
1117
  if (check_for_node(root, "resonance_scattering")) {
9,602 ✔
1118
    xml_node node_res_scat = root.child("resonance_scattering");
15 ✔
1119

1120
    // See if resonance scattering is enabled
1121
    if (check_for_node(node_res_scat, "enable")) {
15 !
1122
      res_scat_on = get_node_value_bool(node_res_scat, "enable");
15 ✔
1123
    } else {
1124
      res_scat_on = true;
×
1125
    }
1126

1127
    // Determine what method is used
1128
    if (check_for_node(node_res_scat, "method")) {
15 !
1129
      auto temp = get_node_value(node_res_scat, "method", true, true);
15 ✔
1130
      if (temp == "rvs") {
15 !
1131
        res_scat_method = ResScatMethod::rvs;
15 ✔
1132
      } else if (temp == "dbrc") {
×
1133
        res_scat_method = ResScatMethod::dbrc;
×
1134
      } else {
1135
        fatal_error(
×
1136
          "Unrecognized resonance elastic scattering method: " + temp + ".");
×
1137
      }
1138
    }
15 ✔
1139

1140
    // Minimum energy for resonance scattering
1141
    if (check_for_node(node_res_scat, "energy_min")) {
15 !
1142
      res_scat_energy_min =
30 ✔
1143
        std::stod(get_node_value(node_res_scat, "energy_min"));
30 ✔
1144
    }
1145
    if (res_scat_energy_min < 0.0) {
15 !
1146
      fatal_error("Lower resonance scattering energy bound is negative");
×
1147
    }
1148

1149
    // Maximum energy for resonance scattering
1150
    if (check_for_node(node_res_scat, "energy_max")) {
15 !
1151
      res_scat_energy_max =
30 ✔
1152
        std::stod(get_node_value(node_res_scat, "energy_max"));
30 ✔
1153
    }
1154
    if (res_scat_energy_max < res_scat_energy_min) {
15 !
1155
      fatal_error("Upper resonance scattering energy bound is below the "
×
1156
                  "lower resonance scattering energy bound.");
1157
    }
1158

1159
    // Get resonance scattering nuclides
1160
    if (check_for_node(node_res_scat, "nuclides")) {
15 !
1161
      res_scat_nuclides =
15 ✔
1162
        get_node_array<std::string>(node_res_scat, "nuclides");
30 ✔
1163
    }
1164
  }
1165

1166
  // Get volume calculations
1167
  for (pugi::xml_node node_vol : root.children("volume_calc")) {
9,912 ✔
1168
    model::volume_calcs.emplace_back(node_vol);
310 ✔
1169
  }
1170

1171
  // Get temperature settings
1172
  if (check_for_node(root, "temperature_default")) {
9,602 ✔
1173
    temperature_default =
342 ✔
1174
      std::stod(get_node_value(root, "temperature_default"));
342 ✔
1175
  }
1176
  if (check_for_node(root, "temperature_method")) {
9,602 ✔
1177
    auto temp = get_node_value(root, "temperature_method", true, true);
485 ✔
1178
    if (temp == "nearest") {
485 ✔
1179
      temperature_method = TemperatureMethod::NEAREST;
304 ✔
1180
    } else if (temp == "interpolation") {
181 !
1181
      temperature_method = TemperatureMethod::INTERPOLATION;
181 ✔
1182
    } else {
1183
      fatal_error("Unknown temperature method: " + temp);
×
1184
    }
1185
  }
485 ✔
1186
  if (check_for_node(root, "temperature_tolerance")) {
9,602 ✔
1187
    temperature_tolerance =
680 ✔
1188
      std::stod(get_node_value(root, "temperature_tolerance"));
680 ✔
1189
  }
1190
  if (check_for_node(root, "temperature_multipole")) {
9,602 ✔
1191
    temperature_multipole = get_node_value_bool(root, "temperature_multipole");
185 ✔
1192

1193
    // Multipole currently doesn't work with photon transport
1194
    if (temperature_multipole && photon_transport) {
185 !
1195
      fatal_error("Multipole data cannot currently be used in conjunction with "
×
1196
                  "photon transport.");
1197
    }
1198
  }
1199
  if (check_for_node(root, "temperature_range")) {
9,602 ✔
1200
    auto range = get_node_array<double>(root, "temperature_range");
170 ✔
1201
    temperature_range[0] = range.at(0);
170 ✔
1202
    temperature_range[1] = range.at(1);
170 ✔
1203
  }
170 ✔
1204

1205
  // Check for tabular_legendre options
1206
  if (check_for_node(root, "tabular_legendre")) {
9,602 ✔
1207
    // Get pointer to tabular_legendre node
1208
    xml_node node_tab_leg = root.child("tabular_legendre");
105 ✔
1209

1210
    // Check for enable option
1211
    if (check_for_node(node_tab_leg, "enable")) {
105 !
1212
      legendre_to_tabular = get_node_value_bool(node_tab_leg, "enable");
105 ✔
1213
    }
1214

1215
    // Check for the number of points
1216
    if (check_for_node(node_tab_leg, "num_points")) {
105 !
1217
      legendre_to_tabular_points =
×
1218
        std::stoi(get_node_value(node_tab_leg, "num_points"));
×
1219
      if (legendre_to_tabular_points <= 1 && !run_CE) {
×
1220
        fatal_error(
×
1221
          "The 'num_points' subelement/attribute of the "
1222
          "<tabular_legendre> element must contain a value greater than 1");
1223
      }
1224
    }
1225
  }
1226

1227
  // Check whether create delayed neutrons in fission
1228
  if (check_for_node(root, "create_delayed_neutrons")) {
9,602 !
1229
    create_delayed_neutrons =
×
1230
      get_node_value_bool(root, "create_delayed_neutrons");
×
1231
  }
1232

1233
  // Check whether create fission sites
1234
  if (run_mode == RunMode::FIXED_SOURCE) {
9,602 ✔
1235
    if (check_for_node(root, "create_fission_neutrons")) {
3,688 ✔
1236
      create_fission_neutrons =
336 ✔
1237
        get_node_value_bool(root, "create_fission_neutrons");
336 ✔
1238
    }
1239
  }
1240

1241
  // Check whether to scale fission photon yields
1242
  if (check_for_node(root, "delayed_photon_scaling")) {
9,602 !
1243
    delayed_photon_scaling =
×
1244
      get_node_value_bool(root, "delayed_photon_scaling");
×
1245
  }
1246

1247
  // Check whether to use event-based parallelism
1248
  if (check_for_node(root, "event_based")) {
9,602 !
1249
    event_based = get_node_value_bool(root, "event_based");
×
1250
  }
1251

1252
  // Check whether material cell offsets should be generated
1253
  if (check_for_node(root, "material_cell_offsets")) {
9,602 !
1254
    material_cell_offsets = get_node_value_bool(root, "material_cell_offsets");
×
1255
  }
1256

1257
  // Weight window information
1258
  for (pugi::xml_node node_ww : root.children("weight_windows")) {
9,907 ✔
1259
    variance_reduction::weight_windows.emplace_back(
305 ✔
1260
      std::make_unique<WeightWindows>(node_ww));
610 ✔
1261
  }
1262

1263
  // Enable weight windows by default if one or more are present
1264
  if (variance_reduction::weight_windows.size() > 0)
9,602 ✔
1265
    settings::weight_windows_on = true;
209 ✔
1266

1267
  // read weight windows from file
1268
  if (check_for_node(root, "weight_windows_file")) {
9,602 ✔
1269
    weight_windows_file = get_node_value(root, "weight_windows_file");
26 ✔
1270
    weight_windows_on = true;
26 ✔
1271
  }
1272

1273
  // read settings for weight windows value, this will override
1274
  // the automatic setting even if weight windows are present
1275
  if (check_for_node(root, "weight_windows_on")) {
9,602 ✔
1276
    weight_windows_on = get_node_value_bool(root, "weight_windows_on");
93 ✔
1277
  }
1278

1279
  if (check_for_node(root, "max_secondaries")) {
9,602 !
1280
    settings::max_secondaries =
×
1281
      std::stoi(get_node_value(root, "max_secondaries"));
×
1282
  }
1283

1284
  if (check_for_node(root, "max_history_splits")) {
9,602 ✔
1285
    settings::max_history_splits =
798 ✔
1286
      std::stoi(get_node_value(root, "max_history_splits"));
798 ✔
1287
  }
1288

1289
  if (check_for_node(root, "max_tracks")) {
9,602 ✔
1290
    settings::max_tracks = std::stoi(get_node_value(root, "max_tracks"));
90 ✔
1291
  }
1292

1293
  // Create weight window generator objects
1294
  if (check_for_node(root, "weight_window_generators")) {
9,602 ✔
1295
    auto wwgs_node = root.child("weight_window_generators");
142 ✔
1296
    for (pugi::xml_node node_wwg :
284 ✔
1297
      wwgs_node.children("weight_windows_generator")) {
284 ✔
1298
      variance_reduction::weight_windows_generators.emplace_back(
142 ✔
1299
        std::make_unique<WeightWindowsGenerator>(node_wwg));
284 ✔
1300
    }
1301
    // if any of the weight windows are intended to be generated otf, make
1302
    // sure they're applied
1303
    for (const auto& wwg : variance_reduction::weight_windows_generators) {
142 !
1304
      if (wwg->on_the_fly_) {
142 !
1305
        settings::weight_windows_on = true;
142 ✔
1306
        break;
142 ✔
1307
      }
1308
    }
1309
    // If any weight window generators have local FW-CADIS target tallies,
1310
    // user-defined adjoint sources cannot be used at the same time.
1311
    if (!model::adjoint_sources.empty()) {
142 !
1312
      for (const auto& wwg : variance_reduction::weight_windows_generators) {
×
1313
        if (!wwg->targets_.empty()) {
×
1314
          fatal_error("Cannot use both user-defined adjoint sources and "
×
1315
                      "FW-CADIS target tallies at the same time.");
1316
        }
1317
      }
1318
    }
1319
  }
1320

1321
  // Set up weight window checkpoints
1322
  if (check_for_node(root, "weight_window_checkpoints")) {
9,602 ✔
1323
    xml_node ww_checkpoints = root.child("weight_window_checkpoints");
184 ✔
1324
    if (check_for_node(ww_checkpoints, "collision")) {
184 !
1325
      weight_window_checkpoint_collision =
184 ✔
1326
        get_node_value_bool(ww_checkpoints, "collision");
184 ✔
1327
    }
1328
    if (check_for_node(ww_checkpoints, "surface")) {
184 !
1329
      weight_window_checkpoint_surface =
184 ✔
1330
        get_node_value_bool(ww_checkpoints, "surface");
184 ✔
1331
    }
1332
  }
1333

1334
  if (weight_windows_on) {
9,602 ✔
1335
    if (!weight_window_checkpoint_surface &&
366 ✔
1336
        !weight_window_checkpoint_collision)
193 !
1337
      fatal_error(
×
1338
        "Weight Windows are enabled but there are no valid checkpoints.");
1339
  }
1340

1341
  if (check_for_node(root, "use_decay_photons")) {
9,602 ✔
1342
    settings::use_decay_photons =
11 ✔
1343
      get_node_value_bool(root, "use_decay_photons");
11 ✔
1344
  }
1345

1346
  // If weight windows are on, also enable shared secondary bank (unless
1347
  // explicitly disabled by user).
1348
  if (check_for_node(root, "shared_secondary_bank")) {
9,602 ✔
1349
    bool val = get_node_value_bool(root, "shared_secondary_bank");
335 ✔
1350
    if (val && run_mode == RunMode::EIGENVALUE) {
335 !
1351
      warning(
×
1352
        "Shared secondary bank is not supported in eigenvalue calculations. "
1353
        "Setting will be ignored.");
1354
    } else {
1355
      settings::use_shared_secondary_bank = val;
335 ✔
1356
    }
1357
  } else if (settings::weight_windows_on) {
9,267 ✔
1358
    if (run_mode == RunMode::EIGENVALUE) {
184 !
1359
      warning(
×
1360
        "Shared secondary bank is not supported in eigenvalue calculations. "
1361
        "Particle local secondary banks will be used instead.");
1362
    } else if (run_mode == RunMode::FIXED_SOURCE) {
184 !
1363
      settings::use_shared_secondary_bank = true;
184 ✔
1364
    }
1365
  }
1366
}
9,602 ✔
1367

1368
void free_memory_settings()
9,729 ✔
1369
{
1370
  settings::statepoint_batch.clear();
9,729 ✔
1371
  settings::sourcepoint_batch.clear();
9,729 ✔
1372
  settings::source_write_surf_id.clear();
9,729 ✔
1373
  settings::res_scat_nuclides.clear();
9,729 ✔
1374
  settings::track_identifiers.clear();
9,729 ✔
1375
  settings::ifp_delayed_group_on = false;
9,729 ✔
1376
  settings::ifp_lifetime_on = false;
9,729 ✔
1377
}
9,729 ✔
1378

1379
//==============================================================================
1380
// C API functions
1381
//==============================================================================
1382

1383
namespace {
1384

1385
int invalid_setting(const char* type, const char* name)
×
1386
{
1387
  set_errmsg(fmt::format("Unknown {} setting '{}'.", type, name));
×
1388
  return OPENMC_E_INVALID_ARGUMENT;
×
1389
}
1390

1391
bool* bool_setting(const char* name)
4,466 ✔
1392
{
1393
  if (std::strcmp(name, "cmfd_run") == 0) {
4,466 ✔
1394
    return &settings::cmfd_run;
1395
  } else if (std::strcmp(name, "entropy_on") == 0) {
4,246 ✔
1396
    return &settings::entropy_on;
1397
  } else if (std::strcmp(name, "event_based") == 0) {
1,892 ✔
1398
    return &settings::event_based;
1399
  } else if (std::strcmp(name, "need_depletion_rx") == 0) {
1,672 ✔
1400
    return &simulation::need_depletion_rx;
1401
  } else if (std::strcmp(name, "photon_transport") == 0) {
814 ✔
1402
    return &settings::photon_transport;
1403
  } else if (std::strcmp(name, "output_summary") == 0) {
759 ✔
1404
    return &settings::output_summary;
1405
  } else if (std::strcmp(name, "reduce_tallies") == 0) {
704 ✔
1406
    return &settings::reduce_tallies;
1407
  } else if (std::strcmp(name, "restart_run") == 0) {
649 ✔
1408
    return &settings::restart_run;
1409
  } else if (std::strcmp(name, "run_ce") == 0) {
429 ✔
1410
    return &settings::run_CE;
1411
  } else if (std::strcmp(name, "trigger_on") == 0) {
220 ✔
1412
    return &settings::trigger_on;
1413
  } else if (std::strcmp(name, "weight_windows_on") == 0) {
165 !
1414
    return &settings::weight_windows_on;
165 ✔
1415
  }
1416
  return nullptr;
1417
}
1418

1419
} // namespace
1420

1421
extern "C" int openmc_setting_get_bool(const char* name, bool* value)
3,069 ✔
1422
{
1423
  if (!name || !value) {
3,069 !
1424
    set_errmsg("Setting name and output pointer must not be null.");
×
1425
    return OPENMC_E_INVALID_ARGUMENT;
×
1426
  }
1427

1428
  bool* setting = bool_setting(name);
3,069 ✔
1429
  if (!setting)
3,069 !
1430
    return invalid_setting("boolean", name);
×
1431

1432
  *value = *setting;
3,069 ✔
1433
  return 0;
3,069 ✔
1434
}
1435

1436
extern "C" int openmc_setting_set_bool(const char* name, bool value)
1,397 ✔
1437
{
1438
  if (!name) {
1,397 !
1439
    set_errmsg("Setting name must not be null.");
×
1440
    return OPENMC_E_INVALID_ARGUMENT;
×
1441
  }
1442

1443
  bool* setting = bool_setting(name);
1,397 ✔
1444
  if (!setting)
1,397 !
1445
    return invalid_setting("boolean", name);
×
1446

1447
  *setting = value;
1,397 ✔
1448
  return 0;
1,397 ✔
1449
}
1450

1451
extern "C" int openmc_setting_get_int32(const char* name, int32_t* value)
1,221 ✔
1452
{
1453
  if (!name || !value) {
1,221 !
1454
    set_errmsg("Setting name and output pointer must not be null.");
×
1455
    return OPENMC_E_INVALID_ARGUMENT;
×
1456
  }
1457

1458
  if (std::strcmp(name, "gen_per_batch") == 0) {
1,221 ✔
1459
    *value = settings::gen_per_batch;
44 ✔
1460
  } else if (std::strcmp(name, "max_lost_particles") == 0) {
1,177 ✔
1461
    *value = settings::max_lost_particles;
33 ✔
1462
  } else if (std::strcmp(name, "max_write_lost_particles") == 0) {
1,144 ✔
1463
    *value = settings::max_write_lost_particles;
33 ✔
1464
  } else if (std::strcmp(name, "n_inactive") == 0) {
1,111 ✔
1465
    *value = settings::n_inactive;
44 ✔
1466
  } else if (std::strcmp(name, "run_mode") == 0) {
1,067 ✔
1467
    *value = static_cast<int32_t>(settings::run_mode);
22 ✔
1468
  } else if (std::strcmp(name, "verbosity") == 0) {
1,045 !
1469
    *value = settings::verbosity;
1,045 ✔
1470
  } else {
1471
    return invalid_setting("int32", name);
×
1472
  }
1473
  return 0;
1474
}
1475

1476
extern "C" int openmc_setting_set_int32(const char* name, int32_t value)
132 ✔
1477
{
1478
  if (!name) {
132 !
1479
    set_errmsg("Setting name must not be null.");
×
1480
    return OPENMC_E_INVALID_ARGUMENT;
×
1481
  }
1482

1483
  if (std::strcmp(name, "gen_per_batch") == 0) {
132 ✔
1484
    settings::gen_per_batch = value;
22 ✔
1485
  } else if (std::strcmp(name, "max_lost_particles") == 0) {
110 ✔
1486
    settings::max_lost_particles = value;
22 ✔
1487
  } else if (std::strcmp(name, "max_write_lost_particles") == 0) {
88 ✔
1488
    settings::max_write_lost_particles = value;
22 ✔
1489
  } else if (std::strcmp(name, "n_inactive") == 0) {
66 ✔
1490
    settings::n_inactive = value;
22 ✔
1491
  } else if (std::strcmp(name, "run_mode") == 0) {
44 ✔
1492
    if (value < static_cast<int32_t>(RunMode::UNSET) ||
22 !
1493
        value > static_cast<int32_t>(RunMode::VOLUME)) {
1494
      set_errmsg(fmt::format("Invalid run mode: {}.", value));
×
1495
      return OPENMC_E_INVALID_ARGUMENT;
×
1496
    }
1497
    settings::run_mode = static_cast<RunMode>(value);
22 ✔
1498
  } else if (std::strcmp(name, "verbosity") == 0) {
22 !
1499
    settings::verbosity = value;
22 ✔
1500
  } else {
1501
    return invalid_setting("int32", name);
×
1502
  }
1503
  return 0;
1504
}
1505

1506
extern "C" int openmc_setting_get_int64(const char* name, int64_t* value)
121 ✔
1507
{
1508
  if (!name || !value) {
121 !
1509
    set_errmsg("Setting name and output pointer must not be null.");
×
1510
    return OPENMC_E_INVALID_ARGUMENT;
×
1511
  }
1512

1513
  if (std::strcmp(name, "n_particles") != 0)
121 !
1514
    return invalid_setting("int64", name);
×
1515

1516
  *value = settings::n_particles;
121 ✔
1517
  return 0;
121 ✔
1518
}
1519

1520
extern "C" int openmc_setting_set_int64(const char* name, int64_t value)
99 ✔
1521
{
1522
  if (!name) {
99 !
1523
    set_errmsg("Setting name must not be null.");
×
1524
    return OPENMC_E_INVALID_ARGUMENT;
×
1525
  }
1526

1527
  if (std::strcmp(name, "n_particles") != 0)
99 !
1528
    return invalid_setting("int64", name);
×
1529

1530
  settings::n_particles = value;
99 ✔
1531
  return 0;
99 ✔
1532
}
1533

1534
extern "C" int openmc_setting_get_double(const char* name, double* value)
33 ✔
1535
{
1536
  if (!name || !value) {
33 !
1537
    set_errmsg("Setting name and output pointer must not be null.");
×
1538
    return OPENMC_E_INVALID_ARGUMENT;
×
1539
  }
1540

1541
  if (std::strcmp(name, "rel_max_lost_particles") != 0)
33 !
1542
    return invalid_setting("double", name);
×
1543

1544
  *value = settings::rel_max_lost_particles;
33 ✔
1545
  return 0;
33 ✔
1546
}
1547

1548
extern "C" int openmc_setting_set_double(const char* name, double value)
22 ✔
1549
{
1550
  if (!name) {
22 !
1551
    set_errmsg("Setting name must not be null.");
×
1552
    return OPENMC_E_INVALID_ARGUMENT;
×
1553
  }
1554

1555
  if (std::strcmp(name, "rel_max_lost_particles") != 0)
22 !
1556
    return invalid_setting("double", name);
×
1557

1558
  settings::rel_max_lost_particles = value;
22 ✔
1559
  return 0;
22 ✔
1560
}
1561

1562
extern "C" int openmc_setting_get_string(const char* name, const char** value)
22 ✔
1563
{
1564
  if (!name || !value) {
22 !
1565
    set_errmsg("Setting name and output pointer must not be null.");
×
1566
    return OPENMC_E_INVALID_ARGUMENT;
×
1567
  }
1568

1569
  if (std::strcmp(name, "path_statepoint") != 0)
22 !
1570
    return invalid_setting("string", name);
×
1571

1572
  *value = settings::path_statepoint.c_str();
22 ✔
1573
  return 0;
22 ✔
1574
}
1575

1576
extern "C" int openmc_set_n_batches(
220 ✔
1577
  int32_t n_batches, bool set_max_batches, bool add_statepoint_batch)
1578
{
1579
  if (settings::n_inactive >= n_batches) {
220 ✔
1580
    set_errmsg("Number of active batches must be greater than zero.");
11 ✔
1581
    return OPENMC_E_INVALID_ARGUMENT;
11 ✔
1582
  }
1583

1584
  if (!settings::trigger_on) {
209 ✔
1585
    // Set n_batches and n_max_batches to same value
1586
    settings::n_batches = n_batches;
187 ✔
1587
    settings::n_max_batches = n_batches;
187 ✔
1588
  } else {
1589
    // Set n_batches and n_max_batches based on value of set_max_batches
1590
    if (set_max_batches) {
22 ✔
1591
      settings::n_max_batches = n_batches;
11 ✔
1592
    } else {
1593
      settings::n_batches = n_batches;
11 ✔
1594
    }
1595
  }
1596

1597
  // Update size of k_generation and entropy
1598
  int m = settings::n_max_batches * settings::gen_per_batch;
209 ✔
1599
  simulation::k_generation.reserve(m);
209 ✔
1600
  simulation::entropy.reserve(m);
209 ✔
1601

1602
  // Add value of n_batches to statepoint_batch
1603
  if (add_statepoint_batch &&
209 ✔
1604
      !(contains(settings::statepoint_batch, n_batches)))
198 ✔
1605
    settings::statepoint_batch.insert(n_batches);
33 ✔
1606

1607
  return 0;
1608
}
1609

1610
extern "C" int openmc_get_n_batches(int* n_batches, bool get_max_batches)
2,530 ✔
1611
{
1612
  *n_batches = get_max_batches ? settings::n_max_batches : settings::n_batches;
2,530 ✔
1613

1614
  return 0;
2,530 ✔
1615
}
1616

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