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

openmc-dev / openmc / 13591584831

28 Feb 2025 03:46PM UTC coverage: 85.051% (+0.3%) from 84.722%
13591584831

Pull #3067

github

web-flow
Merge 08055e996 into c26fde666
Pull Request #3067: Implement user-configurable random number stride

36 of 44 new or added lines in 8 files covered. (81.82%)

3588 existing lines in 111 files now uncovered.

51062 of 60037 relevant lines covered (85.05%)

32650986.73 hits per line

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

79.92
/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_)
476✔
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) {
4,505✔
39
    if (c->type_ != Fill::MATERIAL) {
4,029✔
40
      source_region_offsets_.push_back(-1);
2,057✔
41
    } else {
42
      source_region_offsets_.push_back(n_source_regions_);
1,972✔
43
      n_source_regions_ += c->n_instances_;
1,972✔
44
      n_source_elements_ += c->n_instances_ * negroups_;
1,972✔
45
    }
46
  }
47

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

53
  // Initialize materials
54
  int64_t source_region_id = 0;
476✔
55
  for (int i = 0; i < model::cells.size(); i++) {
4,505✔
56
    Cell& cell = *model::cells[i];
4,029✔
57
    if (cell.type_ == Fill::MATERIAL) {
4,029✔
58
      for (int j = 0; j < cell.n_instances_; j++) {
605,540✔
59
        source_regions_.material(source_region_id++) = cell.material(j);
603,568✔
60
      }
61
    }
62
  }
63

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

69
  // Initialize tally volumes
70
  if (volume_normalized_flux_tallies_) {
476✔
71
    tally_volumes_.resize(model::tallies.size());
408✔
72
    for (int i = 0; i < model::tallies.size(); i++) {
1,462✔
73
      //  Get the shape of the 3D result tensor
74
      auto shape = model::tallies[i]->results().shape();
1,054✔
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] =
1,054✔
79
        xt::xtensor<double, 2>::from_shape({shape[0], shape[1]});
2,108✔
80
    }
81
  }
82

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

91
void FlatSourceDomain::batch_reset()
13,260✔
92
{
93
// Reset scalar fluxes and iteration volume tallies to zero
94
#pragma omp parallel for
7,020✔
95
  for (int64_t sr = 0; sr < n_source_regions_; sr++) {
6,507,040✔
96
    source_regions_.volume(sr) = 0.0;
6,500,800✔
97
    source_regions_.volume_sq(sr) = 0.0;
6,500,800✔
98
  }
99
#pragma omp parallel for
7,020✔
100
  for (int64_t se = 0; se < n_source_elements_; se++) {
12,088,480✔
101
    source_regions_.scalar_flux_new(se) = 0.0;
12,082,240✔
102
  }
103
}
13,260✔
104

105
void FlatSourceDomain::accumulate_iteration_flux()
5,355✔
106
{
107
#pragma omp parallel for
2,835✔
108
  for (int64_t se = 0; se < n_source_elements_; se++) {
4,582,040✔
109
    source_regions_.scalar_flux_final(se) +=
4,579,520✔
110
      source_regions_.scalar_flux_new(se);
4,579,520✔
111
  }
112
}
5,355✔
113

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

120
  double inverse_k_eff = 1.0 / k_eff;
5,695✔
121

122
// Reset all source regions to zero (important for void regions)
123
#pragma omp parallel for
3,015✔
124
  for (int64_t se = 0; se < n_source_elements_; se++) {
5,238,040✔
125
    source_regions_.source(se) = 0.0;
5,235,360✔
126
  }
127

128
  // Add scattering + fission source
129
#pragma omp parallel for
3,015✔
130
  for (int64_t sr = 0; sr < n_source_regions_; sr++) {
2,681,560✔
131
    int material = source_regions_.material(sr);
2,678,880✔
132
    if (material == MATERIAL_VOID) {
2,678,880✔
133
      continue;
320,000✔
134
    }
135
    for (int g_out = 0; g_out < negroups_; g_out++) {
7,274,240✔
136
      double sigma_t = sigma_t_[material * negroups_ + g_out];
4,915,360✔
137
      double scatter_source = 0.0;
4,915,360✔
138
      double fission_source = 0.0;
4,915,360✔
139

140
      for (int g_in = 0; g_in < negroups_; g_in++) {
27,726,080✔
141
        double scalar_flux = source_regions_.scalar_flux_old(sr, g_in);
22,810,720✔
142
        double sigma_s =
143
          sigma_s_[material * negroups_ * negroups_ + g_out * negroups_ + g_in];
22,810,720✔
144
        double nu_sigma_f = nu_sigma_f_[material * negroups_ + g_in];
22,810,720✔
145
        double chi = chi_[material * negroups_ + g_out];
22,810,720✔
146

147
        scatter_source += sigma_s * scalar_flux;
22,810,720✔
148
        fission_source += nu_sigma_f * scalar_flux * chi;
22,810,720✔
149
      }
150
      source_regions_.source(sr, g_out) =
4,915,360✔
151
        (scatter_source + fission_source * inverse_k_eff) / sigma_t;
4,915,360✔
152
    }
153
  }
154

155
  // Add external source if in fixed source mode
156
  if (settings::run_mode == RunMode::FIXED_SOURCE) {
5,695✔
157
#pragma omp parallel for
2,565✔
158
    for (int64_t se = 0; se < n_source_elements_; se++) {
4,650,120✔
159
      source_regions_.source(se) += source_regions_.external_source(se);
4,647,840✔
160
    }
161
  }
162

163
  simulation::time_update_src.stop();
5,695✔
164
}
5,695✔
165

166
// Normalizes flux and updates simulation-averaged volume estimate
167
void FlatSourceDomain::normalize_scalar_flux_and_volumes(
5,695✔
168
  double total_active_distance_per_iteration)
