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

openmc-dev / openmc / 12845926873

18 Jan 2025 05:12PM UTC coverage: 84.871% (+0.004%) from 84.867%
12845926873

Pull #3268

github

web-flow
Merge 5083584b7 into bd874f1b3
Pull Request #3268: Randomized Quasi-Monte Carlo Sampling in The Random Ray Method

74 of 82 new or added lines in 4 files covered. (90.24%)

12 existing lines in 1 file now uncovered.

50146 of 59085 relevant lines covered (84.87%)

34299115.99 hits per line

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

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

3
#include "openmc/constants.h"
4
#include "openmc/geometry.h"
5
#include "openmc/message_passing.h"
6
#include "openmc/mgxs_interface.h"
7
#include "openmc/random_ray/flat_source_domain.h"
8
#include "openmc/random_ray/linear_source_domain.h"
9
#include "openmc/search.h"
10
#include "openmc/settings.h"
11
#include "openmc/simulation.h"
12

13
#include "openmc/distribution_spatial.h"
14
#include "openmc/random_dist.h"
15
#include "openmc/source.h"
16

17
namespace openmc {
18

19
//==============================================================================
20
// Non-method functions
21
//==============================================================================
22

23
// returns 1 - exp(-tau)
24
// Equivalent to -(_expm1f(-tau)), but faster
25
// Written by Colin Josey.
26
float cjosey_exponential(float tau)
530,561,592✔
27
{
28
  constexpr float c1n = -1.0000013559236386308f;
530,561,592✔
29
  constexpr float c2n = 0.23151368626911062025f;
530,561,592✔
30
  constexpr float c3n = -0.061481916409314966140f;
530,561,592✔
31
  constexpr float c4n = 0.0098619906458127653020f;
530,561,592✔
32
  constexpr float c5n = -0.0012629460503540849940f;
530,561,592✔
33
  constexpr float c6n = 0.00010360973791574984608f;
530,561,592✔
34
  constexpr float c7n = -0.000013276571933735820960f;
530,561,592✔
35

36
  constexpr float c0d = 1.0f;
530,561,592✔
37
  constexpr float c1d = -0.73151337729389001396f;
530,561,592✔
38
  constexpr float c2d = 0.26058381273536471371f;
530,561,592✔
39
  constexpr float c3d = -0.059892419041316836940f;
530,561,592✔
40
  constexpr float c4d = 0.0099070188241094279067f;
530,561,592✔
41
  constexpr float c5d = -0.0012623388962473160860f;
530,561,592✔
42
  constexpr float c6d = 0.00010361277635498731388f;
530,561,592✔
43
  constexpr float c7d = -0.000013276569500666698498f;
530,561,592✔
44

45
  float x = -tau;
530,561,592✔
46

47
  float den = c7d;
530,561,592✔
48
  den = den * x + c6d;
530,561,592✔
49
  den = den * x + c5d;
530,561,592✔
50
  den = den * x + c4d;
530,561,592✔
51
  den = den * x + c3d;
530,561,592✔
52
  den = den * x + c2d;
530,561,592✔
53
  den = den * x + c1d;
530,561,592✔
54
  den = den * x + c0d;
530,561,592✔
55

56
  float num = c7n;
530,561,592✔
57
  num = num * x + c6n;
530,561,592✔
58
  num = num * x + c5n;
530,561,592✔
59
  num = num * x + c4n;
530,561,592✔
60
  num = num * x + c3n;
530,561,592✔
61
  num = num * x + c2n;
530,561,592✔
62
  num = num * x + c1n;
530,561,592✔
63
  num = num * x;
530,561,592✔
64

65
  return num / den;
530,561,592✔
66
}
67

68
// The below two functions (exponentialG and exponentialG2) were developed
69
// by Colin Josey. The implementation of these functions is closely based
70
// on the OpenMOC versions of these functions. The OpenMOC license is given
71
// below:
72

73
// Copyright (C) 2012-2023 Massachusetts Institute of Technology and OpenMOC
74
// contributors
75
//
76
// Permission is hereby granted, free of charge, to any person obtaining a copy
77
// of this software and associated documentation files (the "Software"), to deal
78
// in the Software without restriction, including without limitation the rights
79
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
80
// copies of the Software, and to permit persons to whom the Software is
81
// furnished to do so, subject to the following conditions:
82
//
83
// The above copyright notice and this permission notice shall be included in
84
// all copies or substantial portions of the Software.
85
//
86
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
87
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
88
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
89
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
90
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
91
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
92
// SOFTWARE.
93

94
// Computes y = 1/x-(1-exp(-x))/x**2 using a 5/6th order rational
95
// approximation. It is accurate to 2e-7 over [0, 1e5]. Developed by Colin
96
// Josey using Remez's algorithm, with original implementation in OpenMOC at:
97
// https://github.com/mit-crpg/OpenMOC/blob/develop/src/exponentials.h
98
float exponentialG(float tau)
861,279,828✔
99
{
100
  // Numerator coefficients in rational approximation for 1/x - (1 - exp(-x)) /
101
  // x^2
102
  constexpr float d0n = 0.5f;
861,279,828✔
103
  constexpr float d1n = 0.176558112351595f;
861,279,828✔
104
  constexpr float d2n = 0.04041584305811143f;
861,279,828✔
105
  constexpr float d3n = 0.006178333902037397f;
861,279,828✔
106
  constexpr float d4n = 0.0006429894635552992f;
861,279,828✔
107
  constexpr float d5n = 0.00006064409107557148f;
861,279,828✔
108

109
  // Denominator coefficients in rational approximation for 1/x - (1 - exp(-x))
110
  // / x^2
111
  constexpr float d0d = 1.0f;
861,279,828✔
112
  constexpr float d1d = 0.6864462055546078f;
861,279,828✔
113
  constexpr float d2d = 0.2263358514260129f;
861,279,828✔
114
  constexpr float d3d = 0.04721469893686252f;
861,279,828✔
115
  constexpr float d4d = 0.006883236664917246f;
861,279,828✔
116
  constexpr float d5d = 0.0007036272419147752f;
861,279,828✔
117
  constexpr float d6d = 0.00006064409107557148f;
861,279,828✔
118

119
  float x = tau;
861,279,828✔
120

121
  float num = d5n;
861,279,828✔
122
  num = num * x + d4n;
861,279,828✔
123
  num = num * x + d3n;
861,279,828✔
124
  num = num * x + d2n;
861,279,828✔
125
  num = num * x + d1n;
861,279,828✔
126
  num = num * x + d0n;
861,279,828✔
127

128
  float den = d6d;
861,279,828✔
129
  den = den * x + d5d;
861,279,828✔
130
  den = den * x + d4d;
861,279,828✔
131
  den = den * x + d3d;
861,279,828✔
132
  den = den * x + d2d;
861,279,828✔
133
  den = den * x + d1d;
861,279,828✔
134
  den = den * x + d0d;
861,279,828✔
135

136
  return num / den;
861,279,828✔
137
}
138

139
// Computes G2 : y = 2/3 - (1 + 2/x) * (1/x + 0.5 - (1 + 1/x) * (1-exp(-x)) /
140
// x) using a 5/5th order rational approximation. It is accurate to 1e-6 over
141
// [0, 1e6]. Developed by Colin Josey using Remez's algorithm, with original
142
// implementation in OpenMOC at:
143
// https://github.com/mit-crpg/OpenMOC/blob/develop/src/exponentials.h
144
float exponentialG2(float tau)
861,279,828✔
145
{
146

147
  // Coefficients for numerator in rational approximation
148
  constexpr float g1n = -0.08335775885589858f;
861,279,828✔
149
  constexpr float g2n = -0.003603942303847604f;
861,279,828✔
150
  constexpr float g3n = 0.0037673183263550827f;
861,279,828✔
151
  constexpr float g4n = 0.00001124183494990467f;
861,279,828✔
152
  constexpr float g5n = 0.00016837426505799449f;
861,279,828✔
153

154
  // Coefficients for denominator in rational approximation
155
  constexpr float g1d = 0.7454048371823628f;
861,279,828✔
156
  constexpr float g2d = 0.23794300531408347f;
861,279,828✔
157
  constexpr float g3d = 0.05367250964303789f;
861,279,828✔
158
  constexpr float g4d = 0.006125197988351906f;
861,279,828✔
159
  constexpr float g5d = 0.0010102514456857377f;
861,279,828✔
160

161
  float x = tau;
861,279,828✔
162

163
  float num = g5n;
861,279,828✔
164
  num = num * x + g4n;
861,279,828✔
165
  num = num * x + g3n;
861,279,828✔
166
  num = num * x + g2n;
861,279,828✔
167
  num = num * x + g1n;
861,279,828✔
168
  num = num * x;
861,279,828✔
169

170
  float den = g5d;
861,279,828✔
171
  den = den * x + g4d;
861,279,828✔
172
  den = den * x + g3d;
861,279,828✔
173
  den = den * x + g2d;
861,279,828✔
174
  den = den * x + g1d;
861,279,828✔
175
  den = den * x + 1.0f;
861,279,828✔
176

177
  return num / den;
861,279,828✔
178
}
179

180
// Implementation of the Fisher-Yates shuffle algorithm.
181
// Algorithm adapted from:
182
//    https://en.cppreference.com/w/cpp/algorithm/random_shuffle#Version_3
183
void fisher_yates_shuffle(vector<int>& arr, uint64_t* seed)
1,728,000✔
184
{
185
  // Loop over the array from the last element down to the second
186
  for (size_t i = arr.size() - 1; i > 0; --i) {
7,452,000✔
187
    // Generate a random index in the range [0, i]
188
    size_t j = uniform_int_distribution(0, i, seed);
5,724,000✔
189
    // Swap arr[i] with arr[j]
190
    std::swap(arr[i], arr[j]);
5,724,000✔
191
  }
192
}
1,728,000✔
193

194
// Function to generate randomized Halton sequence samples
195
//
196
// Algorithm adapted from:
197
//      A. B. Owen. A randomized halton algorithm in r. Arxiv, 6 2017.
198
//      URL https://arxiv.org/abs/1706.02808
199
vector<vector<float>> rhalton(int N, int dim, uint64_t* seed, int64_t skip = 0)
12,000✔
200
{
201
  int b;
202
  double b2r;
203
  vector<double> ans(N);
12,000✔
204
  vector<int> ind(N);
12,000✔
205
  vector<int> primes = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29};
12,000✔
206
  vector<vector<float>> halton(N, vector<float>(dim, 0.0));
24,000✔
207

208
  std::iota(ind.begin(), ind.end(), skip);
12,000✔
209

210
  for (int D = 0; D < dim; ++D) {
72,000✔
211
    b = primes[D];
60,000✔
212
    b2r = 1.0 / b;
60,000✔
213
    vector<int> res(ind);
60,000✔
214
    std::fill(ans.begin(), ans.end(), 0.0);
60,000✔
215

216
    while ((1.0 - b2r) < 1.0) {
1,788,000✔
217
      vector<int> dig(N);
1,728,000✔
218
      // randomaly permute a sequence from skip to skip+N
219
      vector<int> perm(b);
1,728,000✔
220
      std::iota(perm.begin(), perm.end(), 0);
1,728,000✔
221
      fisher_yates_shuffle(perm, seed);
1,728,000✔
222

223
      for (int i = 0; i < N; ++i) {
3,456,000✔
224
        dig[i] = res[i] % b;
1,728,000✔
225
        ans[i] += perm[dig[i]] * b2r;
1,728,000✔
226
        res[i] = (res[i] - dig[i]) / b;
1,728,000✔
227
      }
228
      b2r /= b;
1,728,000✔
229
    }
1,728,000✔
230

231
    for (int i = 0; i < N; ++i) {
120,000✔
232
      halton[i][D] = ans[i];
60,000✔
233
    }
234
  }
60,000✔
235

236
  return halton;
24,000✔
237
}
12,000✔
238

