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

openmc-dev / openmc / 21497172691

29 Jan 2026 10:34PM UTC coverage: 81.959% (-0.04%) from 82.001%
21497172691

Pull #3748

github

web-flow
Merge ed3ccb631 into f7a734189
Pull Request #3748: Fix for plotting model with multi-group cross sections

17245 of 24021 branches covered (71.79%)

Branch coverage included in aggregate %.

3 of 6 new or added lines in 1 file covered. (50.0%)

779 existing lines in 15 files now uncovered.

55797 of 65099 relevant lines covered (85.71%)

43965072.54 hits per line

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

83.27
/src/random_ray/random_ray_simulation.cpp
1
#include "openmc/random_ray/random_ray_simulation.h"
2

3
#include "openmc/eigenvalue.h"
4
#include "openmc/geometry.h"
5
#include "openmc/message_passing.h"
6
#include "openmc/mgxs_interface.h"
7
#include "openmc/output.h"
8
#include "openmc/plot.h"
9
#include "openmc/random_ray/flat_source_domain.h"
10
#include "openmc/random_ray/random_ray.h"
11
#include "openmc/simulation.h"
12
#include "openmc/source.h"
13
#include "openmc/tallies/filter.h"
14
#include "openmc/tallies/tally.h"
15
#include "openmc/tallies/tally_scoring.h"
16
#include "openmc/timer.h"
17
#include "openmc/weight_windows.h"
18