169
{
170
  double normalization_factor = 1.0 / total_active_distance_per_iteration;
5,695✔
171
  double volume_normalization_factor =
5,695✔
172
    1.0 / (total_active_distance_per_iteration * simulation::current_batch);
5,695✔
173

174
// Normalize scalar flux to total distance travelled by all rays this
175
// iteration
176
#pragma omp parallel for
3,015✔
177
  for (int64_t se = 0; se < n_source_elements_; se++) {
5,238,040✔
178
    source_regions_.scalar_flux_new(se) *= normalization_factor;
5,235,360✔
179
  }
180

181
// Accumulate cell-wise ray length tallies collected this iteration, then
182
// update the simulation-averaged cell-wise volume estimates
183
#pragma omp parallel for
3,015✔
184
  for (int64_t sr = 0; sr < n_source_regions_; sr++) {
2,681,560✔
185
    source_regions_.volume_t(sr) += source_regions_.volume(sr);
2,678,880✔
186
    source_regions_.volume_sq_t(sr) += source_regions_.volume_sq(sr);
2,678,880✔
187
    source_regions_.volume_naive(sr) =
5,357,760✔
188
      source_regions_.volume(sr) * normalization_factor;
2,678,880✔
189
    source_regions_.volume_sq(sr) =
5,357,760✔
190
      source_regions_.volume_sq_t(sr) / source_regions_.volume_t(sr);
2,678,880✔
191
    source_regions_.volume(sr) =
2,678,880✔
192
      source_regions_.volume_t(sr) * volume_normalization_factor;
2,678,880✔
193
  }
194
}
5,695✔
195

196
void FlatSourceDomain::set_flux_to_flux_plus_source(
11,805,106✔
197
  int64_t sr, double volume, int g)
198
{
199
  int material = source_regions_.material(sr);
11,805,106✔
200
  if (material == MATERIAL_VOID) {
11,805,106✔
201
    source_regions_.scalar_flux_new(sr, g) /= volume;
1,360,000✔
202
    source_regions_.scalar_flux_new(sr, g) +=
1,360,000✔
203
      0.5f * source_regions_.external_source(sr, g) *
1,360,000✔
204
      source_regions_.volume_sq(sr);
1,360,000✔
205
  } else {
206
    double sigma_t = sigma_t_[source_regions_.material(sr) * negroups_ + g];
10,445,106✔
207
    source_regions_.scalar_flux_new(sr, g) /= (sigma_t * volume);
10,445,106✔
208
    source_regions_.scalar_flux_new(sr, g) += source_regions_.source(sr, g);
10,445,106✔
209
  }
210
}
11,805,106✔
211

UNCOV
212
void FlatSourceDomain::set_flux_to_old_flux(int64_t sr, int g)
×
213
{
UNCOV
214
  source_regions_.scalar_flux_new(sr, g) =
×
UNCOV
215
    source_regions_.scalar_flux_old(sr, g);
×
216
}
217

218
void FlatSourceDomain::set_flux_to_source(int64_t sr, int g)
34✔
219
{
220
  source_regions_.scalar_flux_new(sr, g) = source_regions_.source(sr, g);
34✔
221
}
34✔
222