239
//==============================================================================
240
// RandomRay implementation
241
//==============================================================================
242

243
// Static Variable Declarations
244
double RandomRay::distance_inactive_;
245
double RandomRay::distance_active_;
246
unique_ptr<Source> RandomRay::ray_source_;
247
RandomRaySourceShape RandomRay::source_shape_ {RandomRaySourceShape::FLAT};
248
RandomRaySampleMethod RandomRay::sample_method_ {RandomRaySampleMethod::PRNG};
249

250
RandomRay::RandomRay()
570,000✔
251
  : angular_flux_(data::mg.num_energy_groups_),
570,000✔
252
    delta_psi_(data::mg.num_energy_groups_),
570,000✔
253
    negroups_(data::mg.num_energy_groups_)
1,710,000✔
254
{
255
  if (source_shape_ == RandomRaySourceShape::LINEAR ||
570,000✔
256
      source_shape_ == RandomRaySourceShape::LINEAR_XY) {
349,200✔
257
    delta_moments_.resize(negroups_);
357,000✔
258
  }
259
}
570,000✔
260

261
RandomRay::RandomRay(uint64_t ray_id, FlatSourceDomain* domain) : RandomRay()
570,000✔
262
{
263
  initialize_ray(ray_id, domain);
570,000✔
264
}
570,000✔
265

