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

openmc-dev / openmc / 21489819490

29 Jan 2026 06:21PM UTC coverage: 80.077% (-1.9%) from 81.953%
21489819490

Pull #3757

github

web-flow
Merge d08626053 into f7a734189
Pull Request #3757: Testing point detectors

16004 of 22621 branches covered (70.75%)

Branch coverage included in aggregate %.

94 of 518 new or added lines in 26 files covered. (18.15%)

1021 existing lines in 52 files now uncovered.

53779 of 64524 relevant lines covered (83.35%)

8016833.26 hits per line

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

82.39
/src/simulation.cpp
1
#include "openmc/simulation.h"
2

3
#include "openmc/bank.h"
4
#include "openmc/capi.h"
5
#include "openmc/collision_track.h"
6
#include "openmc/container_util.h"
7
#include "openmc/eigenvalue.h"
8
#include "openmc/error.h"
9
#include "openmc/event.h"
10
#include "openmc/geometry_aux.h"
11
#include "openmc/ifp.h"
12
#include "openmc/material.h"
13
#include "openmc/message_passing.h"
14
#include "openmc/nuclide.h"
15
#include "openmc/output.h"
16
#include "openmc/particle.h"
17
#include "openmc/photon.h"
18
#include "openmc/random_lcg.h"
19
#include "openmc/settings.h"
20
#include "openmc/source.h"
21
#include "openmc/state_point.h"
22
#include "openmc/tallies/derivative.h"
23
#include "openmc/tallies/filter.h"
24
#include "openmc/tallies/tally.h"
25
#include "openmc/tallies/tally_scoring.h"
26
#include "openmc/tallies/trigger.h"
27
#include "openmc/timer.h"
28
#include "openmc/track_output.h"
29
#include "openmc/weight_windows.h"
30

31
#ifdef _OPENMP
32
#include <omp.h>
33
#endif
34
#include "xtensor/xview.hpp"
35

36
#ifdef OPENMC_MPI
37
#include <mpi.h>
38
#endif
39

40
#include <fmt/format.h>
41

42
#include <algorithm>
43
#include <cmath>
44
#include <string>
45

46
//==============================================================================
47
// C API functions
48
//==============================================================================
49

50
// OPENMC_RUN encompasses all the main logic where iterations are performed
51
// over the batches, generations, and histories in a fixed source or
52
// k-eigenvalue calculation.
53

54
int openmc_run()
469✔
55
{
56
  openmc::simulation::time_total.start();
469✔
57
  openmc_simulation_init();
469✔
58

59
  // Ensure that a batch isn't executed in the case that the maximum number of
60
  // batches has already been run in a restart statepoint file
61
  int status = 0;
469✔
62
  if (openmc::simulation::current_batch >= openmc::settings::n_max_batches) {
469✔
63
    status = openmc::STATUS_EXIT_MAX_BATCH;
1✔
64
  }
65

66
  int err = 0;
469✔
67
  while (status == 0 && err == 0) {
9,667!
68
    err = openmc_next_batch(&status);
9,200✔
69
  }
70

71
  openmc_simulation_finalize();
467✔
72
  openmc::simulation::time_total.stop();
467✔
73
  return err;
467✔
74
}
75