223
// Combine transport flux contributions and flat source contributions from the
224
// previous iteration to generate this iteration's estimate of scalar flux.
225
int64_t FlatSourceDomain::add_source_to_scalar_flux()
13,260✔
226
{
227
  int64_t n_hits = 0;
13,260✔
228

229
#pragma omp parallel for reduction(+ : n_hits)
7,020✔
230
  for (int64_t sr = 0; sr < n_source_regions_; sr++) {
6,507,040✔
231

232
    double volume_simulation_avg = source_regions_.volume(sr);
6,500,800✔
233
    double volume_iteration = source_regions_.volume_naive(sr);
6,500,800✔
234

235
    // Increment the number of hits if cell was hit this iteration
236
    if (volume_iteration) {
6,500,800✔
237
      n_hits++;
6,500,784✔
238
    }
239

240
    // The volume treatment depends on the volume estimator type
241
    // and whether or not an external source is present in the cell.
242
    double volume;
243
    switch (volume_estimator_) {
6,500,800✔
244
    case RandomRayVolumeEstimator::NAIVE:
1,244,160✔
245
      volume = volume_iteration;
1,244,160✔
246
      break;
1,244,160✔
247
    case RandomRayVolumeEstimator::SIMULATION_AVERAGED:
691,200✔
248
      volume = volume_simulation_avg;
691,200✔
249
      break;
691,200✔
250
    case RandomRayVolumeEstimator::HYBRID:
4,565,440✔
251
      if (source_regions_.external_source_present(sr)) {
4,565,440✔
252
        volume = volume_iteration;
18,640✔
253
      } else {
254
        volume = volume_simulation_avg;
4,546,800✔
255
      }
256
      break;
4,565,440✔
257
    default:
258
      fatal_error("Invalid volume estimator type");
259
    }
260

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

295
  // Return the number of source regions that were hit this iteration
296
  return n_hits;
13,260✔
297
}
298

299
// Generates new estimate of k_eff based on the differences between this
300
// iteration's estimate of the scalar flux and the last iteration's estimate.
301
double FlatSourceDomain::compute_k_eff(double k_eff_old) const
2,210✔
302
{
303
  double fission_rate_old = 0;
2,210✔
304
  double fission_rate_new = 0;
2,210✔
305

306
  // Vector for gathering fission source terms for Shannon entropy calculation
307
  vector<float> p(n_source_regions_, 0.0f);
2,210✔
308

309
#pragma omp parallel for reduction(+ : fission_rate_old, fission_rate_new)
1,170✔
310
  for (int64_t sr = 0; sr < n_source_regions_; sr++) {
276,240✔
311

312
    // If simulation averaged volume is zero, don't include this cell
313
    double volume = source_regions_.volume(sr);
275,200✔
314
    if (volume == 0.0) {
275,200✔
315
      continue;
316
    }
317

318
    int material = source_regions_.material(sr);
275,200✔
319
    if (material == MATERIAL_VOID) {
275,200✔
320
      continue;
321
    }
322

323
    double sr_fission_source_old = 0;
275,200✔
324
    double sr_fission_source_new = 0;
275,200✔
325

326
    for (int g = 0; g < negroups_; g++) {
1,955,840✔
327
      double nu_sigma_f = nu_sigma_f_[material * negroups_ + g];
1,680,640✔
328
      sr_fission_source_old +=
1,680,640✔
329
        nu_sigma_f * source_regions_.scalar_flux_old(sr, g);
1,680,640✔
330
      sr_fission_source_new +=
1,680,640✔
331
        nu_sigma_f * source_regions_.scalar_flux_new(sr, g);
1,680,640✔
332
    }
333

334
    // Compute total fission rates in FSR
335
    sr_fission_source_old *= volume;
275,200✔
336
    sr_fission_source_new *= volume;
275,200✔
337

338
    // Accumulate totals
339
    fission_rate_old += sr_fission_source_old;
275,200✔
340
    fission_rate_new += sr_fission_source_new;
275,200✔
341

342
    // Store total fission rate in the FSR for Shannon calculation
343
    p[sr] = sr_fission_source_new;
275,200✔
344
  }
345

346
  double k_eff_new = k_eff_old * (fission_rate_new / fission_rate_old);
2,210✔
347

348
  double H = 0.0;
2,210✔
349
  // defining an inverse sum for better performance
350
  double inverse_sum = 1 / fission_rate_new;
2,210✔
351

352
#pragma omp parallel for reduction(+ : H)
1,170✔
353
  for (int64_t sr = 0; sr < n_source_regions_; sr++) {
276,240✔
354
    // Only if FSR has non-negative and non-zero fission source
355
    if (p[sr] > 0.0f) {
275,200✔
356
      // Normalize to total weight of bank sites. p_i for better performance
357
      float p_i = p[sr] * inverse_sum;
110,080✔
358
      // Sum values to obtain Shannon entropy.
359
      H -= p_i * std::log2(p_i);
110,080✔
360
    }
361
  }
362

363
  // Adds entropy value to shared entropy vector in openmc namespace.
364
  simulation::entropy.push_back(H);
2,210✔
365

366
  return k_eff_new;
2,210✔
367
}
2,210✔
368

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

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

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

402
void FlatSourceDomain::convert_source_regions_to_tallies()
336✔
403
{
404
  openmc::simulation::time_tallies.start();
336✔
405

406
  // Tracks if we've generated a mapping yet for all source regions.
407
  bool all_source_regions_mapped = true;
336✔
408

409
// Attempt to generate mapping for all source regions
410
#pragma omp parallel for
168✔
411
  for (int64_t sr = 0; sr < n_source_regions_; sr++) {
213,192✔
412

413
    // If this source region has not been hit by a ray yet, then
414
    // we aren't going to be able to map it, so skip it.
415
    if (!source_regions_.position_recorded(sr)) {
213,024✔
416
      all_source_regions_mapped = false;
417
      continue;
418
    }
419

420
    // A particle located at the recorded midpoint of a ray
421
    // crossing through this source region is used to estabilish
422
    // the spatial location of the source region
423
    Particle p;
213,024✔
424
    p.r() = source_regions_.position(sr);
213,024✔
425
    p.r_last() = source_regions_.position(sr);
213,024✔
426
    bool found = exhaustive_find_cell(p);
213,024✔
427

428
    // Loop over energy groups (so as to support energy filters)
429
    for (int g = 0; g < negroups_; g++) {
503,808✔
430

431
      // Set particle to the current energy
432
      p.g() = g;
290,784✔
433
      p.g_last() = g;
290,784✔
434
      p.E() = data::mg.energy_bin_avg_[p.g()];
290,784✔
435
      p.E_last() = p.E();
290,784✔
436

437
      int64_t source_element = sr * negroups_ + g;
290,784✔
438

439
      // If this task has already been populated, we don't need to do
440
      // it again.
441
      if (source_regions_.tally_task(sr, g).size() > 0) {
290,784✔
442
        continue;
443
      }
444

445
      // Loop over all active tallies. This logic is essentially identical
446
      // to what happens when scanning for applicable tallies during
447
      // MC transport.
448
      for (auto i_tally : model::active_tallies) {
993,216✔
449
        Tally& tally {*model::tallies[i_tally]};
702,432✔
450

451
        // Initialize an iterator over valid filter bin combinations.
452
        // If there are no valid combinations, use a continue statement
453
        // to ensure we skip the assume_separate break below.
454
        auto filter_iter = FilterBinIter(tally, p);
702,432✔
455
        auto end = FilterBinIter(tally, true, &p.filter_matches());
702,432✔
456
        if (filter_iter == end)
702,432✔
457
          continue;
393,984✔
458

459
        // Loop over filter bins.
460
        for (; filter_iter != end; ++filter_iter) {
616,896✔
461
          auto filter_index = filter_iter.index_;
308,448✔
462
          auto filter_weight = filter_iter.weight_;
308,448✔
463

464
          // Loop over scores
465
          for (int score = 0; score < tally.scores_.size(); score++) {
798,336✔
466
            auto score_bin = tally.scores_[score];
489,888✔
467
            // If a valid tally, filter, and score combination has been found,
468
            // then add it to the list of tally tasks for this source element.
469
            TallyTask task(i_tally, filter_index, score, score_bin);
489,888✔
470
            source_regions_.tally_task(sr, g).push_back(task);
489,888✔
471

472
            // Also add this task to the list of volume tasks for this source
473
            // region.
474
            source_regions_.volume_task(sr).insert(task);
489,888✔
475
          }
476
        }
477
      }
478
      // Reset all the filter matches for the next tally event.
479
      for (auto& match : p.filter_matches())
1,125,408✔
480
        match.bins_present_ = false;
834,624✔
481
    }
482
  }
213,024✔
483
  openmc::simulation::time_tallies.stop();
336✔
484

485
  mapped_all_tallies_ = all_source_regions_mapped;
336✔
486
}
336✔
487

488
// Set the volume accumulators to zero for all tallies
489
void FlatSourceDomain::reset_tally_volumes()
3,780✔
490
{
491
  if (volume_normalized_flux_tallies_) {
3,780✔
492
#pragma omp parallel for
1,530✔
493
    for (int i = 0; i < tally_volumes_.size(); i++) {
5,460✔
494
      auto& tensor = tally_volumes_[i];
3,930✔
495
      tensor.fill(0.0); // Set all elements of the tensor to 0.0
3,930✔
496
    }
497
  }
498
}
3,780✔
499

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

516
  // Step 1 is to sum over all source regions and energy groups to get the
517
  // total external source strength in the simulation.
518
  double simulation_external_source_strength = 0.0;
3,203✔
519
#pragma omp parallel for reduction(+ : simulation_external_source_strength)
1,611✔
520
  for (int64_t sr = 0; sr < n_source_regions_; sr++) {
2,316,488✔
521
    int material = source_regions_.material(sr);
2,314,896✔
522
    double volume = source_regions_.volume(sr) * simulation_volume_;
2,314,896✔
523
    for (int g = 0; g < negroups_; g++) {
5,289,600✔
524
      // For non-void regions, we store the external source pre-divided by
525
      // sigma_t. We need to multiply non-void regions back up by sigma_t
526
      // to get the total source strength in the expected units.
527
      double sigma_t = 1.0;
2,974,704✔
528
      if (material != MATERIAL_VOID) {
2,974,704✔
529
        sigma_t = sigma_t_[material * negroups_ + g];
2,718,704✔
530
      }
531
      simulation_external_source_strength +=
2,974,704✔
532
        source_regions_.external_source(sr, g) * sigma_t * volume;
2,974,704✔
533
    }
534
  }
535

536
  // Step 2 is to determine the total user-specified external source strength
537
  double user_external_source_strength = 0.0;
3,203✔
538
  for (auto& ext_source : model::external_sources) {
6,406✔
539
    user_external_source_strength += ext_source->strength();
3,203✔
540
  }
541

542
  // The correction factor is the ratio of the user-specified external source
543
  // strength to the simulation external source strength.
544
  double source_normalization_factor =
3,203✔
545
    user_external_source_strength / simulation_external_source_strength;
546

547
  return source_normalization_factor;
3,203✔
548
}
549

