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

openmc-dev / openmc / 13076146263

31 Jan 2025 03:51PM UTC coverage: 84.909% (+0.05%) from 84.863%
13076146263

Pull #3288

github

web-flow
Merge e9066cabf into d9c8e594c
Pull Request #3288: Random Ray Source Region Refactor

277 of 289 new or added lines in 7 files covered. (95.85%)

2 existing lines in 1 file now uncovered.

50184 of 59103 relevant lines covered (84.91%)

34628929.3 hits per line

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

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

3
#include "openmc/cell.h"
4
#include "openmc/eigenvalue.h"
5
#include "openmc/geometry.h"
6
#include "openmc/material.h"
7
#include "openmc/message_passing.h"
8
#include "openmc/mgxs_interface.h"
9
#include "openmc/output.h"
10
#include "openmc/plot.h"
11
#include "openmc/random_ray/random_ray.h"
12
#include "openmc/simulation.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

18
#include <cstdio>
19

20
namespace openmc {
21

22
//==============================================================================
23
// FlatSourceDomain implementation
24
//==============================================================================
25

26
// Static Variable Declarations
27
RandomRayVolumeEstimator FlatSourceDomain::volume_estimator_ {
28
  RandomRayVolumeEstimator::HYBRID};
29
bool FlatSourceDomain::volume_normalized_flux_tallies_ {false};
30
bool FlatSourceDomain::adjoint_ {false};
31

32
FlatSourceDomain::FlatSourceDomain() : negroups_(data::mg.num_energy_groups_)
425✔
33
{
34
  // Count the number of source regions, compute the cell offset
35
  // indices, and store the material type The reason for the offsets is that
36
  // some cell types may not have material fills, and therefore do not
37
  // produce FSRs. Thus, we cannot index into the global arrays directly
38
  for (const auto& c : model::cells) {
3,995✔
39
    if (c->type_ != Fill::MATERIAL) {
3,570✔
40
      source_region_offsets_.push_back(-1);
1,819✔
41
    } else {
42
      source_region_offsets_.push_back(n_source_regions_);
1,751✔
43
      n_source_regions_ += c->n_instances_;
1,751✔
44
      n_source_elements_ += c->n_instances_ * negroups_;
1,751✔
45
    }
46
  }
47

48
  // Initialize cell-wise arrays
49
  bool is_linear = RandomRay::source_shape_ != RandomRaySourceShape::FLAT;
425✔
50
  source_regions_ = SourceRegionContainer(negroups_, is_linear);
425✔
51
  source_regions_.assign(n_source_regions_, SourceRegion(negroups_, is_linear));
425✔
52

53
  // Initialize materials
54
  int64_t source_region_id = 0;
425✔
55
  for (int i = 0; i < model::cells.size(); i++) {
3,995✔
56
    Cell& cell = *model::cells[i];
3,570✔
57
    if (cell.type_ == Fill::MATERIAL) {
3,570✔
58
      for (int j = 0; j < cell.n_instances_; j++) {
542,419✔
59
        source_regions_.material(source_region_id++) = cell.material(j);
540,668✔
60
      }
61
    }
62
  }
63

64
  // Sanity check
65
  if (source_region_id != n_source_regions_) {
425✔
66
    fatal_error("Unexpected number of source regions");
×
67
  }
68

69
  // Initialize tally volumes
70
  if (volume_normalized_flux_tallies_) {
425✔
71
    tally_volumes_.resize(model::tallies.size());
357✔
72
    for (int i = 0; i < model::tallies.size(); i++) {
1,292✔
73
      //  Get the shape of the 3D result tensor
74
      auto shape = model::tallies[i]->results().shape();
935✔
75

76
      // Create a new 2D tensor with the same size as the first
77
      // two dimensions of the 3D tensor
78
      tally_volumes_[i] =
935✔
79
        xt::xtensor<double, 2>::from_shape({shape[0], shape[1]});
1,870✔
80
    }
81
  }
82

83
  // Compute simulation domain volume based on ray source
84
  auto* is = dynamic_cast<IndependentSource*>(RandomRay::ray_source_.get());
425✔
85
  SpatialDistribution* space_dist = is->space();
425✔
86
  SpatialBox* sb = dynamic_cast<SpatialBox*>(space_dist);
425✔
87
  Position dims = sb->upper_right() - sb->lower_left();
425✔
88
  simulation_volume_ = dims.x * dims.y * dims.z;
425✔
89
}
425✔
90

91
void FlatSourceDomain::batch_reset()
11,730✔
92
{
93
// Reset scalar fluxes and iteration volume tallies to zero
94
#pragma omp parallel for
6,210✔
95
  for (int64_t sr = 0; sr < n_source_regions_; sr++) {
5,380,880✔
96
    source_regions_.volume(sr) = 0.0;
5,375,360✔
97
  }
98
#pragma omp parallel for
6,210✔
99
  for (int64_t se = 0; se < n_source_elements_; se++) {
10,845,200✔
100
    source_regions_.scalar_flux_new(se) = 0.0;
10,839,680✔
101
  }
102
}
11,730✔
103

104
void FlatSourceDomain::accumulate_iteration_flux()
4,590✔
105
{
106
#pragma omp parallel for
2,430✔
107
  for (int64_t se = 0; se < n_source_elements_; se++) {
3,960,400✔
108
    source_regions_.scalar_flux_final(se) +=
3,958,240✔
109
      source_regions_.scalar_flux_new(se);
3,958,240✔
110
  }
111
}
4,590✔
112

113
// Compute new estimate of scattering + fission sources in each source region
114
// based on the flux estimate from the previous iteration.
115
void FlatSourceDomain::update_neutron_source(double k_eff)
4,845✔
116
{
117
  simulation::time_update_src.start();
4,845✔
118

119
  double inverse_k_eff = 1.0 / k_eff;
4,845✔
120

121
  // Add scattering source
122
#pragma omp parallel for
2,565✔
123
  for (int64_t sr = 0; sr < n_source_regions_; sr++) {
2,108,680✔
124
    int material = source_regions_.material(sr);
2,106,400✔
125

126
    for (int g_out = 0; g_out < negroups_; g_out++) {
6,652,160✔
127
      double sigma_t = sigma_t_[material * negroups_ + g_out];
4,545,760✔
128
      double scatter_source = 0.0;
4,545,760✔
129

130
      for (int g_in = 0; g_in < negroups_; g_in++) {
26,167,040✔
131
        double scalar_flux = source_regions_.scalar_flux_old(sr, g_in);
21,621,280✔
132
        double sigma_s =
133
          sigma_s_[material * negroups_ * negroups_ + g_out * negroups_ + g_in];
21,621,280✔
134
        scatter_source += sigma_s * scalar_flux;
21,621,280✔
135
      }
136

137
      source_regions_.source(sr, g_out) = scatter_source / sigma_t;
4,545,760✔
138
    }
139
  }
140

141
  // Add fission source
142
#pragma omp parallel for
2,565✔
143
  for (int64_t sr = 0; sr < n_source_regions_; sr++) {
2,108,680✔
144
    int material = source_regions_.material(sr);
2,106,400✔
145

146
    for (int g_out = 0; g_out < negroups_; g_out++) {
6,652,160✔
147
      double sigma_t = sigma_t_[material * negroups_ + g_out];
4,545,760✔
148
      double fission_source = 0.0;
4,545,760✔
149

150
      for (int g_in = 0; g_in < negroups_; g_in++) {
26,167,040✔
151
        double scalar_flux = source_regions_.scalar_flux_old(sr, g_in);
21,621,280✔
152
        double nu_sigma_f = nu_sigma_f_[material * negroups_ + g_in];
21,621,280✔
153
        double chi = chi_[material * negroups_ + g_out];
21,621,280✔
154
        fission_source += nu_sigma_f * scalar_flux * chi;
21,621,280✔
155
      }
156
      source_regions_.source(sr, g_out) +=
4,545,760✔
157
        fission_source * inverse_k_eff / sigma_t;
4,545,760✔
158
    }
159
  }
160

161
  // Add external source if in fixed source mode
162
  if (settings::run_mode == RunMode::FIXED_SOURCE) {
4,845✔
163
#pragma omp parallel for
2,205✔
164
    for (int64_t se = 0; se < n_source_elements_; se++) {
4,096,840✔
165
      source_regions_.source(se) += source_regions_.external_source(se);
4,094,880✔
166
    }
167
  }
168

169
  simulation::time_update_src.stop();
4,845✔
170
}
4,845✔
171

172
// Normalizes flux and updates simulation-averaged volume estimate
173
void FlatSourceDomain::normalize_scalar_flux_and_volumes(
4,845✔
174
  double total_active_distance_per_iteration)
175
{
176
  double normalization_factor = 1.0 / total_active_distance_per_iteration;
4,845✔
177
  double volume_normalization_factor =
4,845✔
178
    1.0 / (total_active_distance_per_iteration * simulation::current_batch);
4,845✔
179

180
// Normalize scalar flux to total distance travelled by all rays this
181
// iteration
182
#pragma omp parallel for
2,565✔
183
  for (int64_t se = 0; se < n_source_elements_; se++) {
4,548,040✔
184
    source_regions_.scalar_flux_new(se) *= normalization_factor;
4,545,760✔
185
  }
186

187
// Accumulate cell-wise ray length tallies collected this iteration, then
188
// update the simulation-averaged cell-wise volume estimates
189
#pragma omp parallel for
2,565✔
190
  for (int64_t sr = 0; sr < n_source_regions_; sr++) {
2,108,680✔
191
    source_regions_.volume_t(sr) += source_regions_.volume(sr);
2,106,400✔
192
    source_regions_.volume_naive(sr) =
4,212,800✔
193
      source_regions_.volume(sr) * normalization_factor;
2,106,400✔
194
    source_regions_.volume(sr) =
2,106,400✔
195
      source_regions_.volume_t(sr) * volume_normalization_factor;
2,106,400✔
196
  }
197
}
4,845✔
198

199
void FlatSourceDomain::set_flux_to_flux_plus_source(
9,659,706✔
200
  int64_t sr, double volume, int g)
201
{
202
  double sigma_t = sigma_t_[source_regions_.material(sr) * negroups_ + g];
9,659,706✔
203
  source_regions_.scalar_flux_new(sr, g) /= (sigma_t * volume);
9,659,706✔
204
  source_regions_.scalar_flux_new(sr, g) += source_regions_.source(sr, g);
9,659,706✔
205
}
9,659,706✔
206

NEW
207
void FlatSourceDomain::set_flux_to_old_flux(int64_t sr, int g)
×
208
{
NEW
209
  source_regions_.scalar_flux_new(sr, g) =
×
NEW
210
    source_regions_.scalar_flux_old(sr, g);
×
211
}
212

213
void FlatSourceDomain::set_flux_to_source(int64_t sr, int g)
34✔
214
{
215
  source_regions_.scalar_flux_new(sr, g) = source_regions_.source(sr, g);
34✔
216
}
34✔
217

218
// Combine transport flux contributions and flat source contributions from the
219
// previous iteration to generate this iteration's estimate of scalar flux.
220
int64_t FlatSourceDomain::add_source_to_scalar_flux()
11,730✔
221
{
222
  int64_t n_hits = 0;
11,730✔
223

224
#pragma omp parallel for reduction(+ : n_hits)
6,210✔
225
  for (int64_t sr = 0; sr < n_source_regions_; sr++) {
5,380,880✔
226

227
    double volume_simulation_avg = source_regions_.volume(sr);
5,375,360✔
228
    double volume_iteration = source_regions_.volume_naive(sr);
5,375,360✔
229

230
    // Increment the number of hits if cell was hit this iteration
231
    if (volume_iteration) {
5,375,360✔
232
      n_hits++;
5,375,344✔
233
    }
234

235
    // The volume treatment depends on the volume estimator type
236
    // and whether or not an external source is present in the cell.
237
    double volume;
238
    switch (volume_estimator_) {
5,375,360✔
239
    case RandomRayVolumeEstimator::NAIVE:
967,680✔
240
      volume = volume_iteration;
967,680✔
241
      break;
967,680✔
242
    case RandomRayVolumeEstimator::SIMULATION_AVERAGED:
691,200✔
243
      volume = volume_simulation_avg;
691,200✔
244
      break;
691,200✔
245
    case RandomRayVolumeEstimator::HYBRID:
3,716,480✔
246
      if (source_regions_.external_source_present(sr)) {
3,716,480✔
247
        volume = volume_iteration;
14,160✔
248
      } else {
249
        volume = volume_simulation_avg;
3,702,320✔
250
      }
251
      break;
3,716,480✔
252
    default:
253
      fatal_error("Invalid volume estimator type");
254
    }
255

256
    for (int g = 0; g < negroups_; g++) {
16,215,040✔
257
      // There are three scenarios we need to consider:
258
      if (volume_iteration > 0.0) {
10,839,680✔
259
        // 1. If the FSR was hit this iteration, then the new flux is equal to
260
        // the flat source from the previous iteration plus the contributions
261
        // from rays passing through the source region (computed during the
262
        // transport sweep)
263
        set_flux_to_flux_plus_source(sr, volume, g);
10,839,664✔
264
      } else if (volume_simulation_avg > 0.0) {
16✔
265
        // 2. If the FSR was not hit this iteration, but has been hit some
266
        // previous iteration, then we need to make a choice about what
267
        // to do. Naively we will usually want to set the flux to be equal
268
        // to the reduced source. However, in fixed source problems where
269
        // there is a strong external source present in the cell, and where
270
        // the cell has a very low cross section, this approximation will
271
        // cause a huge upward bias in the flux estimate of the cell (in these
272
        // conditions, the flux estimate can be orders of magnitude too large).
273
        // Thus, to avoid this bias, if any external source is present
274
        // in the cell we will use the previous iteration's flux estimate. This
275
        // injects a small degree of correlation into the simulation, but this
276
        // is going to be trivial when the miss rate is a few percent or less.
277
        if (source_regions_.external_source_present(sr)) {
16✔
278
          set_flux_to_old_flux(sr, g);
279
        } else {
280
          set_flux_to_source(sr, g);
16✔
281
        }
282
      }
283
      // If the FSR was not hit this iteration, and it has never been hit in
284
      // any iteration (i.e., volume is zero), then we want to set this to 0
285
      // to avoid dividing anything by a zero volume. This happens implicitly
286
      // given that the new scalar flux arrays are set to zero each iteration.
287
    }
288
  }
289

290
  // Return the number of source regions that were hit this iteration
291
  return n_hits;
11,730✔
292
}
293

294
// Generates new estimate of k_eff based on the differences between this
295
// iteration's estimate of the scalar flux and the last iteration's estimate.
296
double FlatSourceDomain::compute_k_eff(double k_eff_old) const
2,040✔
297
{
298
  double fission_rate_old = 0;
2,040✔
299
  double fission_rate_new = 0;
2,040✔
300

301
  // Vector for gathering fission source terms for Shannon entropy calculation
302
  vector<float> p(n_source_regions_, 0.0f);
2,040✔
303

304
#pragma omp parallel for reduction(+ : fission_rate_old, fission_rate_new)
1,080✔
305
  for (int64_t sr = 0; sr < n_source_regions_; sr++) {
256,640✔
306

307
    // If simulation averaged volume is zero, don't include this cell
308
    double volume = source_regions_.volume(sr);
255,680✔
309
    if (volume == 0.0) {
255,680✔
310
      continue;
311
    }
312

313
    int material = source_regions_.material(sr);
255,680✔
314

315
    double sr_fission_source_old = 0;
255,680✔
316
    double sr_fission_source_new = 0;
255,680✔
317

318
    for (int g = 0; g < negroups_; g++) {
1,799,680✔
319
      double nu_sigma_f = nu_sigma_f_[material * negroups_ + g];
1,544,000✔
320
      sr_fission_source_old +=
1,544,000✔
321
        nu_sigma_f * source_regions_.scalar_flux_old(sr, g);
1,544,000✔
322
      sr_fission_source_new +=
1,544,000✔
323
        nu_sigma_f * source_regions_.scalar_flux_new(sr, g);
1,544,000✔
324
    }
325

326
    // Compute total fission rates in FSR
327
    sr_fission_source_old *= volume;
255,680✔
328
    sr_fission_source_new *= volume;
255,680✔
329

330
    // Accumulate totals
331
    fission_rate_old += sr_fission_source_old;
255,680✔
332
    fission_rate_new += sr_fission_source_new;
255,680✔
333

334
    // Store total fission rate in the FSR for Shannon calculation
335
    p[sr] = sr_fission_source_new;
255,680✔
336
  }
337

338
  double k_eff_new = k_eff_old * (fission_rate_new / fission_rate_old);
2,040✔
339

340
  double H = 0.0;
2,040✔
341
  // defining an inverse sum for better performance
342
  double inverse_sum = 1 / fission_rate_new;
2,040✔
343

344
#pragma omp parallel for reduction(+ : H)
1,080✔
345
  for (int64_t sr = 0; sr < n_source_regions_; sr++) {
256,640✔
346
    // Only if FSR has non-negative and non-zero fission source
347
    if (p[sr] > 0.0f) {
255,680✔
348
      // Normalize to total weight of bank sites. p_i for better performance
349
      float p_i = p[sr] * inverse_sum;
104,320✔
350
      // Sum values to obtain Shannon entropy.
351
      H -= p_i * std::log2(p_i);
104,320✔
352
    }
353
  }
354

355
  // Adds entropy value to shared entropy vector in openmc namespace.
356
  simulation::entropy.push_back(H);
2,040✔
357

358
  return k_eff_new;
2,040✔
359
}
2,040✔
360

361
// This function is responsible for generating a mapping between random
362
// ray flat source regions (cell instances) and tally bins. The mapping
363
// takes the form of a "TallyTask" object, which accounts for one single
364
// score being applied to a single tally. Thus, a single source region
365
// may have anywhere from zero to many tally tasks associated with it ---
366
// meaning that the global "tally_task" data structure is in 2D. The outer
367
// dimension corresponds to the source element (i.e., each entry corresponds
368
// to a specific energy group within a specific source region), and the
369
// inner dimension corresponds to the tallying task itself. Mechanically,
370
// the mapping between FSRs and spatial filters is done by considering
371
// the location of a single known ray midpoint that passed through the
372
// FSR. I.e., during transport, the first ray to pass through a given FSR
373
// will write down its midpoint for use with this function. This is a cheap
374
// and easy way of mapping FSRs to spatial tally filters, but comes with
375
// the downside of adding the restriction that spatial tally filters must
376
// share boundaries with the physical geometry of the simulation (so as
377
// not to subdivide any FSR). It is acceptable for a spatial tally region
378
// to contain multiple FSRs, but not the other way around.
379

380
// TODO: In future work, it would be preferable to offer a more general
381
// (but perhaps slightly more expensive) option for handling arbitrary
382
// spatial tallies that would be allowed to subdivide FSRs.
383

384
// Besides generating the mapping structure, this function also keeps track
385
// of whether or not all flat source regions have been hit yet. This is
386
// required, as there is no guarantee that all flat source regions will
387
// be hit every iteration, such that in the first few iterations some FSRs
388
// may not have a known position within them yet to facilitate mapping to
389
// spatial tally filters. However, after several iterations, if all FSRs
390
// have been hit and have had a tally map generated, then this status will
391
// be passed back to the caller to alert them that this function doesn't
392
// need to be called for the remainder of the simulation.
393

394
void FlatSourceDomain::convert_source_regions_to_tallies()
300✔
395
{
396
  openmc::simulation::time_tallies.start();
300✔
397

398
  // Tracks if we've generated a mapping yet for all source regions.
399
  bool all_source_regions_mapped = true;
300✔
400

401
// Attempt to generate mapping for all source regions
402
#pragma omp parallel for
150✔
403
  for (int64_t sr = 0; sr < n_source_regions_; sr++) {
190,974✔
404

405
    // If this source region has not been hit by a ray yet, then
406
    // we aren't going to be able to map it, so skip it.
407
    if (!source_regions_.position_recorded(sr)) {
190,824✔
408
      all_source_regions_mapped = false;
409
      continue;
410
    }
411

412
    // A particle located at the recorded midpoint of a ray
413
    // crossing through this source region is used to estabilish
414
    // the spatial location of the source region
415
    Particle p;
190,824✔
416
    p.r() = source_regions_.position(sr);
190,824✔
417
    p.r_last() = source_regions_.position(sr);
190,824✔
418
    bool found = exhaustive_find_cell(p);
190,824✔
419

420
    // Loop over energy groups (so as to support energy filters)
421
    for (int g = 0; g < negroups_; g++) {
450,624✔
422

423
      // Set particle to the current energy
424
      p.g() = g;
259,800✔
425
      p.g_last() = g;
259,800✔
426
      p.E() = data::mg.energy_bin_avg_[p.g()];
259,800✔
427
      p.E_last() = p.E();
259,800✔
428

429
      int64_t source_element = sr * negroups_ + g;
259,800✔
430

431
      // If this task has already been populated, we don't need to do
432
      // it again.
433
      if (source_regions_.tally_task(sr, g).size() > 0) {
259,800✔
434
        continue;
435
      }
436

437
      // Loop over all active tallies. This logic is essentially identical
438
      // to what happens when scanning for applicable tallies during
439
      // MC transport.
440
      for (auto i_tally : model::active_tallies) {
889,776✔
441
        Tally& tally {*model::tallies[i_tally]};
629,976✔
442

443
        // Initialize an iterator over valid filter bin combinations.
444
        // If there are no valid combinations, use a continue statement
445
        // to ensure we skip the assume_separate break below.
446
        auto filter_iter = FilterBinIter(tally, p);
629,976✔
447
        auto end = FilterBinIter(tally, true, &p.filter_matches());
629,976✔
448
        if (filter_iter == end)
629,976✔
449
          continue;
352,512✔
450

451
        // Loop over filter bins.
452
        for (; filter_iter != end; ++filter_iter) {
554,928✔
453
          auto filter_index = filter_iter.index_;
277,464✔
454
          auto filter_weight = filter_iter.weight_;
277,464✔
455

456
          // Loop over scores
457
          for (auto score_index = 0; score_index < tally.scores_.size();
715,872✔
458
               score_index++) {
459
            auto score_bin = tally.scores_[score_index];
438,408✔
460
            // If a valid tally, filter, and score combination has been found,
461
            // then add it to the list of tally tasks for this source element.
462
            TallyTask task(i_tally, filter_index, score_index, score_bin);
438,408✔
463
            source_regions_.tally_task(sr, g).push_back(task);
438,408✔
464

465
            // Also add this task to the list of volume tasks for this source
466
            // region.
467
            source_regions_.volume_task(sr).insert(task);
438,408✔
468
          }
469
        }
470
      }
471
      // Reset all the filter matches for the next tally event.
472
      for (auto& match : p.filter_matches())
1,011,720✔
473
        match.bins_present_ = false;
751,920✔
474
    }
475
  }
190,824✔
476
  openmc::simulation::time_tallies.stop();
300✔
477

478
  mapped_all_tallies_ = all_source_regions_mapped;
300✔
479
}
300✔
480

481
// Set the volume accumulators to zero for all tallies
482
void FlatSourceDomain::reset_tally_volumes()
3,240✔
483
{
484
  if (volume_normalized_flux_tallies_) {
3,240✔
485
#pragma omp parallel for
1,260✔
486
    for (int i = 0; i < tally_volumes_.size(); i++) {
4,440✔
487
      auto& tensor = tally_volumes_[i];
3,180✔
488
      tensor.fill(0.0); // Set all elements of the tensor to 0.0
3,180✔
489
    }
490
  }
491
}
3,240✔
492

493
// In fixed source mode, due to the way that volumetric fixed sources are
494
// converted and applied as volumetric sources in one or more source regions,
495
// we need to perform an additional normalization step to ensure that the
496
// reported scalar fluxes are in units per source neutron. This allows for
497
// direct comparison of reported tallies to Monte Carlo flux results.
498
// This factor needs to be computed at each iteration, as it is based on the
499
// volume estimate of each FSR, which improves over the course of the
500
// simulation
501
double FlatSourceDomain::compute_fixed_source_normalization_factor() const
3,614✔
502
{
503
  // If we are not in fixed source mode, then there are no external sources
504
  // so no normalization is needed.
505
  if (settings::run_mode != RunMode::FIXED_SOURCE || adjoint_) {
3,614✔
506
    return 1.0;
925✔
507
  }
508

509
  // Step 1 is to sum over all source regions and energy groups to get the
510
  // total external source strength in the simulation.
511
  double simulation_external_source_strength = 0.0;
2,689✔
512
#pragma omp parallel for reduction(+ : simulation_external_source_strength)
1,353✔
513
  for (int64_t sr = 0; sr < n_source_regions_; sr++) {
1,873,864✔
514
    int material = source_regions_.material(sr);
1,872,528✔
515
    double volume = source_regions_.volume(sr) * simulation_volume_;
1,872,528✔
516
    for (int g = 0; g < negroups_; g++) {
4,404,864✔
517
      double sigma_t = sigma_t_[material * negroups_ + g];
2,532,336✔
518
      simulation_external_source_strength +=
2,532,336✔
519
        source_regions_.external_source(sr, g) * sigma_t * volume;
2,532,336✔
520
    }
521
  }
522

523
  // Step 2 is to determine the total user-specified external source strength
524
  double user_external_source_strength = 0.0;
2,689✔
525
  for (auto& ext_source : model::external_sources) {
5,378✔
526
    user_external_source_strength += ext_source->strength();
2,689✔
527
  }
528

529
  // The correction factor is the ratio of the user-specified external source
530
  // strength to the simulation external source strength.
531
  double source_normalization_factor =
2,689✔
532
    user_external_source_strength / simulation_external_source_strength;
533

534
  return source_normalization_factor;
2,689✔
535
}
536

537
// Tallying in random ray is not done directly during transport, rather,
538
// it is done only once after each power iteration. This is made possible
539
// by way of a mapping data structure that relates spatial source regions
540
// (FSRs) to tally/filter/score combinations. The mechanism by which the
541
// mapping is done (and the limitations incurred) is documented in the
542
// "convert_source_regions_to_tallies()" function comments above. The present
543
// tally function simply traverses the mapping data structure and executes
544
// the scoring operations to OpenMC's native tally result arrays.
545

546
void FlatSourceDomain::random_ray_tally()
3,240✔
547
{
548
  openmc::simulation::time_tallies.start();
3,240✔
549

550
  // Reset our tally volumes to zero
551
  reset_tally_volumes();
3,240✔
552

553
  double source_normalization_factor =
554
    compute_fixed_source_normalization_factor();
3,240✔
555

556
// We loop over all source regions and energy groups. For each
557
// element, we check if there are any scores needed and apply
558
// them.
559
#pragma omp parallel for
1,620✔
560
  for (int64_t sr = 0; sr < n_source_regions_; sr++) {
1,860,780✔
561
    // The fsr.volume_ is the unitless fractional simulation averaged volume
562
    // (i.e., it is the FSR's fraction of the overall simulation volume). The
563
    // simulation_volume_ is the total 3D physical volume in cm^3 of the
564
    // entire global simulation domain (as defined by the ray source box).
565
    // Thus, the FSR's true 3D spatial volume in cm^3 is found by multiplying
566
    // its fraction of the total volume by the total volume. Not important in
567
    // eigenvalue solves, but useful in fixed source solves for returning the
568
    // flux shape with a magnitude that makes sense relative to the fixed
569
    // source strength.
570
    double volume = source_regions_.volume(sr) * simulation_volume_;
1,859,160✔
571

572
    double material = source_regions_.material(sr);
1,859,160✔
573
    for (int g = 0; g < negroups_; g++) {
4,827,840✔
574
      double flux =
575
        source_regions_.scalar_flux_new(sr, g) * source_normalization_factor;
2,968,680✔
576

577
      // Determine numerical score value
578
      for (auto& task : source_regions_.tally_task(sr, g)) {
8,614,560✔
579
        double score;
580
        switch (task.score_type) {
5,645,880✔
581

582
        case SCORE_FLUX:
3,057,000✔
583
          score = flux * volume;
3,057,000✔
584
          break;
3,057,000✔
585

586
        case SCORE_TOTAL:
587
          score = flux * volume * sigma_t_[material * negroups_ + g];
588
          break;
589

590
        case SCORE_FISSION:
1,294,440✔
591
          score = flux * volume * sigma_f_[material * negroups_ + g];
1,294,440✔
592
          break;
1,294,440✔
593

594
        case SCORE_NU_FISSION:
1,294,440✔
595
          score = flux * volume * nu_sigma_f_[material * negroups_ + g];
1,294,440✔
596
          break;
1,294,440✔
597

598
        case SCORE_EVENTS:
599
          score = 1.0;
600
          break;
601

602
        default:
603
          fatal_error("Invalid score specified in tallies.xml. Only flux, "
604
                      "total, fission, nu-fission, and events are supported in "
605
                      "random ray mode.");
606
          break;
607
        }
608

609
        // Apply score to the appropriate tally bin
610
        Tally& tally {*model::tallies[task.tally_idx]};
5,645,880✔
611
#pragma omp atomic
612
        tally.results_(task.filter_idx, task.score_idx, TallyResult::VALUE) +=
5,645,880✔
613
          score;
614
      }
615
    }
616

617
    // For flux tallies, the total volume of the spatial region is needed
618
    // for normalizing the flux. We store this volume in a separate tensor.
619
    // We only contribute to each volume tally bin once per FSR.
620
    if (volume_normalized_flux_tallies_) {
1,859,160✔
621
      for (const auto& task : source_regions_.volume_task(sr)) {
5,089,200✔
622
        if (task.score_type == SCORE_FLUX) {
3,401,640✔
623
#pragma omp atomic
624
          tally_volumes_[task.tally_idx](task.filter_idx, task.score_idx) +=
2,274,360✔
625
            volume;
626
        }
627
      }
628
    }
629
  } // end FSR loop
630

631
  // Normalize any flux scores by the total volume of the FSRs scoring to that
632
  // bin. To do this, we loop over all tallies, and then all filter bins,
633
  // and then scores. For each score, we check the tally data structure to
634
  // see what index that score corresponds to. If that score is a flux score,
635
  // then we divide it by volume.
636
  if (volume_normalized_flux_tallies_) {
3,240✔
637
    for (int i = 0; i < model::tallies.size(); i++) {
8,880✔
638
      Tally& tally {*model::tallies[i]};
6,360✔
639
#pragma omp parallel for
3,180✔
640
      for (int bin = 0; bin < tally.n_filter_bins(); bin++) {
28,170✔
641
        for (int score_idx = 0; score_idx < tally.n_scores(); score_idx++) {
68,460✔
642
          auto score_type = tally.scores_[score_idx];
43,470✔
643
          if (score_type == SCORE_FLUX) {
43,470✔
644
            double vol = tally_volumes_[i](bin, score_idx);
24,990✔
645
            if (vol > 0.0) {
24,990✔
646
              tally.results_(bin, score_idx, TallyResult::VALUE) /= vol;
24,990✔
647
            }
648
          }
649
        }
650
      }
651
    }
652
  }
653

654
  openmc::simulation::time_tallies.stop();
3,240✔
655
}
3,240✔
656

657
void FlatSourceDomain::all_reduce_replicated_source_regions()
11,730✔
658
{
659
#ifdef OPENMC_MPI
660
  // If we only have 1 MPI rank, no need
661
  // to reduce anything.
662
  if (mpi::n_procs <= 1)
6,900✔
663
    return;
664

665
  simulation::time_bank_sendrecv.start();
6,900✔
666

667
  // First, we broadcast the fully mapped tally status variable so that
668
  // all ranks are on the same page
669
  int mapped_all_tallies_i = static_cast<int>(mapped_all_tallies_);
6,900✔
670
  MPI_Bcast(&mapped_all_tallies_i, 1, MPI_INT, 0, mpi::intracomm);
6,900✔
671

672
  bool reduce_position =
6,900✔
673
    simulation::current_batch > settings::n_inactive && !mapped_all_tallies_i;
6,900✔
674

675
  source_regions_.mpi_sync_ranks(reduce_position);
6,900✔
676

677
  simulation::time_bank_sendrecv.stop();
6,900✔
678
#endif
679
}
4,830✔
680

681
double FlatSourceDomain::evaluate_flux_at_point(
×
682
  Position r, int64_t sr, int g) const
683
{
NEW
684
  return source_regions_.scalar_flux_final(sr, g) /
×
685
         (settings::n_batches - settings::n_inactive);
×
686
}
687

688
// Outputs all basic material, FSR ID, multigroup flux, and
689
// fission source data to .vtk file that can be directly
690
// loaded and displayed by Paraview. Note that .vtk binary
691
// files require big endian byte ordering, so endianness
692
// is checked and flipped if necessary.
693
void FlatSourceDomain::output_to_vtk() const
×
694
{
695
  // Rename .h5 plot filename(s) to .vtk filenames
696
  for (int p = 0; p < model::plots.size(); p++) {
×
697
    PlottableInterface* plot = model::plots[p].get();
×
698
    plot->path_plot() =
×
699
      plot->path_plot().substr(0, plot->path_plot().find_last_of('.')) + ".vtk";
×
700
  }
701

702
  // Print header information
703
  print_plot();
×
704

705
  // Outer loop over plots
706
  for (int p = 0; p < model::plots.size(); p++) {
×
707

708
    // Get handle to OpenMC plot object and extract params
709
    Plot* openmc_plot = dynamic_cast<Plot*>(model::plots[p].get());
×
710

711
    // Random ray plots only support voxel plots
712
    if (!openmc_plot) {
×
713
      warning(fmt::format("Plot {} is invalid plot type -- only voxel plotting "
×
714
                          "is allowed in random ray mode.",
715
        p));
716
      continue;
×
717
    } else if (openmc_plot->type_ != Plot::PlotType::voxel) {
×
718
      warning(fmt::format("Plot {} is invalid plot type -- only voxel plotting "
×
719
                          "is allowed in random ray mode.",
720
        p));
721
      continue;
×
722
    }
723

724
    int Nx = openmc_plot->pixels_[0];
×
725
    int Ny = openmc_plot->pixels_[1];
×
726
    int Nz = openmc_plot->pixels_[2];
×
727
    Position origin = openmc_plot->origin_;
×
728
    Position width = openmc_plot->width_;
×
729
    Position ll = origin - width / 2.0;
×
730
    double x_delta = width.x / Nx;
×
731
    double y_delta = width.y / Ny;
×
732
    double z_delta = width.z / Nz;
×
733
    std::string filename = openmc_plot->path_plot();
×
734

735
    // Perform sanity checks on file size
736
    uint64_t bytes = Nx * Ny * Nz * (negroups_ + 1 + 1 + 1) * sizeof(float);
×
737
    write_message(5, "Processing plot {}: {}... (Estimated size is {} MB)",
×
738
      openmc_plot->id(), filename, bytes / 1.0e6);
×
739
    if (bytes / 1.0e9 > 1.0) {
×
740
      warning("Voxel plot specification is very large (>1 GB). Plotting may be "
×
741
              "slow.");
742
    } else if (bytes / 1.0e9 > 100.0) {
×
743
      fatal_error("Voxel plot specification is too large (>100 GB). Exiting.");
×
744
    }
745

746
    // Relate voxel spatial locations to random ray source regions
747
    vector<int> voxel_indices(Nx * Ny * Nz);
×
748
    vector<Position> voxel_positions(Nx * Ny * Nz);
×
749

750
#pragma omp parallel for collapse(3)
751
    for (int z = 0; z < Nz; z++) {
752
      for (int y = 0; y < Ny; y++) {
753
        for (int x = 0; x < Nx; x++) {
754
          Position sample;
755
          sample.z = ll.z + z_delta / 2.0 + z * z_delta;
756
          sample.y = ll.y + y_delta / 2.0 + y * y_delta;
757
          sample.x = ll.x + x_delta / 2.0 + x * x_delta;
758
          Particle p;
759
          p.r() = sample;
760
          bool found = exhaustive_find_cell(p);
761
          int i_cell = p.lowest_coord().cell;
762
          int64_t source_region_idx =
763
            source_region_offsets_[i_cell] + p.cell_instance();
764
          voxel_indices[z * Ny * Nx + y * Nx + x] = source_region_idx;
765
          voxel_positions[z * Ny * Nx + y * Nx + x] = sample;
766
        }
767
      }
768
    }
769

770
    double source_normalization_factor =
771
      compute_fixed_source_normalization_factor();
×
772

773
    // Open file for writing
774
    std::FILE* plot = std::fopen(filename.c_str(), "wb");
×
775

776
    // Write vtk metadata
777
    std::fprintf(plot, "# vtk DataFile Version 2.0\n");
×
778
    std::fprintf(plot, "Dataset File\n");
×
779
    std::fprintf(plot, "BINARY\n");
×
780
    std::fprintf(plot, "DATASET STRUCTURED_POINTS\n");
×
781
    std::fprintf(plot, "DIMENSIONS %d %d %d\n", Nx, Ny, Nz);
×
782
    std::fprintf(plot, "ORIGIN 0 0 0\n");
×
783
    std::fprintf(plot, "SPACING %lf %lf %lf\n", x_delta, y_delta, z_delta);
×
784
    std::fprintf(plot, "POINT_DATA %d\n", Nx * Ny * Nz);
×
785

786
    // Plot multigroup flux data
787
    for (int g = 0; g < negroups_; g++) {
×
788
      std::fprintf(plot, "SCALARS flux_group_%d float\n", g);
×
789
      std::fprintf(plot, "LOOKUP_TABLE default\n");
×
790
      for (int i = 0; i < Nx * Ny * Nz; i++) {
×
791
        int64_t fsr = voxel_indices[i];
×
792
        int64_t source_element = fsr * negroups_ + g;
×
793
        float flux = evaluate_flux_at_point(voxel_positions[i], fsr, g);
×
794
        flux = convert_to_big_endian<float>(flux);
×
795
        std::fwrite(&flux, sizeof(float), 1, plot);
×
796
      }
797
    }
798

799
    // Plot FSRs
800
    std::fprintf(plot, "SCALARS FSRs float\n");
×
801
    std::fprintf(plot, "LOOKUP_TABLE default\n");
×
802
    for (int fsr : voxel_indices) {
×
803
      float value = future_prn(10, fsr);
×
804
      value = convert_to_big_endian<float>(value);
×
805
      std::fwrite(&value, sizeof(float), 1, plot);
×
806
    }
807

808
    // Plot Materials
809
    std::fprintf(plot, "SCALARS Materials int\n");
×
810
    std::fprintf(plot, "LOOKUP_TABLE default\n");
×
811
    for (int fsr : voxel_indices) {
×
NEW
812
      int mat = source_regions_.material(fsr);
×
813
      mat = convert_to_big_endian<int>(mat);
×
814
      std::fwrite(&mat, sizeof(int), 1, plot);
×
815
    }
816

817
    // Plot fission source
818
    std::fprintf(plot, "SCALARS total_fission_source float\n");
×
819
    std::fprintf(plot, "LOOKUP_TABLE default\n");
×
820
    for (int i = 0; i < Nx * Ny * Nz; i++) {
×
821
      int64_t fsr = voxel_indices[i];
×
822

823
      float total_fission = 0.0;
×
NEW
824
      int mat = source_regions_.material(fsr);
×
825
      for (int g = 0; g < negroups_; g++) {
×
826
        int64_t source_element = fsr * negroups_ + g;
×
827
        float flux = evaluate_flux_at_point(voxel_positions[i], fsr, g);
×
828
        double sigma_f = sigma_f_[mat * negroups_ + g];
×
829
        total_fission += sigma_f * flux;
×
830
      }
831
      total_fission = convert_to_big_endian<float>(total_fission);
×
832
      std::fwrite(&total_fission, sizeof(float), 1, plot);
×
833
    }
834

835
    std::fclose(plot);
×
836
  }
837
}
838

839
void FlatSourceDomain::apply_external_source_to_source_region(
2,074✔
840
  Discrete* discrete, double strength_factor, int64_t sr)
841
{
842
  source_regions_.external_source_present(sr) = 1;
2,074✔
843

844
  const auto& discrete_energies = discrete->x();
2,074✔
845
  const auto& discrete_probs = discrete->prob();
2,074✔
846

847
  for (int i = 0; i < discrete_energies.size(); i++) {
4,352✔
848
    int g = data::mg.get_group_index(discrete_energies[i]);
2,278✔
849
    source_regions_.external_source(sr, g) +=
2,278✔
850
      discrete_probs[i] * strength_factor;
2,278✔
851
  }
852
}
2,074✔
853

854
void FlatSourceDomain::apply_external_source_to_cell_instances(int32_t i_cell,
323✔
855
  Discrete* discrete, double strength_factor, int target_material_id,
856
  const vector<int32_t>& instances)
857
{
858
  Cell& cell = *model::cells[i_cell];
323✔
859

860
  if (cell.type_ != Fill::MATERIAL)
323✔
861
    return;
×
862

863
  for (int j : instances) {
31,637✔
864
    int cell_material_idx = cell.material(j);
31,314✔
865
    int cell_material_id = model::materials[cell_material_idx]->id();
31,314✔
866
    if (target_material_id == C_NONE ||
31,314✔
867
        cell_material_id == target_material_id) {
868
      int64_t source_region = source_region_offsets_[i_cell] + j;
2,074✔
869
      apply_external_source_to_source_region(
2,074✔
870
        discrete, strength_factor, source_region);
871
    }
872
  }
873
}
874

875
void FlatSourceDomain::apply_external_source_to_cell_and_children(
357✔
876
  int32_t i_cell, Discrete* discrete, double strength_factor,
877
  int32_t target_material_id)
878
{
879
  Cell& cell = *model::cells[i_cell];
357✔
880

881
  if (cell.type_ == Fill::MATERIAL) {
357✔
882
    vector<int> instances(cell.n_instances_);
323✔
883
    std::iota(instances.begin(), instances.end(), 0);
323✔
884
    apply_external_source_to_cell_instances(
323✔
885
      i_cell, discrete, strength_factor, target_material_id, instances);
886
  } else if (target_material_id == C_NONE) {
357✔
887
    std::unordered_map<int32_t, vector<int32_t>> cell_instance_list =
888
      cell.get_contained_cells(0, nullptr);
×
889
    for (const auto& pair : cell_instance_list) {
×
890
      int32_t i_child_cell = pair.first;
×
891
      apply_external_source_to_cell_instances(i_child_cell, discrete,
×
892
        strength_factor, target_material_id, pair.second);
×
893
    }
894
  }
895
}
357✔
896

897
void FlatSourceDomain::count_external_source_regions()
289✔
898
{
899
  n_external_source_regions_ = 0;
289✔
900
#pragma omp parallel for reduction(+ : n_external_source_regions_)
153✔
901
  for (int64_t sr = 0; sr < n_source_regions_; sr++) {
213,064✔
902
    if (source_regions_.external_source_present(sr)) {
212,928✔
903
      n_external_source_regions_++;
976✔
904
    }
905
  }
906
}
289✔
907

908
void FlatSourceDomain::convert_external_sources()
289✔
909
{
910
  // Loop over external sources
911
  for (int es = 0; es < model::external_sources.size(); es++) {
578✔
912
    Source* s = model::external_sources[es].get();
289✔
913
    IndependentSource* is = dynamic_cast<IndependentSource*>(s);
289✔
914
    Discrete* energy = dynamic_cast<Discrete*>(is->energy());
289✔
915
    const std::unordered_set<int32_t>& domain_ids = is->domain_ids();
289✔
916

917
    double strength_factor = is->strength();
289✔
918

919
    if (is->domain_type() == Source::DomainType::MATERIAL) {
289✔
920
      for (int32_t material_id : domain_ids) {
34✔
921
        for (int i_cell = 0; i_cell < model::cells.size(); i_cell++) {
102✔
922
          apply_external_source_to_cell_and_children(
85✔
923
            i_cell, energy, strength_factor, material_id);
924
        }
925
      }
926
    } else if (is->domain_type() == Source::DomainType::CELL) {
272✔
927
      for (int32_t cell_id : domain_ids) {
34✔
928
        int32_t i_cell = model::cell_map[cell_id];
17✔
929
        apply_external_source_to_cell_and_children(
17✔
930
          i_cell, energy, strength_factor, C_NONE);
931
      }
932
    } else if (is->domain_type() == Source::DomainType::UNIVERSE) {
255✔
933
      for (int32_t universe_id : domain_ids) {
510✔
934
        int32_t i_universe = model::universe_map[universe_id];
255✔
935
        Universe& universe = *model::universes[i_universe];
255✔
936
        for (int32_t i_cell : universe.cells_) {
510✔
937
          apply_external_source_to_cell_and_children(
255✔
938
            i_cell, energy, strength_factor, C_NONE);
939
        }
940
      }
941
    }
942
  } // End loop over external sources
943

944
// Divide the fixed source term by sigma t (to save time when applying each
945
// iteration)
946
#pragma omp parallel for
153✔
947
  for (int64_t sr = 0; sr < n_source_regions_; sr++) {
213,064✔
948
    for (int g = 0; g < negroups_; g++) {
459,264✔
949
      double sigma_t = sigma_t_[source_regions_.material(sr) * negroups_ + g];
246,336✔
950
      source_regions_.external_source(sr, g) /= sigma_t;
246,336✔
951
    }
952
  }
953
}
289✔
954

955
void FlatSourceDomain::flux_swap()
11,730✔
956
{
957
  source_regions_.flux_swap();
11,730✔
958
}
11,730✔
959

960
void FlatSourceDomain::flatten_xs()
425✔
961
{
962
  // Temperature and angle indices, if using multiple temperature
963
  // data sets and/or anisotropic data sets.
964
  // TODO: Currently assumes we are only using single temp/single angle data.
965
  const int t = 0;
425✔
966
  const int a = 0;
425✔
967

968
  n_materials_ = data::mg.macro_xs_.size();
425✔
969
  for (auto& m : data::mg.macro_xs_) {
1,547✔
970
    for (int g_out = 0; g_out < negroups_; g_out++) {
3,672✔
971
      if (m.exists_in_model) {
2,550✔
972
        double sigma_t =
973
          m.get_xs(MgxsType::TOTAL, g_out, NULL, NULL, NULL, t, a);
2,550✔
974
        sigma_t_.push_back(sigma_t);
2,550✔
975

976
        double nu_Sigma_f =
977
          m.get_xs(MgxsType::NU_FISSION, g_out, NULL, NULL, NULL, t, a);
2,550✔
978
        nu_sigma_f_.push_back(nu_Sigma_f);
2,550✔
979

980
        double sigma_f =
981
          m.get_xs(MgxsType::FISSION, g_out, NULL, NULL, NULL, t, a);
2,550✔
982
        sigma_f_.push_back(sigma_f);
2,550✔
983

984
        double chi =
985
          m.get_xs(MgxsType::CHI_PROMPT, g_out, &g_out, NULL, NULL, t, a);
2,550✔
986
        chi_.push_back(chi);
2,550✔
987

988
        for (int g_in = 0; g_in < negroups_; g_in++) {
15,096✔
989
          double sigma_s =
990
            m.get_xs(MgxsType::NU_SCATTER, g_in, &g_out, NULL, NULL, t, a);
12,546✔
991
          sigma_s_.push_back(sigma_s);
12,546✔
992
        }
993
      } else {
994
        sigma_t_.push_back(0);
×
995
        nu_sigma_f_.push_back(0);
×
996
        sigma_f_.push_back(0);
×
997
        chi_.push_back(0);
×
998
        for (int g_in = 0; g_in < negroups_; g_in++) {
×
999
          sigma_s_.push_back(0);
×
1000
        }
1001
      }
1002
    }
1003
  }
1004
}
425✔
1005

1006
void FlatSourceDomain::set_adjoint_sources(const vector<double>& forward_flux)
34✔
1007
{
1008
  // Set the external source to 1/forward_flux
1009
  // The forward flux is given in terms of total for the forward simulation
1010
  // so we must convert it to a "per batch" quantity
1011
#pragma omp parallel for
18✔
1012
  for (int64_t se = 0; se < n_source_elements_; se++) {
27,664✔
1013
    source_regions_.external_source(se) = 1.0 / forward_flux[se];
27,648✔
1014
  }
1015

1016
  // Divide the fixed source term by sigma t (to save time when applying each
1017
  // iteration)
1018
#pragma omp parallel for
18✔
1019
  for (int64_t sr = 0; sr < n_source_regions_; sr++) {
27,664✔
1020
    for (int g = 0; g < negroups_; g++) {
55,296✔
1021
      double sigma_t = sigma_t_[source_regions_.material(sr) * negroups_ + g];
27,648✔
1022
      source_regions_.external_source(sr, g) /= sigma_t;
27,648✔
1023
    }
1024
  }
1025
}
34✔
1026

1027
void FlatSourceDomain::transpose_scattering_matrix()
51✔
1028
{
1029
  // Transpose the inner two dimensions for each material
1030
  for (int m = 0; m < n_materials_; ++m) {
187✔
1031
    int material_offset = m * negroups_ * negroups_;
136✔
1032
    for (int i = 0; i < negroups_; ++i) {
476✔
1033
      for (int j = i + 1; j < negroups_; ++j) {
1,054✔
1034
        // Calculate indices of the elements to swap
1035
        int idx1 = material_offset + i * negroups_ + j;
714✔
1036
        int idx2 = material_offset + j * negroups_ + i;
714✔
1037

1038
        // Swap the elements to transpose the matrix
1039
        std::swap(sigma_s_[idx1], sigma_s_[idx2]);
714✔
1040
      }
1041
    }
1042
  }
1043
}
51✔
1044

1045
void FlatSourceDomain::serialize_final_fluxes(vector<double>& flux)
374✔
1046
{
1047
  // Ensure array is correct size
1048
  flux.resize(n_source_regions_ * negroups_);
374✔
1049
// Serialize the final fluxes for output
1050
#pragma omp parallel for
198✔
1051
  for (int64_t se = 0; se < n_source_elements_; se++) {
305,264✔
1052
    flux[se] = source_regions_.scalar_flux_final(se);
305,088✔
1053
  }
1054
}
374✔
1055

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