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

openmc-dev / openmc / 23011177584

12 Mar 2026 03:57PM UTC coverage: 80.895% (-0.7%) from 81.566%
23011177584

Pull #3863

github

web-flow
Merge 00a6e11ae into ba94c5823
Pull Request #3863: Shared Secondary Particle Bank

16504 of 23690 branches covered (69.67%)

Branch coverage included in aggregate %.

261 of 367 new or added lines in 17 files covered. (71.12%)

609 existing lines in 46 files now uncovered.

56448 of 66491 relevant lines covered (84.9%)

25290933.62 hits per line

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

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

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

20
namespace openmc {
21

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

26
// Enforces restrictions on inputs in random ray mode.  While there are
27
// many features that don't make sense in random ray mode, and are therefore
28
// unsupported, we limit our testing/enforcement operations only to inputs
29
// that may cause erroneous/misleading output or crashes from the solver.
30
void validate_random_ray_inputs()
204✔
31
{
32
  // Validate tallies
33
  ///////////////////////////////////////////////////////////////////
34
  for (auto& tally : model::tallies) {
564✔
35

36
    // Validate score types
37
    for (auto score_bin : tally->scores_) {
808✔
38
      switch (score_bin) {
448!
39
      case SCORE_FLUX:
448✔
40
      case SCORE_TOTAL:
448✔
41
      case SCORE_FISSION:
448✔
42
      case SCORE_NU_FISSION:
448✔
43
      case SCORE_EVENTS:
448✔
44
      case SCORE_KAPPA_FISSION:
448✔
45
        break;
448✔
46
      default:
×
47
        fatal_error(
×
48
          "Invalid score specified. Only flux, total, fission, nu-fission, "
49
          "kappa-fission, and event scores are supported in random ray mode.");
50
      }
51
    }
52

53
    // Validate filter types
54
    for (auto f : tally->filters()) {
780✔
55
      auto& filter = *model::tally_filters[f];
420✔
56

57
      switch (filter.type()) {
420!
58
      case FilterType::CELL:
420✔
59
      case FilterType::CELL_INSTANCE:
420✔
60
      case FilterType::DISTRIBCELL:
420✔
61
      case FilterType::ENERGY:
420✔
62
      case FilterType::MATERIAL:
420✔
63
      case FilterType::MESH:
420✔
64
      case FilterType::UNIVERSE:
420✔
65
      case FilterType::PARTICLE:
420✔
66
        break;
420✔
67
      default:
×
68
        fatal_error("Invalid filter specified. Only cell, cell_instance, "
×
69
                    "distribcell, energy, material, mesh, and universe filters "
70
                    "are supported in random ray mode.");
71
      }
72
    }
73
  }
74

75
  // Validate MGXS data
76
  ///////////////////////////////////////////////////////////////////
77
  for (auto& material : data::mg.macro_xs_) {
756✔
78
    if (!material.is_isotropic) {
552!
79
      fatal_error("Anisotropic MGXS detected. Only isotropic XS data sets "
×
80
                  "supported in random ray mode.");
81
    }
82
    for (int g = 0; g < data::mg.num_energy_groups_; g++) {
2,904✔
83
      if (material.exists_in_model) {
2,352✔
84
        // Temperature and angle indices, if using multiple temperature
85
        // data sets and/or anisotropic data sets.
86
        // TODO: Currently assumes we are only using single temp/single angle
87
        // data.
88
        const int t = 0;
2,336✔
89
        const int a = 0;
2,336✔
90
        double sigma_t =
2,336✔
91
          material.get_xs(MgxsType::TOTAL, g, NULL, NULL, NULL, t, a);
2,336✔
92
        if (sigma_t <= 0.0) {
2,336!
93
          fatal_error("No zero or negative total macroscopic cross sections "
×
94
                      "allowed in random ray mode. If the intention is to make "
95
                      "a void material, use a cell fill of 'None' instead.");
96
        }
97
      }
98
    }
99
  }
100

101
  // Validate ray source
102
  ///////////////////////////////////////////////////////////////////
103

104
  // Check for independent source
105
  IndependentSource* is =
204!
106
    dynamic_cast<IndependentSource*>(RandomRay::ray_source_.get());
204!
107
  if (!is) {
204!
108
    fatal_error("Invalid ray source definition. Ray source must provided and "
×
109
                "be of type IndependentSource.");
110
  }
111

112
  // Check for box source
113
  SpatialDistribution* space_dist = is->space();
204!
114
  SpatialBox* sb = dynamic_cast<SpatialBox*>(space_dist);
204!
115
  if (!sb) {
204!
116
    fatal_error(
×
117
      "Invalid ray source definition -- only box sources are allowed.");
118
  }
119

120
  // Check that box source is not restricted to fissionable areas
121
  if (sb->only_fissionable()) {
204!
122
    fatal_error(
×
123
      "Invalid ray source definition -- fissionable spatial distribution "
124
      "not allowed.");
125
  }
126

127
  // Check for isotropic source
128
  UnitSphereDistribution* angle_dist = is->angle();
204!
129
  Isotropic* id = dynamic_cast<Isotropic*>(angle_dist);
204!
130
  if (!id) {
204!
131
    fatal_error("Invalid ray source definition -- only isotropic sources are "
×
132
                "allowed.");
133
  }
134

135
  // Validate external sources
136
  ///////////////////////////////////////////////////////////////////
137
  if (settings::run_mode == RunMode::FIXED_SOURCE) {
204✔
138
    if (model::external_sources.size() < 1) {
108!
139
      fatal_error("Must provide a particle source (in addition to ray source) "
×
140
                  "in fixed source random ray mode.");
141
    }
142

143
    for (int i = 0; i < model::external_sources.size(); i++) {
216✔
144
      Source* s = model::external_sources[i].get();
108!
145

146
      // Check for independent source
147
      IndependentSource* is = dynamic_cast<IndependentSource*>(s);
108!
148

149
      if (!is) {
108!
150
        fatal_error(
×
151
          "Only IndependentSource external source types are allowed in "
152
          "random ray mode");
153
      }
154

155
      // Check for isotropic source
156
      UnitSphereDistribution* angle_dist = is->angle();
108!
157
      Isotropic* id = dynamic_cast<Isotropic*>(angle_dist);
108!
158
      if (!id) {
108!
159
        fatal_error(
×
160
          "Invalid source definition -- only isotropic external sources are "
161
          "allowed in random ray mode.");
162
      }
163

164
      // Validate that a domain ID was specified OR that it is a point source
165
      auto sp = dynamic_cast<SpatialPoint*>(is->space());
108!
166
      if (is->domain_ids().size() == 0 && !sp) {
108!
167
        fatal_error("Fixed sources must be point source or spatially "
×
168
                    "constrained by domain id (cell, material, or universe) in "
169
                    "random ray mode.");
170
      } else if (is->domain_ids().size() > 0 && sp) {
108✔
171
        // If both a domain constraint and a non-default point source location
172
        // are specified, notify user that domain constraint takes precedence.
173
        if (sp->r().x == 0.0 && sp->r().y == 0.0 && sp->r().z == 0.0) {
92!
174
          warning("Fixed source has both a domain constraint and a point "
184✔
175
                  "type spatial distribution. The domain constraint takes "
176
                  "precedence in random ray mode -- point source coordinate "
177
                  "will be ignored.");
178
        }
179
      }
180

181
      // Check that a discrete energy distribution was used
182
      Distribution* d = is->energy();
108!
183
      Discrete* dd = dynamic_cast<Discrete*>(d);
108!
184
      if (!dd) {
108!
185
        fatal_error(
×
186
          "Only discrete (multigroup) energy distributions are allowed for "
187
          "external sources in random ray mode.");
188
      }
189
    }
190
  }
191

192
  // Validate plotting files
193
  ///////////////////////////////////////////////////////////////////
194
  for (int p = 0; p < model::plots.size(); p++) {
204!
195

196
    // Get handle to OpenMC plot object
197
    const auto& openmc_plottable = model::plots[p];
×
198
    Plot* openmc_plot = dynamic_cast<Plot*>(openmc_plottable.get());
×
199

200
    // Random ray plots only support voxel plots
201
    if (!openmc_plot) {
×
202
      warning(fmt::format(
×
203
        "Plot {} will not be used for end of simulation data plotting -- only "
204
        "voxel plotting is allowed in random ray mode.",
205
        openmc_plottable->id()));
×
206
      continue;
×
207
    } else if (openmc_plot->type_ != Plot::PlotType::voxel) {
×
208
      warning(fmt::format(
×
209
        "Plot {} will not be used for end of simulation data plotting -- only "
210
        "voxel plotting is allowed in random ray mode.",
211
        openmc_plottable->id()));
×
212
      continue;
×
213
    }
214
  }
215

216
  // Warn about slow MPI domain replication, if detected
217
  ///////////////////////////////////////////////////////////////////
218
#ifdef OPENMC_MPI
219
  if (mpi::n_procs > 1) {
220
    warning(
221
      "MPI parallelism is not supported by the random ray solver. All work "
222
      "will be performed by rank 0. Domain decomposition may be implemented in "
223
      "the future to provide efficient MPI scaling.");
224
  }
225
#endif
226

227
  // Warn about instability resulting from linear sources in small regions
228
  // when generating weight windows with FW-CADIS and an overlaid mesh.
229
  ///////////////////////////////////////////////////////////////////
230
  if (RandomRay::source_shape_ == RandomRaySourceShape::LINEAR &&
204✔
231
      variance_reduction::weight_windows.size() > 0) {
88✔
232
    warning(
8✔
233
      "Linear sources may result in negative fluxes in small source regions "
234
      "generated by mesh subdivision. Negative sources may result in low "
235
      "quality FW-CADIS weight windows. We recommend you use flat source mode "
236
      "when generating weight windows with an overlaid mesh tally.");
237
  }
238
}
204✔
239

240
void print_adjoint_header()
40✔
241
{
242
  if (!FlatSourceDomain::adjoint_)
40✔
243
    // If we're going to do an adjoint simulation afterwards, report that this
244
    // is the initial forward flux solve.
245
    header("FORWARD FLUX SOLVE", 3);
20✔
246
  else
247
    // Otherwise report that we are doing the adjoint simulation
248
    header("ADJOINT FLUX SOLVE", 3);
20✔
249
}
40✔
250

251
//==============================================================================
252
// RandomRaySimulation implementation
253
//==============================================================================
254

255
RandomRaySimulation::RandomRaySimulation()
204✔
256
  : negroups_(data::mg.num_energy_groups_)
204!
257
{
258
  // There are no source sites in random ray mode, so be sure to disable to
259
  // ensure we don't attempt to write source sites to statepoint
260
  settings::source_write = false;
204✔
261

262
  // Random ray mode does not have an inner loop over generations within a
263
  // batch, so set the current gen to 1
264
  simulation::current_gen = 1;
204✔
265

266
  switch (RandomRay::source_shape_) {
204!
267
  case RandomRaySourceShape::FLAT:
104✔
268
    domain_ = make_unique<FlatSourceDomain>();
104✔
269
    break;
104✔
270
  case RandomRaySourceShape::LINEAR:
100✔
271
  case RandomRaySourceShape::LINEAR_XY:
100✔
272
    domain_ = make_unique<LinearSourceDomain>();
100✔
273
    break;
100✔
274
  default:
×
275
    fatal_error("Unknown random ray source shape");
×
276
  }
277

278
  // Convert OpenMC native MGXS into a more efficient format
279
  // internal to the random ray solver
280
  domain_->flatten_xs();
204✔
281

282
  // Check if adjoint calculation is needed. If it is, we will run the forward
283
  // calculation first and then the adjoint calculation later.
284
  adjoint_needed_ = FlatSourceDomain::adjoint_;
204✔
285

286
  // Adjoint is always false for the forward calculation
287
  FlatSourceDomain::adjoint_ = false;
204✔
288

289
  // The first simulation is run after initialization
290
  is_first_simulation_ = true;
204✔
291
}
204✔
292

293
void RandomRaySimulation::apply_fixed_sources_and_mesh_domains()
204✔
294
{
295
  domain_->apply_meshes();
204✔
296
  if (settings::run_mode == RunMode::FIXED_SOURCE) {
204✔
297
    // Transfer external source user inputs onto random ray source regions
298
    domain_->convert_external_sources();
108✔
299
    domain_->count_external_source_regions();
108✔
300
  }
301
}
204✔
302

303
void RandomRaySimulation::prepare_fixed_sources_adjoint()
20✔
304
{
305
  domain_->source_regions_.adjoint_reset();
20✔
306
  if (settings::run_mode == RunMode::FIXED_SOURCE) {
20✔
307
    domain_->set_adjoint_sources();
16✔
308
  }
309
}
20✔
310

311
void RandomRaySimulation::prepare_adjoint_simulation()
20✔
312
{
313
  // Configure the domain for adjoint simulation
314
  FlatSourceDomain::adjoint_ = true;
20✔
315

316
  // Reset k-eff
317
  domain_->k_eff_ = 1.0;
20✔
318

319
  // Initialize adjoint fixed sources, if present
320
  prepare_fixed_sources_adjoint();
20✔
321

322
  // Transpose scattering matrix
323
  domain_->transpose_scattering_matrix();
20✔
324

325
  // Swap nu_sigma_f and chi
326
  domain_->nu_sigma_f_.swap(domain_->chi_);
20✔
327
}
20✔
328

329
void RandomRaySimulation::simulate()
224✔
330
{
331
  if (!is_first_simulation_) {
224✔
332
    if (mpi::master && adjoint_needed_)
20!
333
      openmc::print_adjoint_header();
20✔
334

335
    // Reset the timers and reinitialize the general OpenMC datastructures if
336
    // this is after the first simulation
337
    reset_timers();
20✔
338

339
    // Initialize OpenMC general data structures
340
    openmc_simulation_init();
20✔
341
  }
342

343
  // Begin main simulation timer
344
  simulation::time_total.start();
224✔
345

346
  // Random ray power iteration loop
347
  while (simulation::current_batch < settings::n_batches) {
6,128✔
348
    // Initialize the current batch
349
    initialize_batch();
5,680✔
350
    initialize_generation();
5,680✔
351

352
    // MPI not supported in random ray solver, so all work is done by rank 0
353
    // TODO: Implement domain decomposition for MPI parallelism
354
    if (mpi::master) {
5,680!
355

356
      // Reset total starting particle weight used for normalizing tallies
357
      simulation::total_weight = 1.0;
5,680✔
358

359
      // Update source term (scattering + fission)
360
      domain_->update_all_neutron_sources();
5,680✔
361

362
      // Reset scalar fluxes, iteration volume tallies, and region hit flags
363
      // to zero
364
      domain_->batch_reset();
5,680✔
365

366
      // At the beginning of the simulation, if mesh subdivision is in use, we
367
      // need to swap the main source region container into the base container,
368
      // as the main source region container will be used to hold the true
369
      // subdivided source regions. The base container will therefore only
370
      // contain the external source region information, the mesh indices,
371
      // material properties, and initial guess values for the flux/source.
372

373
      // Start timer for transport
374
      simulation::time_transport.start();
5,680✔
375

376
// Transport sweep over all random rays for the iteration
377
#pragma omp parallel for schedule(dynamic)                                     \
1,420✔
378
  reduction(+ : total_geometric_intersections_)
1,420✔
379
      for (int i = 0; i < settings::n_particles; i++) {
591,060✔
380
        RandomRay ray(i, domain_.get());
586,800✔
381
        total_geometric_intersections_ +=
1,173,600✔
382
          ray.transport_history_based_single_ray();
586,800✔
383
      }
586,800✔
384

385
      simulation::time_transport.stop();
5,680✔
386

387
      // Add any newly discovered source regions to the main source region
388
      // container.
389
      domain_->finalize_discovered_source_regions();
5,680✔
390

391
      // Normalize scalar flux and update volumes
392
      domain_->normalize_scalar_flux_and_volumes(
5,680✔
393
        settings::n_particles * RandomRay::distance_active_);
394

395
      // Add source to scalar flux, compute number of FSR hits
396
      int64_t n_hits = domain_->add_source_to_scalar_flux();
5,680✔
397

398
      // Apply transport stabilization factors
399
      domain_->apply_transport_stabilization();
5,680✔
400

401
      if (settings::run_mode == RunMode::EIGENVALUE) {
5,680✔
402
        // Compute random ray k-eff
403
        domain_->compute_k_eff();
2,040✔
404

405
        // Store random ray k-eff into OpenMC's native k-eff variable
406
        global_tally_tracklength = domain_->k_eff_;
2,040✔
407
      }
408

409
      // Execute all tallying tasks, if this is an active batch
410
      if (simulation::current_batch > settings::n_inactive) {
5,680✔
411

412
        // Add this iteration's scalar flux estimate to final accumulated
413
        // estimate
414
        domain_->accumulate_iteration_flux();
2,660✔
415

416
        // Use above mapping to contribute FSR flux data to appropriate
417
        // tallies
418
        domain_->random_ray_tally();
2,660✔
419
      }
420

421
      // Set phi_old = phi_new
422
      domain_->flux_swap();
5,680✔
423

424
      // Check for any obvious insabilities/nans/infs
425
      instability_check(n_hits, domain_->k_eff_, avg_miss_rate_);
5,680✔
426
    } // End MPI master work
427

428
    // Finalize the current batch
429
    finalize_generation();
5,680✔
430
    finalize_batch();
5,680✔
431
  } // End random ray power iteration loop
432

433
  domain_->count_external_source_regions();
224✔
434

435
  // End main simulation timer
436
  simulation::time_total.stop();
224✔
437

438
  // Normalize and save the final flux
439
  double source_normalization_factor =
224✔
440
    domain_->compute_fixed_source_normalization_factor() /
224✔
441
    (settings::n_batches - settings::n_inactive);
224✔
442

443
#pragma omp parallel for
56✔
444
  for (uint64_t se = 0; se < domain_->n_source_elements(); se++) {
812,190✔
445
    domain_->source_regions_.scalar_flux_final(se) *=
812,022✔
446
      source_normalization_factor;
447
  }
448

449
  // Finalize OpenMC
450
  openmc_simulation_finalize();
224✔
451

452
  // Output all simulation results
453
  output_simulation_results();
224✔
454

455
  // Toggle that the simulation object has been initialized after the first
456
  // simulation
457
  if (is_first_simulation_)
224✔
458
    is_first_simulation_ = false;
204✔
459
}
224✔
460

461
void RandomRaySimulation::output_simulation_results() const
224✔
462
{
463
  // Print random ray results
464
  if (mpi::master) {
224!
465
    print_results_random_ray(total_geometric_intersections_,
224✔
466
      avg_miss_rate_ / settings::n_batches, negroups_,
224✔
467
      domain_->n_source_regions(), domain_->n_external_source_regions_);
224✔
468
    if (model::plots.size() > 0) {
224!
469
      domain_->output_to_vtk();
×
470
    }
471
  }
472
}
224✔
473

474
// Apply a few sanity checks to catch obvious cases of numerical instability.
475
// Instability typically only occurs if ray density is extremely low.
476
void RandomRaySimulation::instability_check(
5,680✔
477
  int64_t n_hits, double k_eff, double& avg_miss_rate) const
478
{
479
  double percent_missed = ((domain_->n_source_regions() - n_hits) /
5,680!
480
                            static_cast<double>(domain_->n_source_regions())) *
5,680✔
481
                          100.0;
5,680✔
482
  avg_miss_rate += percent_missed;
5,680✔
483

484
  if (mpi::master) {
5,680!
485
    if (percent_missed > 10.0) {
5,680✔
486
      warning(fmt::format(
696✔
487
        "Very high FSR miss rate detected ({:.3f}%). Instability may occur. "
488
        "Increase ray density by adding more rays and/or active distance.",
489
        percent_missed));
490
    } else if (percent_missed > 1.0) {
5,332!
UNCOV
491
      warning(
×
UNCOV
492
        fmt::format("Elevated FSR miss rate detected ({:.3f}%). Increasing "
×
493
                    "ray density by adding more rays and/or active "
494
                    "distance may improve simulation efficiency.",
495
          percent_missed));
496
    }
497

498
    if (k_eff > 10.0 || k_eff < 0.01 || !(std::isfinite(k_eff))) {
5,680!
499
      fatal_error(fmt::format("Instability detected: k-eff = {:.5f}", k_eff));
×
500
    }
501
  }
502
}
5,680✔
503

504
// Print random ray simulation results
505
void RandomRaySimulation::print_results_random_ray(
224✔
506
  uint64_t total_geometric_intersections, double avg_miss_rate, int negroups,
507
  int64_t n_source_regions, int64_t n_external_source_regions) const
508
{
509
  using namespace simulation;
224✔
510

511
  if (settings::verbosity >= 6) {
224!
512
    double total_integrations = total_geometric_intersections * negroups;
224✔
513
    double time_per_integration =
224✔
514
      simulation::time_transport.elapsed() / total_integrations;
224✔
515
    double misc_time = time_total.elapsed() - time_update_src.elapsed() -
224✔
516
                       time_transport.elapsed() - time_tallies.elapsed() -
224✔
517
                       time_bank_sendrecv.elapsed();
224✔
518

519
    header("Simulation Statistics", 4);
224✔
520
    fmt::print(
224✔
521
      " Total Iterations                  = {}\n", settings::n_batches);
522
    fmt::print(
224✔
523
      " Number of Rays per Iteration      = {}\n", settings::n_particles);
524
    fmt::print(" Inactive Distance                 = {} cm\n",
224✔
525
      RandomRay::distance_inactive_);
526
    fmt::print(" Active Distance                   = {} cm\n",
224✔
527
      RandomRay::distance_active_);
528
    fmt::print(" Source Regions (SRs)              = {}\n", n_source_regions);
224✔
529
    fmt::print(
224✔
530
      " SRs Containing External Sources   = {}\n", n_external_source_regions);
531
    fmt::print(" Total Geometric Intersections     = {:.4e}\n",
448✔
532
      static_cast<double>(total_geometric_intersections));
224✔
533
    fmt::print("   Avg per Iteration               = {:.4e}\n",
448✔
534
      static_cast<double>(total_geometric_intersections) / settings::n_batches);
224✔
535
    fmt::print("   Avg per Iteration per SR        = {:.2f}\n",
448✔
536
      static_cast<double>(total_geometric_intersections) /
224✔
537
        static_cast<double>(settings::n_batches) / n_source_regions);
224✔
538
    fmt::print(" Avg SR Miss Rate per Iteration    = {:.4f}%\n", avg_miss_rate);
224✔
539
    fmt::print(" Energy Groups                     = {}\n", negroups);
224✔
540
    fmt::print(
224✔
541
      " Total Integrations                = {:.4e}\n", total_integrations);
542
    fmt::print("   Avg per Iteration               = {:.4e}\n",
448✔
543
      total_integrations / settings::n_batches);
224✔
544

545
    std::string estimator;
224!
546
    switch (domain_->volume_estimator_) {
224!
547
    case RandomRayVolumeEstimator::SIMULATION_AVERAGED:
8✔
548
      estimator = "Simulation Averaged";
8✔
549
      break;
550
    case RandomRayVolumeEstimator::NAIVE:
24✔
551
      estimator = "Naive";
24✔
552
      break;
553
    case RandomRayVolumeEstimator::HYBRID:
192✔
554
      estimator = "Hybrid";
192✔
555
      break;
556
    default:
×
557
      fatal_error("Invalid volume estimator type");
×
558
    }
559
    fmt::print(" Volume Estimator Type             = {}\n", estimator);
224✔
560

561
    std::string adjoint_true = (FlatSourceDomain::adjoint_) ? "ON" : "OFF";
652✔
562
    fmt::print(" Adjoint Flux Mode                 = {}\n", adjoint_true);
224✔
563

564
    std::string shape;
448!
565
    switch (RandomRay::source_shape_) {
224!
566
    case RandomRaySourceShape::FLAT:
120✔
567
      shape = "Flat";
120✔
568
      break;
569
    case RandomRaySourceShape::LINEAR:
92✔
570
      shape = "Linear";
92✔
571
      break;
572
    case RandomRaySourceShape::LINEAR_XY:
12✔
573
      shape = "Linear XY";
12✔
574
      break;
575
    default:
×
576
      fatal_error("Invalid random ray source shape");
×
577
    }
578
    fmt::print(" Source Shape                      = {}\n", shape);
224✔
579
    std::string sample_method;
448!
580
    switch (RandomRay::sample_method_) {
224!
581
    case RandomRaySampleMethod::PRNG:
216✔
582
      sample_method = "PRNG";
216✔
583
      break;
584
    case RandomRaySampleMethod::HALTON:
4✔
585
      sample_method = "Halton";
4✔
586
      break;
587
    case RandomRaySampleMethod::S2:
4✔
588
      sample_method = "PRNG S2";
4✔
589
      break;
590
    }
591
    fmt::print(" Sample Method                     = {}\n", sample_method);
224✔
592

593
    if (domain_->is_transport_stabilization_needed_) {
224✔
594
      fmt::print(" Transport XS Stabilization Used   = YES (rho = {:.3f})\n",
4✔
595
        FlatSourceDomain::diagonal_stabilization_rho_);
596
    } else {
597
      fmt::print(" Transport XS Stabilization Used   = NO\n");
220✔
598
    }
599

600
    header("Timing Statistics", 4);
224✔
601
    show_time("Total time for initialization", time_initialize.elapsed());
224✔
602
    show_time("Reading cross sections", time_read_xs.elapsed(), 1);
224✔
603
    show_time("Total simulation time", time_total.elapsed());
224✔
604
    show_time("Transport sweep only", time_transport.elapsed(), 1);
224✔
605
    show_time("Source update only", time_update_src.elapsed(), 1);
224✔
606
    show_time("Tally conversion only", time_tallies.elapsed(), 1);
224✔
607
    show_time("MPI source reductions only", time_bank_sendrecv.elapsed(), 1);
224✔
608
    show_time("Other iteration routines", misc_time, 1);
224✔
609
    if (settings::run_mode == RunMode::EIGENVALUE) {
224✔
610
      show_time("Time in inactive batches", time_inactive.elapsed());
100✔
611
    }
612
    show_time("Time in active batches", time_active.elapsed());
224✔
613
    show_time("Time writing statepoints", time_statepoint.elapsed());
224✔
614
    show_time("Total time for finalization", time_finalize.elapsed());
224✔
615
    show_time("Time per integration", time_per_integration);
224✔
616
  }
224✔
617

618
  if (settings::verbosity >= 4 && settings::run_mode == RunMode::EIGENVALUE) {
224!
619
    header("Results", 4);
100✔
620
    fmt::print(" k-effective                       = {:.5f} +/- {:.5f}\n",
100✔
621
      simulation::keff, simulation::keff_std);
622
  }
623
}
224✔
624

625
void openmc_finalize_random_ray()
2,704✔
626
{
627
  FlatSourceDomain::volume_estimator_ = RandomRayVolumeEstimator::HYBRID;
2,704✔
628
  FlatSourceDomain::volume_normalized_flux_tallies_ = false;
2,704✔
629
  FlatSourceDomain::adjoint_ = false;
2,704✔
630
  FlatSourceDomain::mesh_domain_map_.clear();
2,704✔
631
  RandomRay::ray_source_.reset();
2,704✔
632
  RandomRay::source_shape_ = RandomRaySourceShape::FLAT;
2,704✔
633
  RandomRay::sample_method_ = RandomRaySampleMethod::PRNG;
2,704✔
634
}
2,704✔
635

636
} // namespace openmc
637