550
// Tallying in random ray is not done directly during transport, rather,
551
// it is done only once after each power iteration. This is made possible
552
// by way of a mapping data structure that relates spatial source regions
553
// (FSRs) to tally/filter/score combinations. The mechanism by which the
554
// mapping is done (and the limitations incurred) is documented in the
555
// "convert_source_regions_to_tallies()" function comments above. The present
556
// tally function simply traverses the mapping data structure and executes
557
// the scoring operations to OpenMC's native tally result arrays.
558

559
void FlatSourceDomain::random_ray_tally()
3,780✔
560
{
561
  openmc::simulation::time_tallies.start();
3,780✔
562

563
  // Reset our tally volumes to zero
564
  reset_tally_volumes();
3,780✔
565

566
  double source_normalization_factor =
567
    compute_fixed_source_normalization_factor();
3,780✔
568

569
// We loop over all source regions and energy groups. For each
570
// element, we check if there are any scores needed and apply
571
// them.
572
#pragma omp parallel for
1,890✔
573
  for (int64_t sr = 0; sr < n_source_regions_; sr++) {
2,283,090✔
574
    // The fsr.volume_ is the unitless fractional simulation averaged volume
575
    // (i.e., it is the FSR's fraction of the overall simulation volume). The
576
    // simulation_volume_ is the total 3D physical volume in cm^3 of the
577
    // entire global simulation domain (as defined by the ray source box).
578
    // Thus, the FSR's true 3D spatial volume in cm^3 is found by multiplying
579
    // its fraction of the total volume by the total volume. Not important in
580
    // eigenvalue solves, but useful in fixed source solves for returning the
581
    // flux shape with a magnitude that makes sense relative to the fixed
582
    // source strength.
583
    double volume = source_regions_.volume(sr) * simulation_volume_;
2,281,200✔
584

585
    double material = source_regions_.material(sr);
2,281,200✔
586
    for (int g = 0; g < negroups_; g++) {
5,715,840✔
587
      double flux =
588
        source_regions_.scalar_flux_new(sr, g) * source_normalization_factor;
3,434,640✔
589

590
      // Determine numerical score value
591
      for (auto& task : source_regions_.tally_task(sr, g)) {
9,648,960✔
592
        double score = 0.0;
6,214,320✔
593
        switch (task.score_type) {
6,214,320✔
594

595
        case SCORE_FLUX:
3,522,960✔
596
          score = flux * volume;
3,522,960✔
597
          break;
3,522,960✔
598

599
        case SCORE_TOTAL:
600
          if (material != MATERIAL_VOID) {
601
            score = flux * volume * sigma_t_[material * negroups_ + g];
602
          }
603
          break;
604

605
        case SCORE_FISSION:
1,345,680✔
606
          if (material != MATERIAL_VOID) {
1,345,680✔
607
            score = flux * volume * sigma_f_[material * negroups_ + g];
1,345,680✔
608
          }
609
          break;
1,345,680✔
610

611
        case SCORE_NU_FISSION:
1,345,680✔
612
          if (material != MATERIAL_VOID) {
1,345,680✔
613
            score = flux * volume * nu_sigma_f_[material * negroups_ + g];
1,345,680✔
614
          }
615
          break;
1,345,680✔
616

617
        case SCORE_EVENTS:
618
          score = 1.0;
619
          break;
620

621
        default:
622
          fatal_error("Invalid score specified in tallies.xml. Only flux, "
623
                      "total, fission, nu-fission, and events are supported in "
624
                      "random ray mode.");
625
          break;
626
        }
627

628
        // Apply score to the appropriate tally bin
629
        Tally& tally {*model::tallies[task.tally_idx]};
6,214,320✔
630
#pragma omp atomic
631
        tally.results_(task.filter_idx, task.score_idx, TallyResult::VALUE) +=
6,214,320✔
632
          score;
633
      }
634
    }
635

636
    // For flux tallies, the total volume of the spatial region is needed
637
    // for normalizing the flux. We store this volume in a separate tensor.
638
    // We only contribute to each volume tally bin once per FSR.
639
    if (volume_normalized_flux_tallies_) {
2,281,200✔
640
      for (const auto& task : source_regions_.volume_task(sr)) {
6,079,680✔
641
        if (task.score_type == SCORE_FLUX) {
3,970,080✔
642
#pragma omp atomic
643
          tally_volumes_[task.tally_idx](task.filter_idx, task.score_idx) +=
2,740,320✔
644
            volume;
645
        }
646
      }
647
    }
648
  } // end FSR loop
649

650
  // Normalize any flux scores by the total volume of the FSRs scoring to that
651
  // bin. To do this, we loop over all tallies, and then all filter bins,
652
  // and then scores. For each score, we check the tally data structure to
653
  // see what index that score corresponds to. If that score is a flux score,
654
  // then we divide it by volume.
655
  if (volume_normalized_flux_tallies_) {
3,780✔
656
    for (int i = 0; i < model::tallies.size(); i++) {
10,920✔
657
      Tally& tally {*model::tallies[i]};
7,860✔
658
#pragma omp parallel for
3,930✔
659
      for (int bin = 0; bin < tally.n_filter_bins(); bin++) {
30,480✔
660
        for (int score_idx = 0; score_idx < tally.n_scores(); score_idx++) {
73,260✔
661
          auto score_type = tally.scores_[score_idx];
46,710✔
662
          if (score_type == SCORE_FLUX) {
46,710✔
663
            double vol = tally_volumes_[i](bin, score_idx);
26,550✔
664
            if (vol > 0.0) {
26,550✔
665
              tally.results_(bin, score_idx, TallyResult::VALUE) /= vol;
26,550✔
666
            }
667
          }
668
        }
669
      }
670
    }
671
  }
672

673
  openmc::simulation::time_tallies.stop();
3,780✔
674
}
3,780✔
675