76
int openmc_simulation_init()
544✔
77
{
78
  using namespace openmc;
79

80
  // Skip if simulation has already been initialized
81
  if (simulation::initialized)
544✔
82
    return 0;
1✔
83

84
  // Initialize nuclear data (energy limits, log grid)
85
  if (settings::run_CE) {
543✔
86
    initialize_data();
460✔
87
  }
88

89
  // Determine how much work each process should do
90
  calculate_work();
543✔
91

92
  // Allocate source, fission and surface source banks.
93
  allocate_banks();
543✔
94

95
  // Create track file if needed
96
  if (!settings::track_identifiers.empty() || settings::write_all_tracks) {
543✔
97
    open_track_file();
6✔
98
  }
99

100
  // If doing an event-based simulation, intialize the particle buffer
101
  // and event queues
102
  if (settings::event_based) {
543!
103
    int64_t event_buffer_length =
UNCOV
104
      std::min(simulation::work_per_rank, settings::max_particles_in_flight);
×
UNCOV
105
    init_event_queues(event_buffer_length);
×
106
  }
107

108
  // Allocate tally results arrays if they're not allocated yet
109
  for (auto& t : model::tallies) {
2,376✔
110
    t->set_strides();
1,833✔
111
    t->init_results();
1,833✔
112
  }
113

114
  // Set up material nuclide index mapping
115
  for (auto& mat : model::materials) {
2,114✔
116
    mat->init_nuclide_index();
1,571✔
117
  }
118

119
  // Reset global variables -- this is done before loading state point (as that
120
  // will potentially populate k_generation and entropy)
121
  simulation::current_batch = 0;
543✔
122
  simulation::ct_current_file = 1;
543✔
123
  simulation::ssw_current_file = 1;
543✔
124
  simulation::k_generation.clear();
543✔
125
  simulation::entropy.clear();
543✔
126
  openmc_reset();
543✔
127

128
  // If this is a restart run, load the state point data and binary source
129
  // file
130
  if (settings::restart_run) {
543✔
131
    load_state_point();
5✔
132
    write_message("Resuming simulation...", 6);
5✔
133
  } else {
134
    // Only initialize primary source bank for eigenvalue simulations
135
    if (settings::run_mode == RunMode::EIGENVALUE &&
538✔
136
        settings::solver_type == SolverType::MONTE_CARLO) {
315✔
137
      initialize_source();
295✔
138
    }
139
  }
140

141
  // Display header
142
  if (mpi::master) {
543!
143
    if (settings::run_mode == RunMode::FIXED_SOURCE) {
543✔
144
      if (settings::solver_type == SolverType::MONTE_CARLO) {
223✔
145
        header("FIXED SOURCE TRANSPORT SIMULATION", 3);
193✔
146
      } else if (settings::solver_type == SolverType::RANDOM_RAY) {
30!
147
        header("FIXED SOURCE TRANSPORT SIMULATION (RANDOM RAY SOLVER)", 3);
30✔
148
      }
149
    } else if (settings::run_mode == RunMode::EIGENVALUE) {
320!
150
      if (settings::solver_type == SolverType::MONTE_CARLO) {
320✔
151
        header("K EIGENVALUE SIMULATION", 3);
300✔
152
      } else if (settings::solver_type == SolverType::RANDOM_RAY) {
20!
153
        header("K EIGENVALUE SIMULATION (RANDOM RAY SOLVER)", 3);
20✔
154
      }
155
      if (settings::verbosity >= 7)
320✔
156
        print_columns();
300✔
157
    }
158
  }
159

160
  // load weight windows from file
161
  if (!settings::weight_windows_file.empty()) {
543!
162
    openmc_weight_windows_import(settings::weight_windows_file.c_str());
×
163
  }
164

165
  // Set flag indicating initialization is done
166
  simulation::initialized = true;
543✔
167
  return 0;
543✔
168
}
169

170
int openmc_simulation_finalize()
541✔
171
{
172
  using namespace openmc;
173

174
  // Skip if simulation was never run
175
  if (!simulation::initialized)
541!
176
    return 0;
×
177

178
  // Stop active batch timer and start finalization timer
179
  simulation::time_active.stop();
541✔
180
  simulation::time_finalize.start();
541✔
181

182
  // Clear material nuclide mapping
183
  for (auto& mat : model::materials) {
2,110✔
184
    mat->mat_nuclide_index_.clear();
1,569✔
185
  }
186

187
  // Close track file if open
188
  if (!settings::track_identifiers.empty() || settings::write_all_tracks) {
541✔
189
    close_track_file();
6✔
190
  }
191

192
  // Increment total number of generations
193
  simulation::total_gen += simulation::current_batch * settings::gen_per_batch;
541✔
194

195
#ifdef OPENMC_MPI
196
  broadcast_results();
197
#endif
198

199
  // Write tally results to tallies.out
200
  if (settings::output_tallies && mpi::master)
541!
201
    write_tallies();
520✔
202

203
  // If weight window generators are present in this simulation,
204
  // write a weight windows file
205
  if (variance_reduction::weight_windows_generators.size() > 0) {
541✔
206
    openmc_weight_windows_export();
9✔
207
  }
208

209
  // Deactivate all tallies
210
  for (auto& t : model::tallies) {
2,374✔
211
    t->active_ = false;
1,833✔
212
  }
213

214
  // Stop timers and show timing statistics
215
  simulation::time_finalize.stop();
541✔
216
  simulation::time_total.stop();
541✔
217
  if (mpi::master) {
541!
218
    if (settings::solver_type != SolverType::RANDOM_RAY) {
541✔
219
      if (settings::verbosity >= 6)
491✔
220
        print_runtime();
471✔
221
      if (settings::verbosity >= 4)
491✔
222
        print_results();
471✔
223
    }
224
  }
225
  if (settings::check_overlaps)
541!
226
    print_overlap_check();
×
227

228
  // Reset flags
229
  simulation::initialized = false;
541✔
230
  return 0;
541✔
231
}
232