266
// Transports ray until termination criteria are met
267
uint64_t RandomRay::transport_history_based_single_ray()
570,000✔
268
{
269
  using namespace openmc;
270
  while (alive()) {
299,373,348✔
271
    event_advance_ray();
299,373,348✔
272
    if (!alive())
299,373,348✔
273
      break;
570,000✔
274
    event_cross_surface();
298,803,348✔
275
  }
276

277
  return n_event();
570,000✔
278
}
279

280
// Transports ray across a single source region
281
void RandomRay::event_advance_ray()
299,373,348✔
282
{
283
  // Find the distance to the nearest boundary
284
  boundary() = distance_to_boundary(*this);
299,373,348✔
285
  double distance = boundary().distance;
299,373,348✔
286

287
  if (distance <= 0.0) {
299,373,348✔
UNCOV
288
    mark_as_lost("Negative transport distance detected for particle " +
×
UNCOV
289
                 std::to_string(id()));
×
UNCOV
290
    return;
×
291
  }
292

293
  if (is_active_) {
299,373,348✔
294
    // If the ray is in the active length, need to check if it has
295
    // reached its maximum termination distance. If so, reduce
296
    // the ray traced length so that the ray does not overrun the
297
    // maximum numerical length (so as to avoid numerical bias).
298
    if (distance_travelled_ + distance >= distance_active_) {
230,636,880✔
299
      distance = distance_active_ - distance_travelled_;
570,000✔
300
      wgt() = 0.0;
570,000✔
301
    }
302

303
    distance_travelled_ += distance;
230,636,880✔
304
    attenuate_flux(distance, true);
230,636,880✔
305
  } else {
306
    // If the ray is still in the dead zone, need to check if it
307
    // has entered the active phase. If so, split into two segments (one
308
    // representing the final part of the dead zone, the other representing the
309
    // first part of the active length) and attenuate each. Otherwise, if the
310
    // full length of the segment is within the dead zone, attenuate as normal.
311
    if (distance_travelled_ + distance >= distance_inactive_) {
68,736,468✔
312
      is_active_ = true;
570,000✔
313
      double distance_dead = distance_inactive_ - distance_travelled_;
570,000✔
314
      attenuate_flux(distance_dead, false);
570,000✔
315

316
      double distance_alive = distance - distance_dead;
570,000✔
317

318
      // Ensure we haven't travelled past the active phase as well
319
      if (distance_alive > distance_active_) {
570,000✔
UNCOV
320
        distance_alive = distance_active_;
×
UNCOV
321
        wgt() = 0.0;
×
322
      }
323

324
      attenuate_flux(distance_alive, true);
570,000✔
325
      distance_travelled_ = distance_alive;
570,000✔
326
    } else {
327
      distance_travelled_ += distance;
68,166,468✔
328
      attenuate_flux(distance, false);
68,166,468✔
329
    }
330
  }
331

332
  // Advance particle
333
  for (int j = 0; j < n_coord(); ++j) {
1,196,850,276✔
334
    coord(j).r += distance * coord(j).u;
897,476,928✔
335
  }
336
}
337