676
void FlatSourceDomain::all_reduce_replicated_source_regions()
13,260✔
677
{
678
#ifdef OPENMC_MPI
679
  // If we only have 1 MPI rank, no need
680
  // to reduce anything.
681
  if (mpi::n_procs <= 1)
7,800✔
682
    return;
683

684
  simulation::time_bank_sendrecv.start();
7,800✔
685

686
  // First, we broadcast the fully mapped tally status variable so that
687
  // all ranks are on the same page
688
  int mapped_all_tallies_i = static_cast<int>(mapped_all_tallies_);
7,800✔
689
  MPI_Bcast(&mapped_all_tallies_i, 1, MPI_INT, 0, mpi::intracomm);
7,800✔
690

691
  bool reduce_position =
7,800✔
692
    simulation::current_batch > settings::n_inactive && !mapped_all_tallies_i;
7,800✔
693

694
  source_regions_.mpi_sync_ranks(reduce_position);
7,800✔
695

696
  simulation::time_bank_sendrecv.stop();
7,800✔
697
#endif
698
}
5,460✔
699

UNCOV
700
double FlatSourceDomain::evaluate_flux_at_point(
×
701
  Position r, int64_t sr, int g) const
702
{
UNCOV
703
  return source_regions_.scalar_flux_final(sr, g) /
×
UNCOV
704
         (settings::n_batches - settings::n_inactive);
×
705
}
706