233
int openmc_next_batch(int* status)
9,575✔
234
{
235
  using namespace openmc;
236
  using openmc::simulation::current_gen;
237

238
  // Make sure simulation has been initialized
239
  if (!simulation::initialized) {
9,575✔
240
    set_errmsg("Simulation has not been initialized yet.");
1✔
241
    return OPENMC_E_ALLOCATE;
1✔
242
  }
243

244
  initialize_batch();
9,574✔
245

246
  // =======================================================================
247
  // LOOP OVER GENERATIONS
248
  for (current_gen = 1; current_gen <= settings::gen_per_batch; ++current_gen) {
19,160✔
249

250
    initialize_generation();
9,588✔
251

252
    // Start timer for transport
253
    simulation::time_transport.start();
9,588✔
254

255
    // Transport loop
256
    if (settings::event_based) {
9,588!
UNCOV
257
      transport_event_based();
×
258
    } else {
259
      transport_history_based();
9,588✔
260
    }
261

262
    // Accumulate time for transport
263
    simulation::time_transport.stop();
9,586✔
264

265
    finalize_generation();
9,586✔
266
  }
267

268
  finalize_batch();
9,572✔
269

270
  // Check simulation ending criteria
271
  if (status) {
9,572!
272
    if (simulation::current_batch >= settings::n_max_batches) {
9,572✔
273
      *status = STATUS_EXIT_MAX_BATCH;
486✔
274
    } else if (simulation::satisfy_triggers) {
9,086✔
275
      *status = STATUS_EXIT_ON_TRIGGER;
7✔
276
    } else {
277
      *status = STATUS_EXIT_NORMAL;
9,079✔
278
    }
279
  }
280
  return 0;
9,572✔
281
}
282

283
bool openmc_is_statepoint_batch()
285✔
284
{
285
  using namespace openmc;
286
  using openmc::simulation::current_gen;
287

288
  if (!simulation::initialized)
285!
289
    return false;
×
290
  else
291
    return contains(settings::statepoint_batch, simulation::current_batch);
285✔
292
}
293

