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

openmc-dev / openmc / 21321806885

24 Jan 2026 09:21PM UTC coverage: 81.864% (-0.1%) from 81.977%
21321806885

Pull #3749

github

web-flow
Merge 200b0cb49 into 3e2f1f521
Pull Request #3749: C-API bindings for key random ray functions

16678 of 22973 branches covered (72.6%)

Branch coverage included in aggregate %.

31 of 31 new or added lines in 3 files covered. (100.0%)

308 existing lines in 25 files now uncovered.

54820 of 64365 relevant lines covered (85.17%)

43016347.45 hits per line

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

82.39
/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()
460✔
31
{
32
  // Validate tallies
33
  ///////////////////////////////////////////////////////////////////
34
  for (auto& tally : model::tallies) {
1,340✔
35

36
    // Validate score types
37
    for (auto score_bin : tally->scores_) {
1,960✔
38
      switch (score_bin) {
1,080!
39
      case SCORE_FLUX:
1,080✔
40
      case SCORE_TOTAL:
41
      case SCORE_FISSION:
42
      case SCORE_NU_FISSION:
43
      case SCORE_EVENTS:
44
      case SCORE_KAPPA_FISSION:
45
        break;
1,080✔
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()) {
1,900✔
55
      auto& filter = *model::tally_filters[f];
1,020✔
56

57
      switch (filter.type()) {
1,020!
58
      case FilterType::CELL:
1,020✔
59
      case FilterType::CELL_INSTANCE:
60
      case FilterType::DISTRIBCELL:
61
      case FilterType::ENERGY:
62
      case FilterType::MATERIAL:
63
      case FilterType::MESH:
64
      case FilterType::UNIVERSE:
65
      case FilterType::PARTICLE:
66
        break;
1,020✔
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_) {
1,720✔
78
    if (!material.is_isotropic) {
1,260!
79
      fatal_error("Anisotropic MGXS detected. Only isotropic XS data sets "
×
80
                  "supported in random ray mode.");
81
    }
82
    if (material.get_xsdata().size() > 1) {
1,260!
83
      warning("Non-isothermal MGXS detected. Only isothermal XS data sets "
×
84
              "supported in random ray mode. Using lowest temperature.");
85
    }
86
    for (int g = 0; g < data::mg.num_energy_groups_; g++) {
6,810✔
87
      if (material.exists_in_model) {
5,550✔
88
        // Temperature and angle indices, if using multiple temperature
89
        // data sets and/or anisotropic data sets.
90
        // TODO: Currently assumes we are only using single temp/single angle
91
        // data.
92
        const int t = 0;
5,510✔
93
        const int a = 0;
5,510✔
94
        double sigma_t =
95
          material.get_xs(MgxsType::TOTAL, g, NULL, NULL, NULL, t, a);
5,510✔
96
        if (sigma_t <= 0.0) {
5,510!
97
          fatal_error("No zero or negative total macroscopic cross sections "
×
98
                      "allowed in random ray mode. If the intention is to make "
99
                      "a void material, use a cell fill of 'None' instead.");
100
        }
101
      }
102
    }
103
  }
104

105
  // Validate ray source
106
  ///////////////////////////////////////////////////////////////////
107

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

116
  // Check for box source
117
  SpatialDistribution* space_dist = is->space();
460✔
118
  SpatialBox* sb = dynamic_cast<SpatialBox*>(space_dist);
460!
119
  if (!sb) {
460!
120
    fatal_error(
×
121
      "Invalid ray source definition -- only box sources are allowed.");
122
  }
123

124
  // Check that box source is not restricted to fissionable areas
125
  if (sb->only_fissionable()) {
460!
126
    fatal_error(
×
127
      "Invalid ray source definition -- fissionable spatial distribution "
128
      "not allowed.");
129
  }
130

131
  // Check for isotropic source
132
  UnitSphereDistribution* angle_dist = is->angle();
460✔
133
  Isotropic* id = dynamic_cast<Isotropic*>(angle_dist);
460!
134
  if (!id) {
460!
135
    fatal_error("Invalid ray source definition -- only isotropic sources are "
×
136
                "allowed.");
137
  }
138

139
  // Validate external sources
140
  ///////////////////////////////////////////////////////////////////
141
  if (settings::run_mode == RunMode::FIXED_SOURCE) {
460✔
142
    if (model::external_sources.size() < 1) {
260!
143
      fatal_error("Must provide a particle source (in addition to ray source) "
×
144
                  "in fixed source random ray mode.");
145
    }
146

147
    for (int i = 0; i < model::external_sources.size(); i++) {
520✔
148
      Source* s = model::external_sources[i].get();
260✔
149

150
      // Check for independent source
151
      IndependentSource* is = dynamic_cast<IndependentSource*>(s);
260!
152

153
      if (!is) {
260!
154
        fatal_error(
×
155
          "Only IndependentSource external source types are allowed in "
156
          "random ray mode");
157
      }
158

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

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

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

196
  // Validate plotting files
197
  ///////////////////////////////////////////////////////////////////
198
  for (int p = 0; p < model::plots.size(); p++) {
460!
199

200
    // Get handle to OpenMC plot object
201
    const auto& openmc_plottable = model::plots[p];
×
202
    Plot* openmc_plot = dynamic_cast<Plot*>(openmc_plottable.get());
×
203

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

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

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

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

255
//==============================================================================
256
// RandomRaySimulation implementation
257
//==============================================================================
258

259
RandomRaySimulation::RandomRaySimulation()
640✔
260
  : negroups_(data::mg.num_energy_groups_)
640✔
261
{
262
  // There are no source sites in random ray mode, so be sure to disable to
263
  // ensure we don't attempt to write source sites to statepoint
264
  settings::source_write = false;
640✔
265

266
  // Random ray mode does not have an inner loop over generations within a
267
  // batch, so set the current gen to 1
268
  simulation::current_gen = 1;
640✔
269

270
  switch (RandomRay::source_shape_) {
640!
271
  case RandomRaySourceShape::FLAT:
332✔
272
    domain_ = make_unique<FlatSourceDomain>();
332✔
273
    break;
332✔
274
  case RandomRaySourceShape::LINEAR:
308✔
275
  case RandomRaySourceShape::LINEAR_XY:
276
    domain_ = make_unique<LinearSourceDomain>();
308✔
277
    break;
308✔
278
  default:
×
279
    fatal_error("Unknown random ray source shape");
×
280
  }
281

282
  // Convert OpenMC native MGXS into a more efficient format
283
  // internal to the random ray solver
284
  domain_->flatten_xs();
640✔
285

286
  // Check if adjoint calculation is needed. If it is, we will run the forward
287
  // calculation first and then the adjoint calculation later.
288
  adjoint_needed_ = FlatSourceDomain::adjoint_;
640✔
289

290
  // Adjoint is always false for the forward calculation
291
  FlatSourceDomain::adjoint_ = false;
640✔
292

293
  // The first simulation is run after initialization
294
  is_first_simulation_ = true;
640✔
295
}
640✔
296

297
void RandomRaySimulation::apply_fixed_sources_and_mesh_domains()
640✔
298
{
299
  domain_->apply_meshes();
640✔
300
  if (settings::run_mode == RunMode::FIXED_SOURCE) {
640✔
301
    // Transfer external source user inputs onto random ray source regions
302
    domain_->convert_external_sources();
364✔
303
    domain_->count_external_source_regions();
364✔
304
  }
305
}
640✔
306

307
void RandomRaySimulation::prepare_fixed_sources_adjoint()
70✔
308
{
309
  domain_->source_regions_.adjoint_reset();
70✔
310
  if (settings::run_mode == RunMode::FIXED_SOURCE) {
70✔
311
    domain_->set_adjoint_sources();
56✔
312
  }
313
}
70✔
314

315
void RandomRaySimulation::prepare_adjoint_simulation()
70✔
316
{
317
  // Configure the domain for adjoint simulation
318
  FlatSourceDomain::adjoint_ = true;
70✔
319

320
  // Reset k-eff
321
  domain_->k_eff_ = 1.0;
70✔
322

323
  // Initialize adjoint fixed sources, if present
324
  prepare_fixed_sources_adjoint();
70✔
325

326
  // Transpose scattering matrix
327
  domain_->transpose_scattering_matrix();
70✔
328

329
  // Swap nu_sigma_f and chi
330
  domain_->nu_sigma_f_.swap(domain_->chi_);
70✔
331
}
70✔
332

333
void RandomRaySimulation::simulate()
710✔
334
{
335
  if (!is_first_simulation_) {
710✔
336
    if (mpi::master && adjoint_needed_)
70!
337
      openmc::print_adjoint_header();
50✔
338

339
    // Reset the timers and reinitialize the general OpenMC datastructures if
340
    // this is after the first simulation
341
    reset_timers();
70✔
342

343
    // Initialize OpenMC general data structures
344
    openmc_simulation_init();
70✔
345
  }
346

347
  // Begin main simulation timer
348
  simulation::time_total.start();
710✔
349

350
  // Random ray power iteration loop
351
  while (simulation::current_batch < settings::n_batches) {
18,810✔
352
    // Initialize the current batch
353
    initialize_batch();
18,100✔
354
    initialize_generation();
18,100✔
355

356
    // MPI not supported in random ray solver, so all work is done by rank 0
357
    // TODO: Implement domain decomposition for MPI parallelism
358
    if (mpi::master) {
18,100✔
359

360
      // Reset total starting particle weight used for normalizing tallies
361
      simulation::total_weight = 1.0;
13,500✔
362

363
      // Update source term (scattering + fission)
364
      domain_->update_all_neutron_sources();
13,500✔
365

366
      // Reset scalar fluxes, iteration volume tallies, and region hit flags
367
      // to zero
368
      domain_->batch_reset();
13,500✔
369

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

377
      // Start timer for transport
378
      simulation::time_transport.start();
13,500✔
379

380
// Transport sweep over all random rays for the iteration
381
#pragma omp parallel for schedule(dynamic)                                     \
6,750✔
382
  reduction(+ : total_geometric_intersections_)
6,750✔
383
      for (int i = 0; i < settings::n_particles; i++) {
949,750✔
384
        RandomRay ray(i, domain_.get());
943,000✔
385
        total_geometric_intersections_ +=
943,000✔
386
          ray.transport_history_based_single_ray();
943,000✔
387
      }
943,000✔
388

389
      simulation::time_transport.stop();
13,500✔
390

391
      // Add any newly discovered source regions to the main source region
392
      // container.
393
      domain_->finalize_discovered_source_regions();
13,500✔
394

395
      // Normalize scalar flux and update volumes
396
      domain_->normalize_scalar_flux_and_volumes(
13,500✔
397
        settings::n_particles * RandomRay::distance_active_);
398

399
      // Add source to scalar flux, compute number of FSR hits
400
      int64_t n_hits = domain_->add_source_to_scalar_flux();
13,500✔
401

402
      // Apply transport stabilization factors
403
      domain_->apply_transport_stabilization();
13,500✔
404

405
      if (settings::run_mode == RunMode::EIGENVALUE) {
13,500✔
406
        // Compute random ray k-eff
407
        domain_->compute_k_eff();
4,700✔
408

409
        // Store random ray k-eff into OpenMC's native k-eff variable
410
        global_tally_tracklength = domain_->k_eff_;
4,700✔
411
      }
412

413
      // Execute all tallying tasks, if this is an active batch
414
      if (simulation::current_batch > settings::n_inactive) {
13,500✔
415

416
        // Add this iteration's scalar flux estimate to final accumulated
417
        // estimate
418
        domain_->accumulate_iteration_flux();
6,250✔
419

420
        // Use above mapping to contribute FSR flux data to appropriate
421
        // tallies
422
        domain_->random_ray_tally();
6,250✔
423
      }
424

425
      // Set phi_old = phi_new
426
      domain_->flux_swap();
13,500✔
427

428
      // Check for any obvious insabilities/nans/infs
429
      instability_check(n_hits, domain_->k_eff_, avg_miss_rate_);
13,500✔
430
    } // End MPI master work
431

432
    // Finalize the current batch
433
    finalize_generation();
18,100✔
434
    finalize_batch();
18,100✔
435
  } // End random ray power iteration loop
436

437
  domain_->count_external_source_regions();
710✔
438

439
  // End main simulation timer
440
  simulation::time_total.stop();
710✔
441

442
  // Normalize and save the final flux
443
  double source_normalization_factor =
444
    domain_->compute_fixed_source_normalization_factor() /
710✔
445
    (settings::n_batches - settings::n_inactive);
710✔
446

447
#pragma omp parallel for
355✔
448
  for (uint64_t se = 0; se < domain_->n_source_elements(); se++) {
1,344,775✔
449
    domain_->source_regions_.scalar_flux_final(se) *=
1,344,420✔
450
      source_normalization_factor;
451
  }
452

453
  // Finalize OpenMC
454
  openmc_simulation_finalize();
710✔
455

456
  // Output all simulation results
457
  output_simulation_results();
710✔
458

459
  // Toggle that the simulation object has been initialized after the first
460
  // simulation
461
  if (is_first_simulation_)
710✔
462
    is_first_simulation_ = false;
640✔
463
}
710✔
464

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

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

488
  if (mpi::master) {
13,500!
489
    if (percent_missed > 10.0) {
13,500✔
490
      warning(fmt::format(
870✔
491
        "Very high FSR miss rate detected ({:.3f}%). Instability may occur. "
492
        "Increase ray density by adding more rays and/or active distance.",
493
        percent_missed));
494
    } else if (percent_missed > 1.0) {
12,630!
UNCOV
495
      warning(
×
UNCOV
496
        fmt::format("Elevated FSR miss rate detected ({:.3f}%). Increasing "
×
497
                    "ray density by adding more rays and/or active "
498
                    "distance may improve simulation efficiency.",
499
          percent_missed));
500
    }
501

502
    if (k_eff > 10.0 || k_eff < 0.01 || !(std::isfinite(k_eff))) {
13,500!
503
      fatal_error(fmt::format("Instability detected: k-eff = {:.5f}", k_eff));
×
504
    }
505
  }
506
}
13,500✔
507

508
// Print random ray simulation results
509
void RandomRaySimulation::print_results_random_ray(
510✔
510
  uint64_t total_geometric_intersections, double avg_miss_rate, int negroups,
511
  int64_t n_source_regions, int64_t n_external_source_regions) const
512
{
513
  using namespace simulation;
514

515
  if (settings::verbosity >= 6) {
510!
516
    double total_integrations = total_geometric_intersections * negroups;
510✔
517
    double time_per_integration =
518
      simulation::time_transport.elapsed() / total_integrations;
510✔
519
    double misc_time = time_total.elapsed() - time_update_src.elapsed() -
510✔
520
                       time_transport.elapsed() - time_tallies.elapsed() -
510✔
521
                       time_bank_sendrecv.elapsed();
510✔
522

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

549
    std::string estimator;
510✔
550
    switch (domain_->volume_estimator_) {
510!
551
    case RandomRayVolumeEstimator::SIMULATION_AVERAGED:
20✔
552
      estimator = "Simulation Averaged";
20✔
553
      break;
20✔
554
    case RandomRayVolumeEstimator::NAIVE:
60✔
555
      estimator = "Naive";
60✔
556
      break;
60✔
557
    case RandomRayVolumeEstimator::HYBRID:
430✔
558
      estimator = "Hybrid";
430✔
559
      break;
430✔
560
    default:
×
561
      fatal_error("Invalid volume estimator type");
×
562
    }
563
    fmt::print(" Volume Estimator Type             = {}\n", estimator);
408✔
564

565
    std::string adjoint_true = (FlatSourceDomain::adjoint_) ? "ON" : "OFF";
1,530✔
566
    fmt::print(" Adjoint Flux Mode                 = {}\n", adjoint_true);
408✔
567

568
    std::string shape;
1,020✔
569
    switch (RandomRay::source_shape_) {
510!
570
    case RandomRaySourceShape::FLAT:
280✔
571
      shape = "Flat";
280✔
572
      break;
280✔
573
    case RandomRaySourceShape::LINEAR:
200✔
574
      shape = "Linear";
200✔
575
      break;
200✔
576
    case RandomRaySourceShape::LINEAR_XY:
30✔
577
      shape = "Linear XY";
30✔
578
      break;
30✔
579
    default:
×
580
      fatal_error("Invalid random ray source shape");
×
581
    }
582
    fmt::print(" Source Shape                      = {}\n", shape);
408✔
583
    std::string sample_method =
584
      (RandomRay::sample_method_ == RandomRaySampleMethod::PRNG) ? "PRNG"
510✔
585
                                                                 : "Halton";
1,530✔
586
    fmt::print(" Sample Method                     = {}\n", sample_method);
408✔
587

588
    if (domain_->is_transport_stabilization_needed_) {
510✔
589
      fmt::print(" Transport XS Stabilization Used   = YES (rho = {:.3f})\n",
10✔
590
        FlatSourceDomain::diagonal_stabilization_rho_);
591
    } else {
592
      fmt::print(" Transport XS Stabilization Used   = NO\n");
500✔
593
    }
594

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

613
  if (settings::verbosity >= 4 && settings::run_mode == RunMode::EIGENVALUE) {
510!
614
    header("Results", 4);
210✔
615
    fmt::print(" k-effective                       = {:.5f} +/- {:.5f}\n",
210✔
616
      simulation::keff, simulation::keff_std);
617
  }
618
}
510✔
619

620
void openmc_finalize_random_ray()
7,243✔
621
{
622
  FlatSourceDomain::volume_estimator_ = RandomRayVolumeEstimator::HYBRID;
7,243✔
623
  FlatSourceDomain::volume_normalized_flux_tallies_ = false;
7,243✔
624
  FlatSourceDomain::adjoint_ = false;
7,243✔
625
  FlatSourceDomain::mesh_domain_map_.clear();
7,243✔
626
  RandomRay::ray_source_.reset();
7,243✔
627
  RandomRay::source_shape_ = RandomRaySourceShape::FLAT;
7,243✔
628
  RandomRay::sample_method_ = RandomRaySampleMethod::PRNG;
7,243✔
629
}
7,243✔
630

631
} // namespace openmc
632

633
//==============================================================================
634
// C API functions
635
//==============================================================================
636

637
void openmc_run_random_ray()
640✔
638
{
639
  //////////////////////////////////////////////////////////
640
  // Run forward simulation
641
  //////////////////////////////////////////////////////////
642

643
  if (openmc::mpi::master) {
640✔
644
    if (openmc::FlatSourceDomain::adjoint_) {
460✔
645
      openmc::FlatSourceDomain::adjoint_ = false;
50✔
646
      openmc::print_adjoint_header();
50✔
647
      openmc::FlatSourceDomain::adjoint_ = true;
50✔
648
    }
649
  }
650

651
  // Initialize OpenMC general data structures
652
  openmc_simulation_init();
640✔
653

654
  // Validate that inputs meet requirements for random ray mode
655
  if (openmc::mpi::master)
640✔
656
    openmc::validate_random_ray_inputs();
460✔
657

658
  // Initialize Random Ray Simulation Object
659
  openmc::RandomRaySimulation sim;
640✔
660

661
  // Initialize fixed sources, if present
662
  sim.apply_fixed_sources_and_mesh_domains();
640✔
663

664
  // Run initial random ray simulation
665
  sim.simulate();
640✔
666

667
  //////////////////////////////////////////////////////////
668
  // Run adjoint simulation (if enabled)
669
  //////////////////////////////////////////////////////////
670

671
  if (sim.adjoint_needed_) {
640✔
672
    // Setup for adjoint simulation
673
    sim.prepare_adjoint_simulation();
70✔
674

675
    // Run adjoint simulation
676
    sim.simulate();
70✔
677
  }
678
}
640✔
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