707
// Outputs all basic material, FSR ID, multigroup flux, and
708
// fission source data to .vtk file that can be directly
709
// loaded and displayed by Paraview. Note that .vtk binary
710
// files require big endian byte ordering, so endianness
711
// is checked and flipped if necessary.
UNCOV
712
void FlatSourceDomain::output_to_vtk() const
×
713
{
714
  // Rename .h5 plot filename(s) to .vtk filenames
UNCOV
715
  for (int p = 0; p < model::plots.size(); p++) {
×
UNCOV
716
    PlottableInterface* plot = model::plots[p].get();
×
UNCOV
717
    plot->path_plot() =
×
UNCOV
718
      plot->path_plot().substr(0, plot->path_plot().find_last_of('.')) + ".vtk";
×
719
  }
720

721
  // Print header information
UNCOV
722
  print_plot();
×
723

724
  // Outer loop over plots
UNCOV
725
  for (int p = 0; p < model::plots.size(); p++) {
×
726

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

730
    // Random ray plots only support voxel plots
UNCOV
731
    if (!openmc_plot) {
×
UNCOV
732
      warning(fmt::format("Plot {} is invalid plot type -- only voxel plotting "
×
733
                          "is allowed in random ray mode.",
734
        p));
UNCOV
735
      continue;
×
UNCOV
736
    } else if (openmc_plot->type_ != Plot::PlotType::voxel) {
×
UNCOV
737
      warning(fmt::format("Plot {} is invalid plot type -- only voxel plotting "
×
738
                          "is allowed in random ray mode.",
739
        p));
UNCOV
740
      continue;
×
741
    }
742

UNCOV
743
    int Nx = openmc_plot->pixels_[0];
×
UNCOV
744
    int Ny = openmc_plot->pixels_[1];
×
UNCOV
745
    int Nz = openmc_plot->pixels_[2];
×
UNCOV
746
    Position origin = openmc_plot->origin_;
×
UNCOV
747
    Position width = openmc_plot->width_;
×
UNCOV
748
    Position ll = origin - width / 2.0;
×
UNCOV
749
    double x_delta = width.x / Nx;
×
UNCOV
750
    double y_delta = width.y / Ny;
×
UNCOV
751
    double z_delta = width.z / Nz;
×
UNCOV
752
    std::string filename = openmc_plot->path_plot();
×
753

754
    // Perform sanity checks on file size
UNCOV
755
    uint64_t bytes = Nx * Ny * Nz * (negroups_ + 1 + 1 + 1) * sizeof(float);
×
UNCOV
756
    write_message(5, "Processing plot {}: {}... (Estimated size is {} MB)",
×
UNCOV
757
      openmc_plot->id(), filename, bytes / 1.0e6);
×
UNCOV
758
    if (bytes / 1.0e9 > 1.0) {
×
759
      warning("Voxel plot specification is very large (>1 GB). Plotting may be "
×
760
              "slow.");
UNCOV
761
    } else if (bytes / 1.0e9 > 100.0) {
×
762
      fatal_error("Voxel plot specification is too large (>100 GB). Exiting.");
×
763
    }
764

765
    // Relate voxel spatial locations to random ray source regions
UNCOV
766
    vector<int> voxel_indices(Nx * Ny * Nz);
×
UNCOV
767
    vector<Position> voxel_positions(Nx * Ny * Nz);
×
768

769
#pragma omp parallel for collapse(3)
770
    for (int z = 0; z < Nz; z++) {
771
      for (int y = 0; y < Ny; y++) {
772
        for (int x = 0; x < Nx; x++) {
773
          Position sample;
774
          sample.z = ll.z + z_delta / 2.0 + z * z_delta;
775
          sample.y = ll.y + y_delta / 2.0 + y * y_delta;
776
          sample.x = ll.x + x_delta / 2.0 + x * x_delta;
777
          Particle p;
778
          p.r() = sample;
779
          bool found = exhaustive_find_cell(p);
780
          int i_cell = p.lowest_coord().cell;
781
          int64_t source_region_idx =
782
            source_region_offsets_[i_cell] + p.cell_instance();
783
          voxel_indices[z * Ny * Nx + y * Nx + x] = source_region_idx;
784
          voxel_positions[z * Ny * Nx + y * Nx + x] = sample;
785
        }
786
      }
787
    }
788

789
    double source_normalization_factor =
790
      compute_fixed_source_normalization_factor();
×
791

792
    // Open file for writing
793
    std::FILE* plot = std::fopen(filename.c_str(), "wb");
×
794

795
    // Write vtk metadata
796
    std::fprintf(plot, "# vtk DataFile Version 2.0\n");
×
797
    std::fprintf(plot, "Dataset File\n");
×
798
    std::fprintf(plot, "BINARY\n");
×
799
    std::fprintf(plot, "DATASET STRUCTURED_POINTS\n");
×
UNCOV
800
    std::fprintf(plot, "DIMENSIONS %d %d %d\n", Nx, Ny, Nz);
×
UNCOV
801
    std::fprintf(plot, "ORIGIN 0 0 0\n");
×
802
    std::fprintf(plot, "SPACING %lf %lf %lf\n", x_delta, y_delta, z_delta);
×
803
    std::fprintf(plot, "POINT_DATA %d\n", Nx * Ny * Nz);
×
804

805
    // Plot multigroup flux data
806
    for (int g = 0; g < negroups_; g++) {
×
UNCOV
807
      std::fprintf(plot, "SCALARS flux_group_%d float\n", g);
×
808
      std::fprintf(plot, "LOOKUP_TABLE default\n");
×
809
      for (int i = 0; i < Nx * Ny * Nz; i++) {
×
UNCOV
810
        int64_t fsr = voxel_indices[i];
×
UNCOV
811
        int64_t source_element = fsr * negroups_ + g;
×
UNCOV
812
        float flux = evaluate_flux_at_point(voxel_positions[i], fsr, g);
×
813
        flux = convert_to_big_endian<float>(flux);
×
UNCOV
814
        std::fwrite(&flux, sizeof(float), 1, plot);
×
815
      }
816
    }
817

818
    // Plot FSRs
UNCOV
819
    std::fprintf(plot, "SCALARS FSRs float\n");
×
UNCOV
820
    std::fprintf(plot, "LOOKUP_TABLE default\n");
×
UNCOV
821
    for (int fsr : voxel_indices) {
×
UNCOV
822
      float value = future_prn(10, fsr);
×
UNCOV
823
      value = convert_to_big_endian<float>(value);
×
UNCOV
824
      std::fwrite(&value, sizeof(float), 1, plot);
×
825
    }
826

827
    // Plot Materials
UNCOV
828
    std::fprintf(plot, "SCALARS Materials int\n");
×
UNCOV
829
    std::fprintf(plot, "LOOKUP_TABLE default\n");
×
UNCOV
830
    for (int fsr : voxel_indices) {
×
UNCOV
831
      int mat = source_regions_.material(fsr);
×
UNCOV
832
      mat = convert_to_big_endian<int>(mat);
×
UNCOV
833
      std::fwrite(&mat, sizeof(int), 1, plot);
×
834
    }
835

836
    // Plot fission source
UNCOV
837
    if (settings::run_mode == RunMode::EIGENVALUE) {
×
838
      std::fprintf(plot, "SCALARS total_fission_source float\n");
×
UNCOV
839
      std::fprintf(plot, "LOOKUP_TABLE default\n");
×
UNCOV
840
      for (int i = 0; i < Nx * Ny * Nz; i++) {
×
841
        int64_t fsr = voxel_indices[i];
×
842

843
        float total_fission = 0.0;
×
844
        int mat = source_regions_.material(fsr);
×
845
        if (mat != MATERIAL_VOID) {
×
846
          for (int g = 0; g < negroups_; g++) {
×
847
            int64_t source_element = fsr * negroups_ + g;
×
848
            float flux = evaluate_flux_at_point(voxel_positions[i], fsr, g);
×
UNCOV
849
            double sigma_f = sigma_f_[mat * negroups_ + g];
×
UNCOV
850
            total_fission += sigma_f * flux;
×
851
          }
852
        }
853
        total_fission = convert_to_big_endian<float>(total_fission);
×
854
        std::fwrite(&total_fission, sizeof(float), 1, plot);
×
855
      }
856
    } else {
857
      std::fprintf(plot, "SCALARS external_source float\n");
×
858
      std::fprintf(plot, "LOOKUP_TABLE default\n");
×
859
      for (int i = 0; i < Nx * Ny * Nz; i++) {
×
860
        int64_t fsr = voxel_indices[i];
×
UNCOV
861
        float total_external = 0.0f;
×
UNCOV
862
        for (int g = 0; g < negroups_; g++) {
×
UNCOV
863
          total_external += source_regions_.external_source(fsr, g);
×
864
        }
865
        total_external = convert_to_big_endian<float>(total_external);
×
866
        std::fwrite(&total_external, sizeof(float), 1, plot);
×
867
      }
868
    }
869

870
    std::fclose(plot);
×
871
  }
872
}
873