294
namespace openmc {
295

296
//==============================================================================
297
// Global variables
298
//==============================================================================
299

300
namespace simulation {
301

302
int ct_current_file;
303
int current_batch;
304
int current_gen;
305
bool initialized {false};
306
double keff {1.0};
307
double keff_std;
308
double k_col_abs {0.0};
309
double k_col_tra {0.0};
310
double k_abs_tra {0.0};
311
double log_spacing;
312
int n_lost_particles {0};
313
bool need_depletion_rx {false};
314
bool nonvacuum_boundary_present {false};
315
int restart_batch;
316
bool satisfy_triggers {false};
317
int ssw_current_file;
318
int total_gen {0};
319
double total_weight;
320
int64_t work_per_rank;
321

322
const RegularMesh* entropy_mesh {nullptr};
323
const RegularMesh* ufs_mesh {nullptr};
324

325
vector<double> k_generation;
326
vector<int64_t> work_index;
327

328
} // namespace simulation
329

330
//==============================================================================
331
// Non-member functions
332
//==============================================================================
333

334
void allocate_banks()
543✔
335
{
336
  if (settings::run_mode == RunMode::EIGENVALUE &&
543✔
337
      settings::solver_type == SolverType::MONTE_CARLO) {
320✔
338
    // Allocate source bank
339
    simulation::source_bank.resize(simulation::work_per_rank);
300✔
340

341
    // Allocate fission bank
342
    init_fission_bank(3 * simulation::work_per_rank);
300✔
343

344
    // Allocate IFP bank
345
    if (settings::ifp_on) {
300✔
346
      resize_simulation_ifp_banks();
6✔
347
    }
348
  }
349

350
  if (settings::surf_source_write) {
543✔
351
    // Allocate surface source bank
352
    simulation::surf_source_bank.reserve(settings::ssw_max_particles);
91✔
353
  }
354

355
  if (settings::collision_track) {
543✔
356
    // Allocate collision track bank
357
    collision_track_reserve_bank();
11✔
358
  }
359
}
543✔
360

361
void initialize_batch()
10,724✔
362
{
363
  // Increment current batch
364
  ++simulation::current_batch;
10,724✔
365
  if (settings::run_mode == RunMode::FIXED_SOURCE) {
10,724✔
366
    if (settings::solver_type == SolverType::RANDOM_RAY &&
4,240✔
367
        simulation::current_batch < settings::n_inactive + 1) {
880✔
368
      write_message(
535✔
369
        6, "Simulating batch {:<4} (inactive)", simulation::current_batch);
370
    } else {
371
      write_message(6, "Simulating batch {}", simulation::current_batch);
3,705✔
372
    }
373
  }
374

375
  // Reset total starting particle weight used for normalizing tallies
376
  simulation::total_weight = 0.0;
10,724✔
377

378
  // Determine if this batch is the first inactive or active batch.
379
  bool first_inactive = false;
10,724✔
380
  bool first_active = false;
10,724✔
381
  if (!settings::restart_run) {
10,724✔
382
    first_inactive = settings::n_inactive > 0 && simulation::current_batch == 1;
10,711✔
383
    first_active = simulation::current_batch == settings::n_inactive + 1;
10,711✔
384
  } else if (simulation::current_batch == simulation::restart_batch + 1) {
13✔
385
    first_inactive = simulation::restart_batch < settings::n_inactive;
4✔
386
    first_active = !first_inactive;
4✔
387
  }
388

389
  // Manage active/inactive timers and activate tallies if necessary.
390
  if (first_inactive) {
10,724✔
391
    simulation::time_inactive.start();
261✔
392
  } else if (first_active) {
10,463✔
393
    simulation::time_inactive.stop();
539✔
394
    simulation::time_active.start();
539✔
395
    for (auto& t : model::tallies) {
2,370✔
396
      t->active_ = true;
1,831✔
397
    }
398
  }
399

400
  // Add user tallies to active tallies list
401
  setup_active_tallies();
10,724✔
402
}
10,724✔
403

404
void finalize_batch()
10,722✔
405
{
406
  // Reduce tallies onto master process and accumulate
407
  simulation::time_tallies.start();
10,722✔
408
  accumulate_tallies();
10,722✔
409
  simulation::time_tallies.stop();
10,722✔
410

411
  // update weight windows if needed
412
  for (const auto& wwg : variance_reduction::weight_windows_generators) {
10,877✔
413
    wwg->update();
155✔
414
  }
415

416
  // Reset global tally results
417
  if (simulation::current_batch <= settings::n_inactive) {
10,722✔
418
    xt::view(simulation::global_tallies, xt::all()) = 0.0;
2,014✔
419
    simulation::n_realizations = 0;
2,014✔
420
  }
421

422
  // Check_triggers
423
  if (mpi::master)
10,722!
424
    check_triggers();
10,722✔
425
#ifdef OPENMC_MPI
426
  MPI_Bcast(&simulation::satisfy_triggers, 1, MPI_C_BOOL, 0, mpi::intracomm);
427
#endif
428
  if (simulation::satisfy_triggers ||
10,722✔
429
      (settings::trigger_on &&
205✔
430
        simulation::current_batch == settings::n_max_batches)) {
205✔
431
    settings::statepoint_batch.insert(simulation::current_batch);
11✔
432
  }
433

434
  // Write out state point if it's been specified for this batch and is not
435
  // a CMFD run instance
436
  if (contains(settings::statepoint_batch, simulation::current_batch) &&
11,280✔
437
      !settings::cmfd_run) {
558✔
438
    if (contains(settings::sourcepoint_batch, simulation::current_batch) &&
1,068✔
439
        settings::source_write && !settings::source_separate) {
1,068✔
440
      bool b = (settings::run_mode == RunMode::EIGENVALUE);
471✔
441
      openmc_statepoint_write(nullptr, &b);
471✔
442
    } else {
443
      bool b = false;
71✔
444
      openmc_statepoint_write(nullptr, &b);
71✔
445
    }
446
  }
447

448
  if (settings::run_mode == RunMode::EIGENVALUE) {
10,722✔
449
    // Write out a separate source point if it's been specified for this batch
450
    if (contains(settings::sourcepoint_batch, simulation::current_batch) &&
6,808✔
451
        settings::source_write && settings::source_separate) {
6,808✔
452

453
      // Determine width for zero padding
454
      int w = std::to_string(settings::n_max_batches).size();
5✔
455
      std::string source_point_filename = fmt::format("{0}source.{1:0{2}}",
456
        settings::path_output, simulation::current_batch, w);
5✔
457
      span<SourceSite> bankspan(simulation::source_bank);
5✔
458
      write_source_point(source_point_filename, bankspan,
5✔
459
        simulation::work_index, settings::source_mcpl_write);
460
    }
5✔
461

462
    // Write a continously-overwritten source point if requested.
463
    if (settings::source_latest) {
6,484✔
464
      auto filename = settings::path_output + "source";
10✔
465
      span<SourceSite> bankspan(simulation::source_bank);
10✔
466
      write_source_point(filename, bankspan, simulation::work_index,
10✔
467
        settings::source_mcpl_write);
468
    }
10✔
469
  }
470

471
  // Write out surface source if requested.
472
  if (settings::surf_source_write &&
10,722✔
473
      simulation::ssw_current_file <= settings::ssw_max_files) {
864✔
474
    bool last_batch = (simulation::current_batch == settings::n_batches);
168✔
475
    if (simulation::surf_source_bank.full() || last_batch) {
168✔
476
      // Determine appropriate filename
477
      auto filename = fmt::format("{}surface_source.{}", settings::path_output,
478
        simulation::current_batch);
94✔
479
      if (settings::ssw_max_files == 1 ||
94✔
480
          (simulation::ssw_current_file == 1 && last_batch)) {
5!
481
        filename = settings::path_output + "surface_source";
89✔
482
      }
483

484
      // Get span of source bank and calculate parallel index vector
485
      auto surf_work_index = mpi::calculate_parallel_index_vector(
486
        simulation::surf_source_bank.size());
94✔
487
      span<SourceSite> surfbankspan(simulation::surf_source_bank.begin(),
488
        simulation::surf_source_bank.size());
94✔
489

490
      // Write surface source file
491
      write_source_point(
94✔
492
        filename, surfbankspan, surf_work_index, settings::surf_mcpl_write);
493

494
      // Reset surface source bank and increment counter
495
      simulation::surf_source_bank.clear();
94✔
496
      if (!last_batch && settings::ssw_max_files >= 1) {
94!
497
        simulation::surf_source_bank.reserve(settings::ssw_max_particles);
77✔
498
      }
499
      ++simulation::ssw_current_file;
94✔
500
    }
94✔
501
  }
502
  // Write collision track file if requested
503
  if (settings::collision_track) {
10,722✔
504
    collision_track_flush_bank();
43✔
505
  }
506
}
10,722✔
507

508
void initialize_generation()
10,738✔
509
{
510
  if (settings::run_mode == RunMode::EIGENVALUE) {
10,738✔
511
    // Clear out the fission bank
512
    simulation::fission_bank.resize(0);
6,498✔
513

514
    // Count source sites if using uniform fission source weighting
515
    if (settings::ufs_on)
6,498✔
516
      ufs_count_sites();
10✔
517

518
    // Store current value of tracklength k
519
    simulation::keff_generation = simulation::global_tallies(
6,498✔
520
      GlobalTally::K_TRACKLENGTH, TallyResult::VALUE);
521
  }
522
}
10,738✔
523

524
void finalize_generation()
10,736✔
525
{
526
  auto& gt = simulation::global_tallies;
10,736✔
527

528
  // Update global tallies with the accumulation variables
529
  if (settings::run_mode == RunMode::EIGENVALUE) {
10,736✔
530
    gt(GlobalTally::K_COLLISION, TallyResult::VALUE) += global_tally_collision;
6,498✔
531
    gt(GlobalTally::K_ABSORPTION, TallyResult::VALUE) +=
6,498✔
532
      global_tally_absorption;
533
    gt(GlobalTally::K_TRACKLENGTH, TallyResult::VALUE) +=
6,498✔
534
      global_tally_tracklength;
535
  }
536
  gt(GlobalTally::LEAKAGE, TallyResult::VALUE) += global_tally_leakage;
10,736✔
537

538
  // reset tallies
539
  if (settings::run_mode == RunMode::EIGENVALUE) {
10,736✔
540
    global_tally_collision = 0.0;
6,498✔
541
    global_tally_absorption = 0.0;
6,498✔
542
    global_tally_tracklength = 0.0;
6,498✔
543
  }
544
  global_tally_leakage = 0.0;
10,736✔
545

546
  if (settings::run_mode == RunMode::EIGENVALUE &&
10,736✔
547
      settings::solver_type == SolverType::MONTE_CARLO) {
6,498✔
548
    // If using shared memory, stable sort the fission bank (by parent IDs)
549
    // so as to allow for reproducibility regardless of which order particles
550
    // are run in.
551
    sort_fission_bank();
6,228✔
552

553
    // Distribute fission bank across processors evenly
554
    synchronize_bank();
6,228✔
555
  }
556

557
  if (settings::run_mode == RunMode::EIGENVALUE) {
10,736✔
558

559
    // Calculate shannon entropy
560
    if (settings::entropy_on &&
6,498✔
561
        settings::solver_type == SolverType::MONTE_CARLO)
965✔
562
      shannon_entropy();
695✔
563

564
    // Collect results and statistics
565
    calculate_generation_keff();
6,498✔
566
    calculate_average_keff();
6,498✔
567

568
    // Write generation output
569
    if (mpi::master && settings::verbosity >= 7) {
6,498!
570
      print_generation();
6,248✔
571
    }
572
  }
573
}
10,736✔
574

575
void initialize_history(Particle& p, int64_t index_source)
15,174,274✔
576
{
577
  // set defaults
578
  if (settings::run_mode == RunMode::EIGENVALUE) {
15,174,274✔
579
    // set defaults for eigenvalue simulations from primary bank
580
    p.from_source(&simulation::source_bank[index_source - 1]);
12,813,200✔
581
  } else if (settings::run_mode == RunMode::FIXED_SOURCE) {
2,361,074!
582
    // initialize random number seed
583
    int64_t id = (simulation::total_gen + overall_generation() - 1) *
2,361,074✔
584
                   settings::n_particles +
2,361,074✔
585
                 simulation::work_index[mpi::rank] + index_source;
2,361,074✔
586
    uint64_t seed = init_seed(id, STREAM_SOURCE);
2,361,074✔
587
    // sample from external source distribution or custom library then set
588
    auto site = sample_external_source(&seed);
2,361,074✔
589
    p.from_source(&site);
2,361,073✔
590
  }
591
  p.current_work() = index_source;
15,174,273✔
592

593
  // set identifier for particle
594
  p.id() = simulation::work_index[mpi::rank] + index_source;
15,174,273✔
595

596
  // set progeny count to zero
597
  p.n_progeny() = 0;
15,174,273✔
598

599
  // Reset particle event counter
600
  p.n_event() = 0;
15,174,273✔
601

602
  // Reset split counter
603
  p.n_split() = 0;
15,174,273✔
604

605
  // Reset weight window ratio
606
  p.ww_factor() = 0.0;
15,174,273✔
607

608
  // set particle history start weight
609
  p.wgt_born() = p.wgt();
15,174,273✔
610

611
  // Reset pulse_height_storage
612
  std::fill(p.pht_storage().begin(), p.pht_storage().end(), 0);
15,174,273✔
613

614
  // set random number seed
615
  int64_t particle_seed =
616
    (simulation::total_gen + overall_generation() - 1) * settings::n_particles +
15,174,273✔
617
    p.id();
15,174,273✔
618
  init_particle_seeds(particle_seed, p.seeds());
15,174,273✔
619

620
  // set particle trace
621
  p.trace() = false;
15,174,273✔
622
  if (simulation::current_batch == settings::trace_batch &&
30,349,546✔
623
      simulation::current_gen == settings::trace_gen &&
15,175,273!
624
      p.id() == settings::trace_particle)
1,000✔
625
    p.trace() = true;
1✔
626

627
  // Set particle track.
628
  p.write_track() = check_track_criteria(p);
15,174,273✔
629

630
  // Set the particle's initial weight window value.
631
  p.wgt_ww_born() = -1.0;
15,174,273✔
632
  apply_weight_windows(p);
15,174,273✔
633

634
  // Display message if high verbosity or trace is on
635
  if (settings::verbosity >= 9 || p.trace()) {
15,174,273!
636
    write_message("Simulating Particle {}", p.id());
1✔
637
  }
638

639
// Add particle's starting weight to count for normalizing tallies later
640
#pragma omp atomic
641
  simulation::total_weight += p.wgt();
15,174,273✔
642

643
  // Force calculation of cross-sections by setting last energy to zero
644
  if (settings::run_CE) {
15,174,273✔
645
    p.invalidate_neutron_xs();
4,990,273✔
646
  }
647

648
  // Prepare to write out particle track.
649
  if (p.write_track())
15,174,273✔
650
    add_particle_track(p);
69✔
651
}
15,174,273✔
652

653
int overall_generation()
17,554,344✔
654
{
655
  using namespace simulation;
656
  return settings::gen_per_batch * (current_batch - 1) + current_gen;
17,554,344✔
657
}
658

659
void calculate_work()
543✔
660
{
661
  // Determine minimum amount of particles to simulate on each processor
662
  int64_t min_work = settings::n_particles / mpi::n_procs;
543✔
663

664
  // Determine number of processors that have one extra particle
665
  int64_t remainder = settings::n_particles % mpi::n_procs;
543✔
666

667
  int64_t i_bank = 0;
543✔
668
  simulation::work_index.resize(mpi::n_procs + 1);
543✔
669
  simulation::work_index[0] = 0;
543✔
670
  for (int i = 0; i < mpi::n_procs; ++i) {
1,086✔
671
    // Number of particles for rank i
672
    int64_t work_i = i < remainder ? min_work + 1 : min_work;
543!
673

674
    // Set number of particles
675
    if (mpi::rank == i)
543!
676
      simulation::work_per_rank = work_i;
543✔
677

678
    // Set index into source bank for rank i
679
    i_bank += work_i;
543✔
680
    simulation::work_index[i + 1] = i_bank;
543✔
681
  }
682
}
543✔
683

684
void initialize_data()
463✔
685
{
686
  // Determine minimum/maximum energy for incident neutron/photon data
687
  data::energy_max = {INFTY, INFTY, INFTY, INFTY};
463✔
688
  data::energy_min = {0.0, 0.0, 0.0, 0.0};
463✔
689

690
  for (const auto& nuc : data::nuclides) {
2,819✔
691
    if (nuc->grid_.size() >= 1) {
2,356!
692
      int neutron = static_cast<int>(ParticleType::neutron);
2,356✔
693
      data::energy_min[neutron] =
4,712✔
694
        std::max(data::energy_min[neutron], nuc->grid_[0].energy.front());
2,356✔
695
      data::energy_max[neutron] =
2,356✔
696
        std::min(data::energy_max[neutron], nuc->grid_[0].energy.back());
2,356✔
697
    }
698
  }
699

700
  if (settings::photon_transport) {
463✔
701
    for (const auto& elem : data::elements) {
69✔
702
      if (elem->energy_.size() >= 1) {
47!
703
        int photon = static_cast<int>(ParticleType::photon);
47✔
704
        int n = elem->energy_.size();
47✔
705
        data::energy_min[photon] =
94✔
706
          std::max(data::energy_min[photon], std::exp(elem->energy_(1)));
47✔
707
        data::energy_max[photon] =
94✔
708
          std::min(data::energy_max[photon], std::exp(elem->energy_(n - 1)));
47✔
709
      }
710
    }
711

712
    if (settings::electron_treatment == ElectronTreatment::TTB) {
22✔
713
      // Determine if minimum/maximum energy for bremsstrahlung is greater/less
714
      // than the current minimum/maximum
715
      if (data::ttb_e_grid.size() >= 1) {
20!
716
        int photon = static_cast<int>(ParticleType::photon);
20✔
717
        int electron = static_cast<int>(ParticleType::electron);
20✔
718
        int positron = static_cast<int>(ParticleType::positron);
20✔
719
        int n_e = data::ttb_e_grid.size();
20✔
720

721
        const std::vector<int> charged = {electron, positron};
20✔
722
        for (auto t : charged) {
60✔
723
          data::energy_min[t] = std::exp(data::ttb_e_grid(1));
40✔
724
          data::energy_max[t] = std::exp(data::ttb_e_grid(n_e - 1));
40✔
725
        }
726

727
        data::energy_min[photon] =
40✔
728
          std::max(data::energy_min[photon], data::energy_min[electron]);
20✔
729

730
        data::energy_max[photon] =
40✔
731
          std::min(data::energy_max[photon], data::energy_max[electron]);
20✔
732
      }
20✔
733
    }
734
  }
735

736
  // Show which nuclide results in lowest energy for neutron transport
737
  for (const auto& nuc : data::nuclides) {
576✔
738
    // If a nuclide is present in a material that's not used in the model, its
739
    // grid has not been allocated
740
    if (nuc->grid_.size() > 0) {
535!
741
      double max_E = nuc->grid_[0].energy.back();
535✔
742
      int neutron = static_cast<int>(ParticleType::neutron);
535✔
743
      if (max_E == data::energy_max[neutron]) {
535✔
744
        write_message(7, "Maximum neutron transport energy: {} eV for {}",
422✔
745
          data::energy_max[neutron], nuc->name_);
422✔
746
        if (mpi::master && data::energy_max[neutron] < 20.0e6) {
422!
747
          warning("Maximum neutron energy is below 20 MeV. This may bias "
×
748
                  "the results.");
749
        }
750
        break;
422✔
751
      }
752
    }
753
  }
754

755
  // Set up logarithmic grid for nuclides
756
  for (auto& nuc : data::nuclides) {
2,819✔
757
    nuc->init_grid();
2,356✔
758
  }
759
  int neutron = static_cast<int>(ParticleType::neutron);
463✔
760
  simulation::log_spacing =
463✔
761
    std::log(data::energy_max[neutron] / data::energy_min[neutron]) /
463✔
762
    settings::n_log_bins;
763
}
463✔
764

765
#ifdef OPENMC_MPI
766
void broadcast_results()
767
{
768
  // Broadcast tally results so that each process has access to results
769
  for (auto& t : model::tallies) {
770
    // Create a new datatype that consists of all values for a given filter
771
    // bin and then use that to broadcast. This is done to minimize the
772
    // chance of the 'count' argument of MPI_BCAST exceeding 2**31
773
    auto& results = t->results_;
774

775
    auto shape = results.shape();
776
    int count_per_filter = shape[1] * shape[2];
777
    MPI_Datatype result_block;
778
    MPI_Type_contiguous(count_per_filter, MPI_DOUBLE, &result_block);
779
    MPI_Type_commit(&result_block);
780
    MPI_Bcast(results.data(), shape[0], result_block, 0, mpi::intracomm);
781
    MPI_Type_free(&result_block);
782
  }
783

784
  // Also broadcast global tally results
785
  auto& gt = simulation::global_tallies;
786
  MPI_Bcast(gt.data(), gt.size(), MPI_DOUBLE, 0, mpi::intracomm);
787

788
  // These guys are needed so that non-master processes can calculate the
789
  // combined estimate of k-effective
790
  double temp[] {
791
    simulation::k_col_abs, simulation::k_col_tra, simulation::k_abs_tra};
792
  MPI_Bcast(temp, 3, MPI_DOUBLE, 0, mpi::intracomm);
793
  simulation::k_col_abs = temp[0];
794
  simulation::k_col_tra = temp[1];
795
  simulation::k_abs_tra = temp[2];
796
}
797

798
#endif
799

800
void free_memory_simulation()
635✔
801
{
802
  simulation::k_generation.clear();
635✔
803
  simulation::entropy.clear();
635✔
804
}
635✔
805

806
void transport_history_based_single_particle(Particle& p)
15,174,276✔
807
{
808
  while (p.alive()) {
391,374,516✔
809
    p.event_calculate_xs();
376,200,241✔
810
    if (p.alive()) {
376,200,241!
811
      p.event_advance();
376,200,241✔
812
    }
813
    if (p.alive()) {
376,200,241✔
814
      if (p.collision_distance() > p.boundary().distance()) {
376,179,793✔
815
        p.event_cross_surface();
128,448,975✔
816
      } else if (p.alive()) {
247,730,818!
817
        p.event_collide();
247,730,818✔
818
      }
819
    }
820
    p.event_revive_from_secondary();
376,200,240✔
821
  }
822
  p.event_death();
15,174,275✔
823
}
15,174,275✔
824

NEW
825
void transport_pseudoparticle(Particle& p, double total_distance, double& mfp)
×
826
{
NEW
827
  if (!p.alive())
×
NEW
828
    return;
×
829

NEW
830
  double distance = total_distance;
×
NEW
831
  bool cross_surface = false;
×
NEW
832
  while (distance > 0.0) {
×
NEW
833
    p.event_calculate_xs();
×
NEW
834
    if (p.alive()) {
×
NEW
835
      cross_surface = (distance > p.boundary().distance());
×
NEW
836
      p.event_advance_pseudo(distance, mfp);
×
837
    } else {
NEW
838
      return;
×
839
    }
NEW
840
    if (p.alive() && cross_surface) {
×
NEW
841
      p.event_cross_surface_pseudo();
×
842
    }
NEW
843
    if (!p.alive())
×
NEW
844
      return;
×
845
  }
846
}
847

848
void transport_history_based()
9,588✔
849
{
850
#pragma omp parallel for schedule(runtime)
851
  for (int64_t i_work = 1; i_work <= simulation::work_per_rank; ++i_work) {
15,183,860✔
852
    Particle p;
15,174,274✔
853
    initialize_history(p, i_work);
15,174,274✔
854
    transport_history_based_single_particle(p);
15,174,273✔
855
  }
15,174,272✔
856
}
9,586✔
857

UNCOV
858
void transport_event_based()
×
859
{
UNCOV
860
  int64_t remaining_work = simulation::work_per_rank;
×
UNCOV
861
  int64_t source_offset = 0;
×
862

863
  // To cap the total amount of memory used to store particle object data, the
864
  // number of particles in flight at any point in time can bet set. In the case
865
  // that the maximum in flight particle count is lower than the total number
866
  // of particles that need to be run this iteration, the event-based transport
867
  // loop is executed multiple times until all particles have been completed.
UNCOV
868
  while (remaining_work > 0) {
×
869
    // Figure out # of particles to run for this subiteration
870
    int64_t n_particles =
UNCOV
871
      std::min(remaining_work, settings::max_particles_in_flight);
×
872

873
    // Initialize all particle histories for this subiteration
UNCOV
874
    process_init_events(n_particles, source_offset);
×
875

876
    // Event-based transport loop
877
    while (true) {
878
      // Determine which event kernel has the longest queue
UNCOV
879
      int64_t max = std::max({simulation::calculate_fuel_xs_queue.size(),
×
UNCOV
880
        simulation::calculate_nonfuel_xs_queue.size(),
×
UNCOV
881
        simulation::advance_particle_queue.size(),
×
UNCOV
882
        simulation::surface_crossing_queue.size(),
×
UNCOV
883
        simulation::collision_queue.size()});
×
884

885
      // Execute event with the longest queue
UNCOV
886
      if (max == 0) {
×
UNCOV
887
        break;
×
UNCOV
888
      } else if (max == simulation::calculate_fuel_xs_queue.size()) {
×
UNCOV
889
        process_calculate_xs_events(simulation::calculate_fuel_xs_queue);
×
UNCOV
890
      } else if (max == simulation::calculate_nonfuel_xs_queue.size()) {
×
UNCOV
891
        process_calculate_xs_events(simulation::calculate_nonfuel_xs_queue);
×
UNCOV
892
      } else if (max == simulation::advance_particle_queue.size()) {
×
UNCOV
893
        process_advance_particle_events();
×
UNCOV
894
      } else if (max == simulation::surface_crossing_queue.size()) {
×
UNCOV
895
        process_surface_crossing_events();
×
UNCOV
896
      } else if (max == simulation::collision_queue.size()) {
×
UNCOV
897
        process_collision_events();
×
898
      }
UNCOV
899
    }
×
900

901
    // Execute death event for all particles
UNCOV
902
    process_death_events(n_particles);
×
903

904
    // Adjust remaining work and source offset variables
UNCOV
905
    remaining_work -= n_particles;
×
UNCOV
906
    source_offset += n_particles;
×
907
  }
UNCOV
908
}
×
909

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

© 2026 Coveralls, Inc