338
void RandomRay::attenuate_flux(double distance, bool is_active)
299,943,348✔
339
{
340
  switch (source_shape_) {
299,943,348✔
341
  case RandomRaySourceShape::FLAT:
109,793,664✔
342
    attenuate_flux_flat_source(distance, is_active);
109,793,664✔
343
    break;
109,793,664✔
344
  case RandomRaySourceShape::LINEAR:
190,149,684✔
345
  case RandomRaySourceShape::LINEAR_XY:
346
    attenuate_flux_linear_source(distance, is_active);
190,149,684✔
347
    break;
190,149,684✔
UNCOV
348
  default:
×
UNCOV
349
    fatal_error("Unknown source shape for random ray transport.");
×
350
  }
351
}
299,943,348✔
352

353
// This function forms the inner loop of the random ray transport process.
354
// It is responsible for several tasks. Based on the incoming angular flux
355
// of the ray and the source term in the region, the outgoing angular flux
356
// is computed. The delta psi between the incoming and outgoing fluxes is
357
// contributed to the estimate of the total scalar flux in the source region.
358
// Additionally, the contribution of the ray path to the stochastically
359
// estimated volume is also kept track of. All tasks involving writing
360
// to the data for the source region are done with a lock over the entire
361
// source region.  Locks are used instead of atomics as all energy groups
362
// must be written, such that locking once is typically much more efficient
363
// than use of many atomic operations corresponding to each energy group
364
// individually (at least on CPU). Several other bookkeeping tasks are also
365
// performed when inside the lock.
366
void RandomRay::attenuate_flux_flat_source(double distance, bool is_active)
109,793,664✔
367
{
368
  // The number of geometric intersections is counted for reporting purposes
369
  n_event()++;
109,793,664✔
370

371
  // Determine source region index etc.
372
  int i_cell = lowest_coord().cell;
109,793,664✔
373

374
  // The source region is the spatial region index
375
  int64_t source_region =
376
    domain_->source_region_offsets_[i_cell] + cell_instance();
109,793,664✔
377

378
  // The source element is the energy-specific region index
379
  int64_t source_element = source_region * negroups_;
109,793,664✔
380
  int material = this->material();
109,793,664✔
381

382
  // MOC incoming flux attenuation + source contribution/attenuation equation
383
  for (int g = 0; g < negroups_; g++) {
640,355,256✔
384
    float sigma_t = domain_->sigma_t_[material * negroups_ + g];
530,561,592✔
385
    float tau = sigma_t * distance;
530,561,592✔
386
    float exponential = cjosey_exponential(tau); // exponential = 1 - exp(-tau)
530,561,592✔
387
    float new_delta_psi =
388
      (angular_flux_[g] - domain_->source_[source_element + g]) * exponential;
530,561,592✔
389
    delta_psi_[g] = new_delta_psi;
530,561,592✔
390
    angular_flux_[g] -= new_delta_psi;
530,561,592✔
391
  }
392

393
  // If ray is in the active phase (not in dead zone), make contributions to
394
  // source region bookkeeping
395
  if (is_active) {
109,793,664✔
396

397
    // Aquire lock for source region
398
    domain_->lock_[source_region].lock();
82,199,460✔
399

400
    // Accumulate delta psi into new estimate of source region flux for
401
    // this iteration
402
    for (int g = 0; g < negroups_; g++) {
459,445,200✔
403
      domain_->scalar_flux_new_[source_element + g] += delta_psi_[g];
377,245,740✔
404
    }
405

406
    // Accomulate volume (ray distance) into this iteration's estimate
407
    // of the source region's volume
408
    domain_->volume_[source_region] += distance;
82,199,460✔
409

410
    // Tally valid position inside the source region (e.g., midpoint of
411
    // the ray) if not done already
412
    if (!domain_->position_recorded_[source_region]) {
82,199,460✔
413
      Position midpoint = r() + u() * (distance / 2.0);
324,797✔
414
      domain_->position_[source_region] = midpoint;
324,797✔
415
      domain_->position_recorded_[source_region] = 1;
324,797✔
416
    }
417

418
    // Release lock
419
    domain_->lock_[source_region].unlock();
82,199,460✔
420
  }
421
}
109,793,664✔
422