874
void FlatSourceDomain::apply_external_source_to_source_region(
2,346✔
875
  Discrete* discrete, double strength_factor, int64_t sr)
876
{
877
  source_regions_.external_source_present(sr) = 1;
2,346✔
878

879
  const auto& discrete_energies = discrete->x();
2,346✔
880
  const auto& discrete_probs = discrete->prob();
2,346✔
881

882
  for (int i = 0; i < discrete_energies.size(); i++) {
4,896✔
883
    int g = data::mg.get_group_index(discrete_energies[i]);
2,550✔
884
    source_regions_.external_source(sr, g) +=
2,550✔
885
      discrete_probs[i] * strength_factor;
2,550✔
886
  }
887
}
2,346✔
888

889
void FlatSourceDomain::apply_external_source_to_cell_instances(int32_t i_cell,
357✔
890
  Discrete* discrete, double strength_factor, int target_material_id,
891
  const vector<int32_t>& instances)
892
{
893
  Cell& cell = *model::cells[i_cell];
357✔
894

895
  if (cell.type_ != Fill::MATERIAL)
357✔
UNCOV
896
    return;
×
897

898
  for (int j : instances) {
31,943✔
899
    int cell_material_idx = cell.material(j);
31,586✔
900
    int cell_material_id;
901
    if (cell_material_idx == MATERIAL_VOID) {
31,586✔
902
      cell_material_id = MATERIAL_VOID;
272✔
903
    } else {
904
      cell_material_id = model::materials[cell_material_idx]->id();
31,314✔
905
    }
906
    if (target_material_id == C_NONE ||
31,586✔
907
        cell_material_id == target_material_id) {
908
      int64_t source_region = source_region_offsets_[i_cell] + j;
2,346✔
909
      apply_external_source_to_source_region(
2,346✔
910
        discrete, strength_factor, source_region);
911
    }
912
  }
913
}
914

915
void FlatSourceDomain::apply_external_source_to_cell_and_children(
391✔
916
  int32_t i_cell, Discrete* discrete, double strength_factor,
917
  int32_t target_material_id)
918
{
919
  Cell& cell = *model::cells[i_cell];
391✔
920

921
  if (cell.type_ == Fill::MATERIAL) {
391✔
922
    vector<int> instances(cell.n_instances_);
357✔
923
    std::iota(instances.begin(), instances.end(), 0);
357✔
924
    apply_external_source_to_cell_instances(
357✔
925
      i_cell, discrete, strength_factor, target_material_id, instances);
926
  } else if (target_material_id == C_NONE) {
391✔
927
    std::unordered_map<int32_t, vector<int32_t>> cell_instance_list =
UNCOV
928
      cell.get_contained_cells(0, nullptr);
×
UNCOV
929
    for (const auto& pair : cell_instance_list) {
×
UNCOV
930
      int32_t i_child_cell = pair.first;
×
UNCOV
931
      apply_external_source_to_cell_instances(i_child_cell, discrete,
×
UNCOV
932
        strength_factor, target_material_id, pair.second);
×
933
    }
934
  }
935
}
391✔
936

937
void FlatSourceDomain::count_external_source_regions()
323✔
938
{
939
  n_external_source_regions_ = 0;
323✔
940
#pragma omp parallel for reduction(+ : n_external_source_regions_)
171✔
941
  for (int64_t sr = 0; sr < n_source_regions_; sr++) {
240,728✔
942
    if (source_regions_.external_source_present(sr)) {
240,576✔
943
      n_external_source_regions_++;
1,104✔
944
    }
945
  }
946
}
323✔
947

948
void FlatSourceDomain::convert_external_sources()
323✔
949
{
950
  // Loop over external sources
951
  for (int es = 0; es < model::external_sources.size(); es++) {
646✔
952
    Source* s = model::external_sources[es].get();
323✔
953
    IndependentSource* is = dynamic_cast<IndependentSource*>(s);
323✔
954
    Discrete* energy = dynamic_cast<Discrete*>(is->energy());
323✔
955
    const std::unordered_set<int32_t>& domain_ids = is->domain_ids();
323✔
956

957
    double strength_factor = is->strength();
323✔
958

959
    if (is->domain_type() == Source::DomainType::MATERIAL) {
323✔
960
      for (int32_t material_id : domain_ids) {
34✔
961
        for (int i_cell = 0; i_cell < model::cells.size(); i_cell++) {
102✔
962
          apply_external_source_to_cell_and_children(
85✔
963
            i_cell, energy, strength_factor, material_id);
964
        }
965
      }
966
    } else if (is->domain_type() == Source::DomainType::CELL) {
306✔
967
      for (int32_t cell_id : domain_ids) {
34✔
968
        int32_t i_cell = model::cell_map[cell_id];
17✔
969
        apply_external_source_to_cell_and_children(
17✔
970
          i_cell, energy, strength_factor, C_NONE);
971
      }
972
    } else if (is->domain_type() == Source::DomainType::UNIVERSE) {
289✔
973
      for (int32_t universe_id : domain_ids) {
578✔
974
        int32_t i_universe = model::universe_map[universe_id];
289✔
975
        Universe& universe = *model::universes[i_universe];
289✔
976
        for (int32_t i_cell : universe.cells_) {
578✔
977
          apply_external_source_to_cell_and_children(
289✔
978
            i_cell, energy, strength_factor, C_NONE);
979
        }
980
      }
981
    }
982
  } // End loop over external sources
983

984
// Divide the fixed source term by sigma t (to save time when applying each
985
// iteration)
986
#pragma omp parallel for
171✔
987
  for (int64_t sr = 0; sr < n_source_regions_; sr++) {
240,728✔
988
    int material = source_regions_.material(sr);
240,576✔
989
    if (material == MATERIAL_VOID) {
240,576✔
990
      continue;
16,000✔
991
    }
992
    for (int g = 0; g < negroups_; g++) {
482,560✔
993
      double sigma_t = sigma_t_[material * negroups_ + g];
257,984✔
994
      source_regions_.external_source(sr, g) /= sigma_t;
257,984✔
995
    }
996
  }
997
}
323✔
998