638
//==============================================================================
639
// C API functions
640
//==============================================================================
641

642
void openmc_run_random_ray()
204✔
643
{
644
  //////////////////////////////////////////////////////////
645
  // Run forward simulation
646
  //////////////////////////////////////////////////////////
647

648
  if (openmc::mpi::master) {
204!
649
    if (openmc::FlatSourceDomain::adjoint_) {
204✔
650
      openmc::FlatSourceDomain::adjoint_ = false;
20✔
651
      openmc::print_adjoint_header();
20✔
652
      openmc::FlatSourceDomain::adjoint_ = true;
20✔
653
    }
654
  }
655

656
  // Initialize OpenMC general data structures
657
  openmc_simulation_init();
204✔
658

659
  // Validate that inputs meet requirements for random ray mode
660
  if (openmc::mpi::master)
204!
661
    openmc::validate_random_ray_inputs();
204✔
662

663
  // Initialize Random Ray Simulation Object
664
  openmc::RandomRaySimulation sim;
204✔
665

666
  // Initialize fixed sources, if present
667
  sim.apply_fixed_sources_and_mesh_domains();
204✔
668

669
  // Run initial random ray simulation
670
  sim.simulate();
204✔
671

672
  //////////////////////////////////////////////////////////
673
  // Run adjoint simulation (if enabled)
674
  //////////////////////////////////////////////////////////
675

676
  if (sim.adjoint_needed_) {
204✔
677
    // Setup for adjoint simulation
678
    sim.prepare_adjoint_simulation();
20✔
679

680
    // Run adjoint simulation
681
    sim.simulate();
20✔
682
  }
683
}
204✔
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