423
void RandomRay::attenuate_flux_linear_source(double distance, bool is_active)
190,149,684✔
424
{
425
  // Cast domain to LinearSourceDomain
426
  LinearSourceDomain* domain = dynamic_cast<LinearSourceDomain*>(domain_);
190,149,684✔
427
  if (!domain) {
190,149,684✔
UNCOV
428
    fatal_error("RandomRay::attenuate_flux_linear_source() called with "
×
429
                "non-LinearSourceDomain domain.");
430
  }
431

432
  // The number of geometric intersections is counted for reporting purposes
433
  n_event()++;
190,149,684✔
434

435
  // Determine source region index etc.
436
  int i_cell = lowest_coord().cell;
190,149,684✔
437

438
  // The source region is the spatial region index
439
  int64_t source_region =
440
    domain_->source_region_offsets_[i_cell] + cell_instance();
190,149,684✔
441

442
  // The source element is the energy-specific region index
443
  int64_t source_element = source_region * negroups_;
190,149,684✔
444
  int material = this->material();
190,149,684✔
445

446
  Position& centroid = domain->centroid_[source_region];
190,149,684✔
447
  Position midpoint = r() + u() * (distance / 2.0);
190,149,684✔
448

449
  // Determine the local position of the midpoint and the ray origin
450
  // relative to the source region's centroid
451
  Position rm_local;
190,149,684✔
452
  Position r0_local;
190,149,684✔
453

454
  // In the first few iterations of the simulation, the source region
455
  // may not yet have had any ray crossings, in which case there will
456
  // be no estimate of its centroid. We detect this by checking if it has
457
  // any accumulated volume. If its volume is zero, just use the midpoint
458
  // of the ray as the region's centroid.
459
  if (domain->volume_t_[source_region]) {
190,149,684✔
460
    rm_local = midpoint - centroid;
185,861,784✔
461
    r0_local = r() - centroid;
185,861,784✔
462
  } else {
463
    rm_local = {0.0, 0.0, 0.0};
4,287,900✔
464
    r0_local = -u() * 0.5 * distance;
4,287,900✔
465
  }
466
  double distance_2 = distance * distance;
190,149,684✔
467

468
  // Linear Source MOC incoming flux attenuation + source
469
  // contribution/attenuation equation
470
  for (int g = 0; g < negroups_; g++) {
1,051,429,512✔
471

472
    // Compute tau, the optical thickness of the ray segment
473
    float sigma_t = domain_->sigma_t_[material * negroups_ + g];
861,279,828✔
474
    float tau = sigma_t * distance;
861,279,828✔
475

476
    // If tau is very small, set it to zero to avoid numerical issues.
477
    // The following computations will still work with tau = 0.
478
    if (tau < 1.0e-8f) {
861,279,828✔
479
      tau = 0.0f;
300✔
480
    }
481

482
    // Compute linear source terms, spatial and directional (dir),
483
    // calculated from the source gradients dot product with local centroid
484
    // and direction, respectively.
485
    float spatial_source =
486
      domain_->source_[source_element + g] +
861,279,828✔
487
      rm_local.dot(domain->source_gradients_[source_element + g]);
861,279,828✔
488
    float dir_source = u().dot(domain->source_gradients_[source_element + g]);
861,279,828✔
489

490
    float gn = exponentialG(tau);
861,279,828✔
491
    float f1 = 1.0f - tau * gn;
861,279,828✔
492
    float f2 = (2.0f * gn - f1) * distance_2;
861,279,828✔
493
    float new_delta_psi = (angular_flux_[g] - spatial_source) * f1 * distance -
861,279,828✔
494
                          0.5 * dir_source * f2;
861,279,828✔
495

496
    float h1 = f1 - gn;
861,279,828✔
497
    float g1 = 0.5f - h1;
861,279,828✔
498
    float g2 = exponentialG2(tau);
861,279,828✔
499
    g1 = g1 * spatial_source;
861,279,828✔
500
    g2 = g2 * dir_source * distance * 0.5f;
861,279,828✔
501
    h1 = h1 * angular_flux_[g];
861,279,828✔
502
    h1 = (g1 + g2 + h1) * distance_2;
861,279,828✔
503
    spatial_source = spatial_source * distance + new_delta_psi;
861,279,828✔
504

505
    // Store contributions for this group into arrays, so that they can
506
    // be accumulated into the source region's estimates inside of the locked
507
    // region.
508
    delta_psi_[g] = new_delta_psi;
861,279,828✔
509
    delta_moments_[g] = r0_local * spatial_source + u() * h1;
861,279,828✔
510

511
    // Update the angular flux for this group
512
    angular_flux_[g] -= new_delta_psi * sigma_t;
861,279,828✔
513

514
    // If 2D mode is enabled, the z-component of the flux moments is forced
515
    // to zero
516
    if (source_shape_ == RandomRaySourceShape::LINEAR_XY) {
861,279,828✔
517
      delta_moments_[g].z = 0.0;
504,226,956✔
518
    }
519
  }
520

521
  // If ray is in the active phase (not in dead zone), make contributions to
522
  // source region bookkeeping
523
  if (is_active) {
190,149,684✔
524
    // Compute an estimate of the spatial moments matrix for the source
525
    // region based on parameters from this ray's crossing
526
    MomentMatrix moment_matrix_estimate;
527
    moment_matrix_estimate.compute_spatial_moments_matrix(
149,007,420✔
528
      rm_local, u(), distance);
149,007,420✔
529

530
    // Aquire lock for source region
531
    domain_->lock_[source_region].lock();
149,007,420✔
532

533
    // Accumulate deltas into the new estimate of source region flux for this
534
    // iteration
535
    for (int g = 0; g < negroups_; g++) {
801,449,280✔
536
      domain_->scalar_flux_new_[source_element + g] += delta_psi_[g];
652,441,860✔
537
      domain->flux_moments_new_[source_element + g] += delta_moments_[g];
652,441,860✔
538
    }
539

540
    // Accumulate the volume (ray segment distance), centroid, and spatial
541
    // momement estimates into the running totals for the iteration for this
542
    // source region. The centroid and spatial momements estimates are scaled by
543
    // the ray segment length as part of length averaging of the estimates.
544
    domain_->volume_[source_region] += distance;
149,007,420✔
545
    domain->centroid_iteration_[source_region] += midpoint * distance;
149,007,420✔
546
    moment_matrix_estimate *= distance;
149,007,420✔
547
    domain->mom_matrix_[source_region] += moment_matrix_estimate;
149,007,420✔
548

549
    // Tally valid position inside the source region (e.g., midpoint of
550
    // the ray) if not done already
551
    if (!domain_->position_recorded_[source_region]) {
149,007,420✔
552
      domain_->position_[source_region] = midpoint;
161,067✔
553
      domain_->position_recorded_[source_region] = 1;
161,067✔
554
    }
555

556
    // Release lock
557
    domain_->lock_[source_region].unlock();
149,007,420✔
558
  }
559
}
190,149,684✔
560