999
void FlatSourceDomain::flux_swap()
13,260✔
1000
{
1001
  source_regions_.flux_swap();
13,260✔
1002
}
13,260✔
1003

1004
void FlatSourceDomain::flatten_xs()
476✔
1005
{
1006
  // Temperature and angle indices, if using multiple temperature
1007
  // data sets and/or anisotropic data sets.
1008
  // TODO: Currently assumes we are only using single temp/single angle data.
1009
  const int t = 0;
476✔
1010
  const int a = 0;
476✔
1011

1012
  n_materials_ = data::mg.macro_xs_.size();
476✔
1013
  for (auto& m : data::mg.macro_xs_) {
1,734✔
1014
    for (int g_out = 0; g_out < negroups_; g_out++) {
4,148✔
1015
      if (m.exists_in_model) {
2,890✔
1016
        double sigma_t =
1017
          m.get_xs(MgxsType::TOTAL, g_out, NULL, NULL, NULL, t, a);
2,822✔
1018
        sigma_t_.push_back(sigma_t);
2,822✔
1019

1020
        double nu_Sigma_f =
1021
          m.get_xs(MgxsType::NU_FISSION, g_out, NULL, NULL, NULL, t, a);
2,822✔
1022
        nu_sigma_f_.push_back(nu_Sigma_f);
2,822✔
1023

1024
        double sigma_f =
1025
          m.get_xs(MgxsType::FISSION, g_out, NULL, NULL, NULL, t, a);
2,822✔
1026
        sigma_f_.push_back(sigma_f);
2,822✔
1027

1028
        double chi =
1029
          m.get_xs(MgxsType::CHI_PROMPT, g_out, &g_out, NULL, NULL, t, a);
2,822✔
1030
        chi_.push_back(chi);
2,822✔
1031

1032
        for (int g_in = 0; g_in < negroups_; g_in++) {
17,068✔
1033
          double sigma_s =
1034
            m.get_xs(MgxsType::NU_SCATTER, g_in, &g_out, NULL, NULL, t, a);
14,246✔
1035
          sigma_s_.push_back(sigma_s);
14,246✔
1036
        }
1037
      } else {
1038
        sigma_t_.push_back(0);
68✔
1039
        nu_sigma_f_.push_back(0);
68✔
1040
        sigma_f_.push_back(0);
68✔
1041
        chi_.push_back(0);
68✔
1042
        for (int g_in = 0; g_in < negroups_; g_in++) {
136✔
1043
          sigma_s_.push_back(0);
68✔
1044
        }
1045
      }
1046
    }
1047
  }
1048
}
476✔
1049

1050
void FlatSourceDomain::set_adjoint_sources(const vector<double>& forward_flux)
34✔
1051
{
1052
  // Set the external source to 1/forward_flux. If the forward flux is negative
1053
  // or zero, set the adjoint source to zero, as this is likely a very small
1054
  // source region that we don't need to bother trying to vector particles
1055
  // towards. Flux negativity in random ray is not related to the flux being
1056
  // small in magnitude, but rather due to the source region being physically
1057
  // small in volume and thus having a noisy flux estimate.
1058
#pragma omp parallel for
18✔
1059
  for (int64_t sr = 0; sr < n_source_regions_; sr++) {
27,664✔
1060
    for (int g = 0; g < negroups_; g++) {
55,296✔
1061
      double flux = forward_flux[sr * negroups_ + g];
27,648✔
1062
      if (flux <= 0.0) {
27,648✔
1063
        source_regions_.external_source(sr, g) = 0.0;
1064
      } else {
1065
        source_regions_.external_source(sr, g) = 1.0 / flux;
27,648✔
1066
      }
1067
    }
1068
  }
1069

1070
  // Divide the fixed source term by sigma t (to save time when applying each
1071
  // iteration)
1072
#pragma omp parallel for
18✔
1073
  for (int64_t sr = 0; sr < n_source_regions_; sr++) {
27,664✔
1074
    int material = source_regions_.material(sr);
27,648✔
1075
    if (material == MATERIAL_VOID) {
27,648✔
1076
      continue;
1077
    }
1078
    for (int g = 0; g < negroups_; g++) {
55,296✔
1079
      double sigma_t = sigma_t_[material * negroups_ + g];
27,648✔
1080
      source_regions_.external_source(sr, g) /= sigma_t;
27,648✔
1081
    }
1082
  }
1083
}
34✔
1084

1085
void FlatSourceDomain::transpose_scattering_matrix()
51✔
1086
{
1087
  // Transpose the inner two dimensions for each material
1088
  for (int m = 0; m < n_materials_; ++m) {
187✔
1089
    int material_offset = m * negroups_ * negroups_;
136✔
1090
    for (int i = 0; i < negroups_; ++i) {
476✔
1091
      for (int j = i + 1; j < negroups_; ++j) {
1,054✔
1092
        // Calculate indices of the elements to swap
1093
        int idx1 = material_offset + i * negroups_ + j;
714✔
1094
        int idx2 = material_offset + j * negroups_ + i;
714✔
1095

1096
        // Swap the elements to transpose the matrix
1097
        std::swap(sigma_s_[idx1], sigma_s_[idx2]);
714✔
1098
      }
1099
    }
1100
  }
1101
}
51✔
1102

1103
void FlatSourceDomain::serialize_final_fluxes(vector<double>& flux)
425✔
1104
{
1105
  // Ensure array is correct size
1106
  flux.resize(n_source_regions_ * negroups_);
425✔
1107
// Serialize the final fluxes for output
1108
#pragma omp parallel for
225✔
1109
  for (int64_t se = 0; se < n_source_elements_; se++) {
346,600✔
1110
    flux[se] = source_regions_.scalar_flux_final(se);
346,400✔
1111
  }
1112
}
425✔
1113

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