19
namespace openmc {
20

21
//==============================================================================
22
// Non-member functions
23
//==============================================================================
24

25
void openmc_run_random_ray()
721✔
26
{
27
  //////////////////////////////////////////////////////////
28
  // Run forward simulation
29
  //////////////////////////////////////////////////////////
30

31
  if (mpi::master) {
721✔
32
    if (FlatSourceDomain::adjoint_) {
496✔
33
      FlatSourceDomain::adjoint_ = false;
56✔
34
      openmc::print_adjoint_header();
56✔
35
      FlatSourceDomain::adjoint_ = true;
56✔
36
    }
37
  }
38

39
  // Initialize OpenMC general data structures
40
  openmc_simulation_init();
721✔
41

42
  // Validate that inputs meet requirements for random ray mode
43
  if (mpi::master)
721✔
44
    validate_random_ray_inputs();
496✔
45

46
  // Initialize Random Ray Simulation Object
47
  RandomRaySimulation sim;
721✔
48

49
  // Initialize fixed sources, if present
50
  sim.apply_fixed_sources_and_mesh_domains();
721✔
51

52
  // Run initial random ray simulation
53
  sim.simulate();
721✔
54

55
  //////////////////////////////////////////////////////////
56
  // Run adjoint simulation (if enabled)
57
  //////////////////////////////////////////////////////////
58

59
  if (sim.adjoint_needed_) {
721✔
60
    // Setup for adjoint simulation
61
    sim.prepare_adjoint_simulation();
81✔
62

63
    // Run adjoint simulation
64
    sim.simulate();
81✔
65
  }
66
}
721✔
67

68
// Enforces restrictions on inputs in random ray mode.  While there are
69
// many features that don't make sense in random ray mode, and are therefore
70
// unsupported, we limit our testing/enforcement operations only to inputs
71
// that may cause erroneous/misleading output or crashes from the solver.
72
void validate_random_ray_inputs()
496✔
73
{
74
  // Validate tallies
75
  ///////////////////////////////////////////////////////////////////
76
  for (auto& tally : model::tallies) {
1,454✔
77

78
    // Validate score types
79
    for (auto score_bin : tally->scores_) {
2,114✔
80
      switch (score_bin) {
1,156!
81
      case SCORE_FLUX:
1,156✔
82
      case SCORE_TOTAL:
83
      case SCORE_FISSION:
84
      case SCORE_NU_FISSION:
85
      case SCORE_EVENTS:
86
      case SCORE_KAPPA_FISSION:
87
        break;
1,156✔
UNCOV
88
      default:
×
UNCOV
89
        fatal_error(
×
90
          "Invalid score specified. Only flux, total, fission, nu-fission, "
91
          "kappa-fission, and event scores are supported in random ray mode.");
92
      }
93
    }
94

95
    // Validate filter types
96
    for (auto f : tally->filters()) {
2,083✔
97
      auto& filter = *model::tally_filters[f];
1,125✔
98

99
      switch (filter.type()) {
1,125!
100
      case FilterType::CELL:
1,125✔
101
      case FilterType::CELL_INSTANCE:
102
      case FilterType::DISTRIBCELL:
103
      case FilterType::ENERGY:
104
      case FilterType::MATERIAL:
105
      case FilterType::MESH:
106
      case FilterType::UNIVERSE:
107
      case FilterType::PARTICLE:
108
        break;
1,125✔
UNCOV
109
      default:
×
UNCOV
110
        fatal_error("Invalid filter specified. Only cell, cell_instance, "
×
111
                    "distribcell, energy, material, mesh, and universe filters "
112
                    "are supported in random ray mode.");
113
      }
114
    }
115
  }
116

117
  // Validate MGXS data
118
  ///////////////////////////////////////////////////////////////////
119
  for (auto& material : data::mg.macro_xs_) {
1,861✔
120
    if (!material.is_isotropic) {
1,365!
UNCOV
121
      fatal_error("Anisotropic MGXS detected. Only isotropic XS data sets "
×
122
                  "supported in random ray mode.");
123
    }
124
    if (material.get_xsdata().size() > 1) {
1,365!
UNCOV
125
      warning("Non-isothermal MGXS detected. Only isothermal XS data sets "
×
126
              "supported in random ray mode. Using lowest temperature.");
127
    }
128
    for (int g = 0; g < data::mg.num_energy_groups_; g++) {
7,318✔
129
      if (material.exists_in_model) {
5,953✔
130
        // Temperature and angle indices, if using multiple temperature
131
        // data sets and/or anisotropic data sets.
132
        // TODO: Currently assumes we are only using single temp/single angle
133
        // data.
134
        const int t = 0;
5,909✔
135
        const int a = 0;
5,909✔
136
        double sigma_t =
137
          material.get_xs(MgxsType::TOTAL, g, NULL, NULL, NULL, t, a);
5,909✔
138
        if (sigma_t <= 0.0) {
5,909!
UNCOV
139
          fatal_error("No zero or negative total macroscopic cross sections "
×
140
                      "allowed in random ray mode. If the intention is to make "
141
                      "a void material, use a cell fill of 'None' instead.");
142
        }
143
      }
144
    }
145
  }
146

147
  // Validate ray source
148
  ///////////////////////////////////////////////////////////////////
149

150
  // Check for independent source
151
  IndependentSource* is =
152
    dynamic_cast<IndependentSource*>(RandomRay::ray_source_.get());
496!
153
  if (!is) {
496!
UNCOV
154
    fatal_error("Invalid ray source definition. Ray source must provided and "
×
155
                "be of type IndependentSource.");
156
  }
157

158
  // Check for box source
159
  SpatialDistribution* space_dist = is->space();
496✔
160
  SpatialBox* sb = dynamic_cast<SpatialBox*>(space_dist);
496!
161
  if (!sb) {
496!
UNCOV
162
    fatal_error(
×
163
      "Invalid ray source definition -- only box sources are allowed.");
164
  }
165

166
  // Check that box source is not restricted to fissionable areas
167
  if (sb->only_fissionable()) {
496!
168
    fatal_error(
×
169
      "Invalid ray source definition -- fissionable spatial distribution "
170
      "not allowed.");
171
  }
172

173
  // Check for isotropic source
174
  UnitSphereDistribution* angle_dist = is->angle();
496✔
175
  Isotropic* id = dynamic_cast<Isotropic*>(angle_dist);
496!
176
  if (!id) {
496!
UNCOV
177
    fatal_error("Invalid ray source definition -- only isotropic sources are "
×
178
                "allowed.");
179
  }
180

181
  // Validate external sources
182
  ///////////////////////////////////////////////////////////////////
183
  if (settings::run_mode == RunMode::FIXED_SOURCE) {
496✔
184
    if (model::external_sources.size() < 1) {
287!
UNCOV
185
      fatal_error("Must provide a particle source (in addition to ray source) "
×
186
                  "in fixed source random ray mode.");
187
    }
188

189
    for (int i = 0; i < model::external_sources.size(); i++) {
574✔
190
      Source* s = model::external_sources[i].get();
287✔
191

192
      // Check for independent source
193
      IndependentSource* is = dynamic_cast<IndependentSource*>(s);
287!
194

195
      if (!is) {
287!
UNCOV
196
        fatal_error(
×
197
          "Only IndependentSource external source types are allowed in "
198
          "random ray mode");
199
      }
200

201
      // Check for isotropic source
202
      UnitSphereDistribution* angle_dist = is->angle();
287✔
203
      Isotropic* id = dynamic_cast<Isotropic*>(angle_dist);
287!
204
      if (!id) {
287!
UNCOV
205
        fatal_error(
×
206
          "Invalid source definition -- only isotropic external sources are "
207
          "allowed in random ray mode.");
208
      }
209

210
      // Validate that a domain ID was specified OR that it is a point source
211
      auto sp = dynamic_cast<SpatialPoint*>(is->space());
287!
212
      if (is->domain_ids().size() == 0 && !sp) {
287!
213
        fatal_error("Fixed sources must be point source or spatially "
×
214
                    "constrained by domain id (cell, material, or universe) in "
215
                    "random ray mode.");
216
      } else if (is->domain_ids().size() > 0 && sp) {
287✔
217
        // If both a domain constraint and a non-default point source location
218
        // are specified, notify user that domain constraint takes precedence.
219
        if (sp->r().x == 0.0 && sp->r().y == 0.0 && sp->r().z == 0.0) {
253!
220
          warning("Fixed source has both a domain constraint and a point "
253✔
221
                  "type spatial distribution. The domain constraint takes "
222
                  "precedence in random ray mode -- point source coordinate "
223
                  "will be ignored.");
224
        }
225
      }
226

227
      // Check that a discrete energy distribution was used
228
      Distribution* d = is->energy();
287✔
229
      Discrete* dd = dynamic_cast<Discrete*>(d);
287!
230
      if (!dd) {
287!
UNCOV
231
        fatal_error(
×
232
          "Only discrete (multigroup) energy distributions are allowed for "
233
          "external sources in random ray mode.");
234
      }
235
    }
236
  }
237

238
  // Validate plotting files
239
  ///////////////////////////////////////////////////////////////////
240
  for (int p = 0; p < model::plots.size(); p++) {
496!
241

242
    // Get handle to OpenMC plot object
UNCOV
243
    const auto& openmc_plottable = model::plots[p];
×
244
    Plot* openmc_plot = dynamic_cast<Plot*>(openmc_plottable.get());
×
245

246
    // Random ray plots only support voxel plots
UNCOV
247
    if (!openmc_plot) {
×
UNCOV
248
      warning(fmt::format(
×
249
        "Plot {} will not be used for end of simulation data plotting -- only "
250
        "voxel plotting is allowed in random ray mode.",
UNCOV
251
        openmc_plottable->id()));
×
UNCOV
252
      continue;
×
UNCOV
253
    } else if (openmc_plot->type_ != Plot::PlotType::voxel) {
×
UNCOV
254
      warning(fmt::format(
×
255
        "Plot {} will not be used for end of simulation data plotting -- only "
256
        "voxel plotting is allowed in random ray mode.",
UNCOV
257
        openmc_plottable->id()));
×
UNCOV
258
      continue;
×
259
    }
260
  }
261

262
  // Warn about slow MPI domain replication, if detected
263
  ///////////////////////////////////////////////////////////////////
264
#ifdef OPENMC_MPI
265
  if (mpi::n_procs > 1) {
226✔
266
    warning(
225✔
267
      "MPI parallelism is not supported by the random ray solver. All work "
268
      "will be performed by rank 0. Domain decomposition may be implemented in "
269
      "the future to provide efficient MPI scaling.");
270
  }
271
#endif
272

273
  // Warn about instability resulting from linear sources in small regions
274
  // when generating weight windows with FW-CADIS and an overlaid mesh.
275
  ///////////////////////////////////////////////////////////////////
276
  if (RandomRay::source_shape_ == RandomRaySourceShape::LINEAR &&
705✔
277
      variance_reduction::weight_windows.size() > 0) {
209✔
278
    warning(
11✔
279
      "Linear sources may result in negative fluxes in small source regions "
280
      "generated by mesh subdivision. Negative sources may result in low "
281
      "quality FW-CADIS weight windows. We recommend you use flat source mode "
282
      "when generating weight windows with an overlaid mesh tally.");
283
  }
284
}
496✔
285

286
void openmc_reset_random_ray()
8,186✔
287
{
288
  FlatSourceDomain::volume_estimator_ = RandomRayVolumeEstimator::HYBRID;
8,186✔
289
  FlatSourceDomain::volume_normalized_flux_tallies_ = false;
8,186✔
290
  FlatSourceDomain::adjoint_ = false;
8,186✔
291
  FlatSourceDomain::mesh_domain_map_.clear();
8,186✔
292
  RandomRay::ray_source_.reset();
8,186✔
293
  RandomRay::source_shape_ = RandomRaySourceShape::FLAT;
8,186✔
294
  RandomRay::sample_method_ = RandomRaySampleMethod::PRNG;
8,186✔
295
}
8,186✔
296

297
void print_adjoint_header()
112✔
298
{
299
  if (!FlatSourceDomain::adjoint_)
112✔
300
    // If we're going to do an adjoint simulation afterwards, report that this
301
    // is the initial forward flux solve.
302
    header("FORWARD FLUX SOLVE", 3);
56✔
303
  else
304
    // Otherwise report that we are doing the adjoint simulation
305
    header("ADJOINT FLUX SOLVE", 3);
56✔
306
}
112✔
307

308
//==============================================================================
309
// RandomRaySimulation implementation
310
//==============================================================================
311

312
RandomRaySimulation::RandomRaySimulation()
721✔
313
  : negroups_(data::mg.num_energy_groups_)
721✔
314
{
315
  // There are no source sites in random ray mode, so be sure to disable to
316
  // ensure we don't attempt to write source sites to statepoint
317
  settings::source_write = false;
721✔
318

319
  // Random ray mode does not have an inner loop over generations within a
320
  // batch, so set the current gen to 1
321
  simulation::current_gen = 1;
721✔
322

323
  switch (RandomRay::source_shape_) {
721!
324
  case RandomRaySourceShape::FLAT:
369✔
325
    domain_ = make_unique<FlatSourceDomain>();
369✔
326
    break;
369✔
327
  case RandomRaySourceShape::LINEAR:
352✔
328
  case RandomRaySourceShape::LINEAR_XY:
329
    domain_ = make_unique<LinearSourceDomain>();
352✔
330
    break;
352✔
UNCOV
331
  default:
×
UNCOV
332
    fatal_error("Unknown random ray source shape");
×
333
  }
334

335
  // Convert OpenMC native MGXS into a more efficient format
336
  // internal to the random ray solver
337
  domain_->flatten_xs();
721✔
338

339
  // Check if adjoint calculation is needed. If it is, we will run the forward
340
  // calculation first and then the adjoint calculation later.
341
  adjoint_needed_ = FlatSourceDomain::adjoint_;
721✔
342

343
  // Adjoint is always false for the forward calculation
344
  FlatSourceDomain::adjoint_ = false;
721✔
345

346
  // The first simulation is run after initialization
347
  is_first_simulation_ = true;
721✔
348
}
721✔
349

350
void RandomRaySimulation::apply_fixed_sources_and_mesh_domains()
721✔
351
{
352
  domain_->apply_meshes();
721✔
353
  if (settings::run_mode == RunMode::FIXED_SOURCE) {
721✔
354
    // Transfer external source user inputs onto random ray source regions
355
    domain_->convert_external_sources();
417✔
356
    domain_->count_external_source_regions();
417✔
357
  }
358
}
721✔
359

360
void RandomRaySimulation::prepare_fixed_sources_adjoint()
81✔
361
{
362
  domain_->source_regions_.adjoint_reset();
81✔
363
  if (settings::run_mode == RunMode::FIXED_SOURCE) {
81✔
364
    domain_->set_adjoint_sources();
65✔
365
  }
366
}
81✔
367

368
void RandomRaySimulation::prepare_adjoint_simulation()
81✔
369
{
370
  // Configure the domain for adjoint simulation
371
  FlatSourceDomain::adjoint_ = true;
81✔
372

373
  // Reset k-eff
374
  domain_->k_eff_ = 1.0;
81✔
375

376
  // Initialize adjoint fixed sources, if present
377
  prepare_fixed_sources_adjoint();
81✔
378

379
  // Transpose scattering matrix
380
  domain_->transpose_scattering_matrix();
81✔
381

382
  // Swap nu_sigma_f and chi
383
  domain_->nu_sigma_f_.swap(domain_->chi_);
81✔
384
}
81✔
385

386
void RandomRaySimulation::simulate()
802✔
387
{
388
  if (!is_first_simulation_) {
802✔
389
    if (mpi::master && adjoint_needed_)
81!
390
      openmc::print_adjoint_header();
56✔
391

392
    // Reset the timers and reinitialize the general OpenMC datastructures if
393
    // this is after the first simulation
394
    reset_timers();
81✔
395

396
    // Initialize OpenMC general data structures
397
    openmc_simulation_init();
81✔
398
  }
399

400
  // Begin main simulation timer
401
  simulation::time_total.start();
802✔
402

403
  // Random ray power iteration loop
404
  while (simulation::current_batch < settings::n_batches) {
19,214✔
405
    // Initialize the current batch
406
    initialize_batch();
18,412✔
407
    initialize_generation();
18,412✔
408

409
    // MPI not supported in random ray solver, so all work is done by rank 0
410
    // TODO: Implement domain decomposition for MPI parallelism
411
    if (mpi::master) {
18,412✔
412

413
      // Reset total starting particle weight used for normalizing tallies
414
      simulation::total_weight = 1.0;
12,662✔
415

416
      // Update source term (scattering + fission)
417
      domain_->update_all_neutron_sources();
12,662✔
418

419
      // Reset scalar fluxes, iteration volume tallies, and region hit flags
420
      // to zero
421
      domain_->batch_reset();
12,662✔
422

423
      // At the beginning of the simulation, if mesh subdivision is in use, we
424
      // need to swap the main source region container into the base container,
425
      // as the main source region container will be used to hold the true
426
      // subdivided source regions. The base container will therefore only
427
      // contain the external source region information, the mesh indices,
428
      // material properties, and initial guess values for the flux/source.
429

430
      // Start timer for transport
431
      simulation::time_transport.start();
12,662✔
432

433
// Transport sweep over all random rays for the iteration
434
#pragma omp parallel for schedule(dynamic)                                     \
6,912✔
435
  reduction(+ : total_geometric_intersections_)
6,912✔
436
      for (int i = 0; i < settings::n_particles; i++) {
898,750✔
437
        RandomRay ray(i, domain_.get());
893,000✔
438
        total_geometric_intersections_ +=
893,000✔
439
          ray.transport_history_based_single_ray();
893,000✔
440
      }
893,000✔
441

442
      simulation::time_transport.stop();
12,662✔
443

444
      // Add any newly discovered source regions to the main source region
445
      // container.
446
      domain_->finalize_discovered_source_regions();
12,662✔
447

448
      // Normalize scalar flux and update volumes
449
      domain_->normalize_scalar_flux_and_volumes(
12,662✔
450
        settings::n_particles * RandomRay::distance_active_);
451

452
      // Add source to scalar flux, compute number of FSR hits
453
      int64_t n_hits = domain_->add_source_to_scalar_flux();
12,662✔
454

455
      // Apply transport stabilization factors
456
      domain_->apply_transport_stabilization();
12,662✔
457

458
      if (settings::run_mode == RunMode::EIGENVALUE) {
12,662✔
459
        // Compute random ray k-eff
460
        domain_->compute_k_eff();
2,970✔
461

462
        // Store random ray k-eff into OpenMC's native k-eff variable
463
        global_tally_tracklength = domain_->k_eff_;
2,970✔
464
      }
465

466
      // Execute all tallying tasks, if this is an active batch
467
      if (simulation::current_batch > settings::n_inactive) {
12,662✔
468

469
        // Add this iteration's scalar flux estimate to final accumulated
470
        // estimate
471
        domain_->accumulate_iteration_flux();
5,231✔
472

473
        // Use above mapping to contribute FSR flux data to appropriate
474
        // tallies
475
        domain_->random_ray_tally();
5,231✔
476
      }
477

478
      // Set phi_old = phi_new
479
      domain_->flux_swap();
12,662✔
480

481
      // Check for any obvious insabilities/nans/infs
482
      instability_check(n_hits, domain_->k_eff_, avg_miss_rate_);
12,662✔
483
    } // End MPI master work
484

485
    // Finalize the current batch
486
    finalize_generation();
18,412✔
487
    finalize_batch();
18,412✔
488
  } // End random ray power iteration loop
489

490
  domain_->count_external_source_regions();
802✔
491

492
  // End main simulation timer
493
  simulation::time_total.stop();
802✔
494

495
  // Normalize and save the final flux
496
  double source_normalization_factor =
497
    domain_->compute_fixed_source_normalization_factor() /
802✔
498
    (settings::n_batches - settings::n_inactive);
802✔
499

500
#pragma omp parallel for
452✔
501
  for (uint64_t se = 0; se < domain_->n_source_elements(); se++) {
1,343,090✔
502
    domain_->source_regions_.scalar_flux_final(se) *=
1,342,740✔
503
      source_normalization_factor;
504
  }
505

506
  // Finalize OpenMC
507
  openmc_simulation_finalize();
802✔
508

509
  // Output all simulation results
510
  output_simulation_results();
802✔
511

512
  // Toggle that the simulation object has been initialized after the first
513
  // simulation
514
  if (is_first_simulation_)
802✔
515
    is_first_simulation_ = false;
721✔
516
}
802✔
517

518
void RandomRaySimulation::output_simulation_results() const
802✔
519
{
520
  // Print random ray results
521
  if (mpi::master) {
802✔
522
    print_results_random_ray(total_geometric_intersections_,
552✔
523
      avg_miss_rate_ / settings::n_batches, negroups_,
552✔
524
      domain_->n_source_regions(), domain_->n_external_source_regions_);
552✔
525
    if (model::plots.size() > 0) {
552!
UNCOV
526
      domain_->output_to_vtk();
×
527
    }
528
  }
529
}
802✔
530

531
// Apply a few sanity checks to catch obvious cases of numerical instability.
532
// Instability typically only occurs if ray density is extremely low.
533
void RandomRaySimulation::instability_check(
12,662✔
534
  int64_t n_hits, double k_eff, double& avg_miss_rate) const
535
{
536
  double percent_missed = ((domain_->n_source_regions() - n_hits) /
12,662✔
537
                            static_cast<double>(domain_->n_source_regions())) *
12,662✔
538
                          100.0;
12,662✔
539
  avg_miss_rate += percent_missed;
12,662✔
540

541
  if (mpi::master) {
12,662!
542
    if (percent_missed > 10.0) {
12,662✔
543
      warning(fmt::format(
957✔
544
        "Very high FSR miss rate detected ({:.3f}%). Instability may occur. "
545
        "Increase ray density by adding more rays and/or active distance.",
546
        percent_missed));
547
    } else if (percent_missed > 1.0) {
11,705✔
548
      warning(
2!
549
        fmt::format("Elevated FSR miss rate detected ({:.3f}%). Increasing "
4!
550
                    "ray density by adding more rays and/or active "
551
                    "distance may improve simulation efficiency.",
552
          percent_missed));
553
    }
554

555
    if (k_eff > 10.0 || k_eff < 0.01 || !(std::isfinite(k_eff))) {
12,662!
UNCOV
556
      fatal_error(fmt::format("Instability detected: k-eff = {:.5f}", k_eff));
×
557
    }
558
  }
559
}
12,662✔
560

561
// Print random ray simulation results
562
void RandomRaySimulation::print_results_random_ray(
552✔
563
  uint64_t total_geometric_intersections, double avg_miss_rate, int negroups,
564
  int64_t n_source_regions, int64_t n_external_source_regions) const
565
{
566
  using namespace simulation;
567

568
  if (settings::verbosity >= 6) {
552!
569
    double total_integrations = total_geometric_intersections * negroups;
552✔
570
    double time_per_integration =
571
      simulation::time_transport.elapsed() / total_integrations;
552✔
572
    double misc_time = time_total.elapsed() - time_update_src.elapsed() -
552✔
573
                       time_transport.elapsed() - time_tallies.elapsed() -
552✔
574
                       time_bank_sendrecv.elapsed();
552✔
575

576
    header("Simulation Statistics", 4);
552✔
577
    fmt::print(
552✔
578
      " Total Iterations                  = {}\n", settings::n_batches);
579
    fmt::print(
552✔
580
      " Number of Rays per Iteration      = {}\n", settings::n_particles);
581
    fmt::print(" Inactive Distance                 = {} cm\n",
552✔
582
      RandomRay::distance_inactive_);
583
    fmt::print(" Active Distance                   = {} cm\n",
552✔
584
      RandomRay::distance_active_);
585
    fmt::print(" Source Regions (SRs)              = {}\n", n_source_regions);
552✔
586
    fmt::print(
452✔
587
      " SRs Containing External Sources   = {}\n", n_external_source_regions);
588
    fmt::print(" Total Geometric Intersections     = {:.4e}\n",
452✔
589
      static_cast<double>(total_geometric_intersections));
552✔
590
    fmt::print("   Avg per Iteration               = {:.4e}\n",
452✔
591
      static_cast<double>(total_geometric_intersections) / settings::n_batches);
552✔
592
    fmt::print("   Avg per Iteration per SR        = {:.2f}\n",
452✔
593
      static_cast<double>(total_geometric_intersections) /
552✔
594
        static_cast<double>(settings::n_batches) / n_source_regions);
1,104✔
595
    fmt::print(" Avg SR Miss Rate per Iteration    = {:.4f}%\n", avg_miss_rate);
552✔
596
    fmt::print(" Energy Groups                     = {}\n", negroups);
552✔
597
    fmt::print(
452✔
598
      " Total Integrations                = {:.4e}\n", total_integrations);
599
    fmt::print("   Avg per Iteration               = {:.4e}\n",
452✔
600
      total_integrations / settings::n_batches);
552✔
601

602
    std::string estimator;
552✔
603
    switch (domain_->volume_estimator_) {
552!
604
    case RandomRayVolumeEstimator::SIMULATION_AVERAGED:
22✔
605
      estimator = "Simulation Averaged";
22✔
606
      break;
22✔
607
    case RandomRayVolumeEstimator::NAIVE:
68✔
608
      estimator = "Naive";
68✔
609
      break;
68✔
610
    case RandomRayVolumeEstimator::HYBRID:
462✔
611
      estimator = "Hybrid";
462✔
612
      break;
462✔
613
    default:
×
UNCOV
614
      fatal_error("Invalid volume estimator type");
×
615
    }
616
    fmt::print(" Volume Estimator Type             = {}\n", estimator);
452✔
617

618
    std::string adjoint_true = (FlatSourceDomain::adjoint_) ? "ON" : "OFF";
1,656✔
619
    fmt::print(" Adjoint Flux Mode                 = {}\n", adjoint_true);
452✔
620

621
    std::string shape;
1,104✔
622
    switch (RandomRay::source_shape_) {
552!
623
    case RandomRaySourceShape::FLAT:
299✔
624
      shape = "Flat";
299✔
625
      break;
299✔
626
    case RandomRaySourceShape::LINEAR:
220✔
627
      shape = "Linear";
220✔
628
      break;
220✔
629
    case RandomRaySourceShape::LINEAR_XY:
33✔
630
      shape = "Linear XY";
33✔
631
      break;
33✔
UNCOV
632
    default:
×
UNCOV
633
      fatal_error("Invalid random ray source shape");
×
634
    }
635
    fmt::print(" Source Shape                      = {}\n", shape);
452✔
636
    std::string sample_method =
637
      (RandomRay::sample_method_ == RandomRaySampleMethod::PRNG) ? "PRNG"
552✔
638
                                                                 : "Halton";
1,656✔
639
    fmt::print(" Sample Method                     = {}\n", sample_method);
452✔
640

641
    if (domain_->is_transport_stabilization_needed_) {
552✔
642
      fmt::print(" Transport XS Stabilization Used   = YES (rho = {:.3f})\n",
11✔
643
        FlatSourceDomain::diagonal_stabilization_rho_);
644
    } else {
645
      fmt::print(" Transport XS Stabilization Used   = NO\n");
541✔
646
    }
647

648
    header("Timing Statistics", 4);
552✔
649
    show_time("Total time for initialization", time_initialize.elapsed());
552✔
650
    show_time("Reading cross sections", time_read_xs.elapsed(), 1);
552✔
651
    show_time("Total simulation time", time_total.elapsed());
552✔
652
    show_time("Transport sweep only", time_transport.elapsed(), 1);
552✔
653
    show_time("Source update only", time_update_src.elapsed(), 1);
552✔
654
    show_time("Tally conversion only", time_tallies.elapsed(), 1);
552✔
655
    show_time("MPI source reductions only", time_bank_sendrecv.elapsed(), 1);
552✔
656
    show_time("Other iteration routines", misc_time, 1);
552✔
657
    if (settings::run_mode == RunMode::EIGENVALUE) {
552✔
658
      show_time("Time in inactive batches", time_inactive.elapsed());
220✔
659
    }
660
    show_time("Time in active batches", time_active.elapsed());
552✔
661
    show_time("Time writing statepoints", time_statepoint.elapsed());
552✔
662
    show_time("Total time for finalization", time_finalize.elapsed());
552✔
663
    show_time("Time per integration", time_per_integration);
552✔
664
  }
552✔
665

666
  if (settings::verbosity >= 4 && settings::run_mode == RunMode::EIGENVALUE) {
552!
667
    header("Results", 4);
220✔
668
    fmt::print(" k-effective                       = {:.5f} +/- {:.5f}\n",
220✔
669
      simulation::keff, simulation::keff_std);
670
  }
671
}
552✔
672

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