561
void RandomRay::initialize_ray(uint64_t ray_id, FlatSourceDomain* domain)
570,000✔
562
{
563
  domain_ = domain;
570,000✔
564

565
  // Reset particle event counter
566
  n_event() = 0;
570,000✔
567

568
  is_active_ = (distance_inactive_ <= 0.0);
570,000✔
569

570
  wgt() = 1.0;
570,000✔
571

572
  // set identifier for particle
573
  id() = simulation::work_index[mpi::rank] + ray_id;
570,000✔
574

575
  // generate source site using sample method
576
  SourceSite site;
570,000✔
577
  switch (sample_method_) {
570,000✔
578
  case RandomRaySampleMethod::PRNG:
558,000✔
579
    site = sample_prng();
558,000✔
580
    break;
558,000✔
581
  case RandomRaySampleMethod::HALTON:
12,000✔
582
    site = sample_halton();
12,000✔
583
    break;
12,000✔
NEW
UNCOV
584
  default:
×
NEW
UNCOV
585
    fatal_error("Unknown sample method for random ray transport.");
×
586
  }
587

588
  site.E = lower_bound_index(
570,000✔
589
    data::mg.rev_energy_bins_.begin(), data::mg.rev_energy_bins_.end(), site.E);
590
  site.E = negroups_ - site.E - 1.;
570,000✔
591
  this->from_source(&site);
570,000✔
592

593
  // Locate ray
594
  if (lowest_coord().cell == C_NONE) {
570,000✔
595
    if (!exhaustive_find_cell(*this)) {
570,000✔
UNCOV
596
      this->mark_as_lost(
×
UNCOV
597
        "Could not find the cell containing particle " + std::to_string(id()));
×
598
    }
599

600
    // Set birth cell attribute
601
    if (cell_born() == C_NONE)
570,000✔
602
      cell_born() = lowest_coord().cell;
570,000✔
603
  }
604

605
  // Initialize ray's starting angular flux to starting location's isotropic
606
  // source
607
  int i_cell = lowest_coord().cell;
570,000✔
608
  int64_t source_region_idx =
609
    domain_->source_region_offsets_[i_cell] + cell_instance();
570,000✔
610

611
  for (int g = 0; g < negroups_; g++) {
2,544,000✔
612
    angular_flux_[g] = domain_->source_[source_region_idx * negroups_ + g];
1,974,000✔
613
  }
614
}
570,000✔
615

616
SourceSite RandomRay::sample_prng()
558,000✔
617
{
618
  // set random number seed
619
  int64_t particle_seed =
620
    (simulation::current_batch - 1) * settings::n_particles + id();
558,000✔
621
  init_particle_seeds(particle_seed, seeds());
558,000✔
622
  stream() = STREAM_TRACKING;
558,000✔
623

624
  // Sample from ray source distribution
625
  SourceSite site {ray_source_->sample(current_seed())};
558,000✔
626

627
  return site;
558,000✔
628
}
629

630
SourceSite RandomRay::sample_halton()
12,000✔
631
{
632
  SourceSite site;
12,000✔
633

634
  // Set random number seed
635
  int64_t batch_seed = (simulation::current_batch - 1) * settings::n_particles;
12,000✔
636
  int64_t skip = id();
12,000✔
637
  init_particle_seeds(batch_seed, seeds());
12,000✔
638
  stream() = STREAM_TRACKING;
12,000✔
639

640
  // Calculate next samples in LDS
641
  vector<vector<float>> samples = rhalton(1, 5, current_seed(), skip = skip);
12,000✔
642

643
  // Get spatial box of ray_source_
644
  SpatialBox* sb = dynamic_cast<SpatialBox*>(
12,000✔
645
    dynamic_cast<IndependentSource*>(RandomRay::ray_source_.get())->space());
12,000✔
646

647
  // Sample spatial distribution
648
  Position xi {samples[0][0], samples[0][1], samples[0][2]};
12,000✔
649
  Position shift {1e-9, 1e-9, 1e-9};
12,000✔
650
  site.r = (sb->lower_left() + shift) +
651
           xi * ((sb->upper_right() - shift) - (sb->lower_left() + shift));
12,000✔
652

653
  // Sample Polar cosine and azimuthal angles
654
  float mu = 2.0 * samples[0][3] - 1.0;
12,000✔
655
  float azi = 2.0 * PI * samples[0][4];
12,000✔
656
  // Convert to Cartesian coordinates
657
  float c = std::sqrt(1.0 - mu * mu);
12,000✔
658
  site.u.x = mu;
12,000✔
659
  site.u.y = std::cos(azi) * c;
12,000✔
660
  site.u.z = std::sin(azi) * c;
12,000✔
661

662
  return site;
24,000✔
663
}
12,000✔
664

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

© 2025 Coveralls, Inc