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

openmc-dev / openmc / 10586562087

27 Aug 2024 10:05PM UTC coverage: 84.707% (-0.2%) from 84.9%
10586562087

Pull #3112

github

web-flow
Merge f7f32bf18 into 5bc04b5d7
Pull Request #3112: Revamp CI with dependency and Python caching for efficient installs

49553 of 58499 relevant lines covered (84.71%)

34324762.08 hits per line

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

80.56
/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

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

47
  // Initialize cell-wise arrays
48
  lock_.resize(n_source_regions_);
323✔
49
  material_.resize(n_source_regions_);
323✔
50
  position_recorded_.assign(n_source_regions_, 0);
323✔
51
  position_.resize(n_source_regions_);
323✔
52
  volume_.assign(n_source_regions_, 0.0);
323✔
53
  volume_t_.assign(n_source_regions_, 0.0);
323✔
54
  volume_naive_.assign(n_source_regions_, 0.0);
323✔
55

56
  // Initialize element-wise arrays
57
  scalar_flux_new_.assign(n_source_elements_, 0.0);
323✔
58
  scalar_flux_final_.assign(n_source_elements_, 0.0);
323✔
59
  source_.resize(n_source_elements_);
323✔
60

61
  tally_task_.resize(n_source_elements_);
323✔
62
  volume_task_.resize(n_source_regions_);
323✔
63

64
  if (settings::run_mode == RunMode::EIGENVALUE) {
323✔
65
    // If in eigenvalue mode, set starting flux to guess of unity
66
    scalar_flux_old_.assign(n_source_elements_, 1.0);
68✔
67
  } else {
68
    // If in fixed source mode, set starting flux to guess of zero
69
    // and initialize external source arrays
70
    scalar_flux_old_.assign(n_source_elements_, 0.0);
255✔
71
    external_source_.assign(n_source_elements_, 0.0);
255✔
72
    external_source_present_.assign(n_source_regions_, false);
255✔
73
  }
74

75
  // Initialize material array
76
  int64_t source_region_id = 0;
323✔
77
  for (int i = 0; i < model::cells.size(); i++) {
2,975✔
78
    Cell& cell = *model::cells[i];
2,652✔
79
    if (cell.type_ == Fill::MATERIAL) {
2,652✔
80
      for (int j = 0; j < cell.n_instances_; j++) {
416,177✔
81
        material_[source_region_id++] = cell.material(j);
414,868✔
82
      }
83
    }
84
  }
85

86
  // Sanity check
87
  if (source_region_id != n_source_regions_) {
323✔
88
    fatal_error("Unexpected number of source regions");
×
89
  }
90

91
  // Initialize tally volumes
92
  if (volume_normalized_flux_tallies_) {
323✔
93
    tally_volumes_.resize(model::tallies.size());
255✔
94
    for (int i = 0; i < model::tallies.size(); i++) {
918✔
95
      //  Get the shape of the 3D result tensor
96
      auto shape = model::tallies[i]->results().shape();
663✔
97

98
      // Create a new 2D tensor with the same size as the first
99
      // two dimensions of the 3D tensor
100
      tally_volumes_[i] =
663✔
101
        xt::xtensor<double, 2>::from_shape({shape[0], shape[1]});
1,326✔
102
    }
103
  }
104

105
  // Compute simulation domain volume based on ray source
106
  auto* is = dynamic_cast<IndependentSource*>(RandomRay::ray_source_.get());
323✔
107
  SpatialDistribution* space_dist = is->space();
323✔
108
  SpatialBox* sb = dynamic_cast<SpatialBox*>(space_dist);
323✔
109
  Position dims = sb->upper_right() - sb->lower_left();
323✔
110
  simulation_volume_ = dims.x * dims.y * dims.z;
323✔
111
}
323✔
112

113
void FlatSourceDomain::batch_reset()
10,710✔
114
{
115
  // Reset scalar fluxes, iteration volume tallies, and region hit flags to
116
  // zero
117
  parallel_fill<double>(scalar_flux_new_, 0.0);
10,710✔
118
  parallel_fill<double>(volume_, 0.0);
10,710✔
119
}
10,710✔
120

121
void FlatSourceDomain::accumulate_iteration_flux()
2,880✔
122
{
123
#pragma omp parallel for
1,440✔
124
  for (int64_t se = 0; se < n_source_elements_; se++) {
2,660,280✔
125
    scalar_flux_final_[se] += scalar_flux_new_[se];
2,658,840✔
126
  }
127
}
2,880✔
128

129
// Compute new estimate of scattering + fission sources in each source region
130
// based on the flux estimate from the previous iteration.
131
void FlatSourceDomain::update_neutron_source(double k_eff)
3,825✔
132
{
133
  simulation::time_update_src.start();
3,825✔
134

135
  double inverse_k_eff = 1.0 / k_eff;
3,825✔
136

137
  // Temperature and angle indices, if using multiple temperature
138
  // data sets and/or anisotropic data sets.
139
  // TODO: Currently assumes we are only using single temp/single angle data.
140
  const int t = 0;
3,825✔
141
  const int a = 0;
3,825✔
142

143
  // Add scattering source
144
#pragma omp parallel for
2,025✔
145
  for (int sr = 0; sr < n_source_regions_; sr++) {
1,516,200✔
146
    int material = material_[sr];
1,514,400✔
147

148
    for (int e_out = 0; e_out < negroups_; e_out++) {
5,233,920✔
149
      double sigma_t = data::mg.macro_xs_[material].get_xs(
3,719,520✔
150
        MgxsType::TOTAL, e_out, nullptr, nullptr, nullptr, t, a);
151
      double scatter_source = 0.0f;
3,719,520✔
152

153
      for (int e_in = 0; e_in < negroups_; e_in++) {
22,874,880✔
154
        double scalar_flux = scalar_flux_old_[sr * negroups_ + e_in];
19,155,360✔
155

156
        double sigma_s = data::mg.macro_xs_[material].get_xs(
19,155,360✔
157
          MgxsType::NU_SCATTER, e_in, &e_out, nullptr, nullptr, t, a);
158
        scatter_source += sigma_s * scalar_flux;
19,155,360✔
159
      }
160

161
      source_[sr * negroups_ + e_out] = scatter_source / sigma_t;
3,719,520✔
162
    }
163
  }
164

165
  // Add fission source
166
#pragma omp parallel for
2,025✔
167
  for (int sr = 0; sr < n_source_regions_; sr++) {
1,516,200✔
168
    int material = material_[sr];
1,514,400✔
169

170
    for (int e_out = 0; e_out < negroups_; e_out++) {
5,233,920✔
171
      double sigma_t = data::mg.macro_xs_[material].get_xs(
3,719,520✔
172
        MgxsType::TOTAL, e_out, nullptr, nullptr, nullptr, t, a);
173
      double fission_source = 0.0f;
3,719,520✔
174

175
      for (int e_in = 0; e_in < negroups_; e_in++) {
22,874,880✔
176
        double scalar_flux = scalar_flux_old_[sr * negroups_ + e_in];
19,155,360✔
177
        double nu_sigma_f = data::mg.macro_xs_[material].get_xs(
19,155,360✔
178
          MgxsType::NU_FISSION, e_in, nullptr, nullptr, nullptr, t, a);
179
        double chi = data::mg.macro_xs_[material].get_xs(
19,155,360✔
180
          MgxsType::CHI_PROMPT, e_in, &e_out, nullptr, nullptr, t, a);
181
        fission_source += nu_sigma_f * scalar_flux * chi;
19,155,360✔
182
      }
183
      source_[sr * negroups_ + e_out] +=
3,719,520✔
184
        fission_source * inverse_k_eff / sigma_t;
3,719,520✔
185
    }
186
  }
187

188
  // Add external source if in fixed source mode
189
  if (settings::run_mode == RunMode::FIXED_SOURCE) {
3,825✔
190
#pragma omp parallel for
1,845✔
191
    for (int se = 0; se < n_source_elements_; se++) {
3,543,560✔
192
      source_[se] += external_source_[se];
3,541,920✔
193
    }
194
  }
195

196
  simulation::time_update_src.stop();
3,825✔
197
}
3,825✔
198

199
// Normalizes flux and updates simulation-averaged volume estimate
200
void FlatSourceDomain::normalize_scalar_flux_and_volumes(
3,825✔
201
  double total_active_distance_per_iteration)
202
{
203
  double normalization_factor = 1.0 / total_active_distance_per_iteration;
3,825✔
204
  double volume_normalization_factor =
3,825✔
205
    1.0 / (total_active_distance_per_iteration * simulation::current_batch);
3,825✔
206

207
// Normalize scalar flux to total distance travelled by all rays this iteration
208
#pragma omp parallel for
2,025✔
209
  for (int64_t e = 0; e < scalar_flux_new_.size(); e++) {
3,721,320✔
210
    scalar_flux_new_[e] *= normalization_factor;
3,719,520✔
211
  }
212

213
// Accumulate cell-wise ray length tallies collected this iteration, then
214
// update the simulation-averaged cell-wise volume estimates
215
#pragma omp parallel for
2,025✔
216
  for (int64_t sr = 0; sr < n_source_regions_; sr++) {
1,516,200✔
217
    volume_t_[sr] += volume_[sr];
1,514,400✔
218
    volume_naive_[sr] = volume_[sr] * normalization_factor;
1,514,400✔
219
    volume_[sr] = volume_t_[sr] * volume_normalization_factor;
1,514,400✔
220
  }
221
}
3,825✔
222

223
void FlatSourceDomain::set_flux_to_flux_plus_source(
7,903,946✔
224
  int64_t idx, double volume, int material, int g)
225
{
226
  // Temperature and angle indices, if using multiple temperature
227
  // data sets and/or anisotropic data sets.
228
  // TODO: Currently assumes we are only using single temp/single
229
  // angle data.
230
  const int t = 0;
7,903,946✔
231
  const int a = 0;
7,903,946✔
232

233
  double sigma_t = data::mg.macro_xs_[material].get_xs(
7,903,946✔
234
    MgxsType::TOTAL, g, nullptr, nullptr, nullptr, t, a);
235

236
  scalar_flux_new_[idx] /= (sigma_t * volume);
7,903,946✔
237
  scalar_flux_new_[idx] += source_[idx];
7,903,946✔
238
}
7,903,946✔
239

240
void FlatSourceDomain::set_flux_to_old_flux(int64_t idx)
×
241
{
242
  scalar_flux_new_[idx] = scalar_flux_old_[idx];
×
243
}
244

245
void FlatSourceDomain::set_flux_to_source(int64_t idx)
34✔
246
{
247
  scalar_flux_new_[idx] = source_[idx];
34✔
248
}
34✔
249

250
// Combine transport flux contributions and flat source contributions from the
251
// previous iteration to generate this iteration's estimate of scalar flux.
252
int64_t FlatSourceDomain::add_source_to_scalar_flux()
10,710✔
253
{
254
  int64_t n_hits = 0;
10,710✔
255

256
#pragma omp parallel for reduction(+ : n_hits)
5,670✔
257
  for (int sr = 0; sr < n_source_regions_; sr++) {
4,788,400✔
258

259
    double volume_simulation_avg = volume_[sr];
4,783,360✔
260
    double volume_iteration = volume_naive_[sr];
4,783,360✔
261

262
    // Increment the number of hits if cell was hit this iteration
263
    if (volume_iteration) {
4,783,360✔
264
      n_hits++;
4,783,344✔
265
    }
266

267
    // Check if an external source is present in this source region
268
    bool external_source_present =
269
      external_source_present_.size() && external_source_present_[sr];
4,783,360✔
270

271
    // The volume treatment depends on the volume estimator type
272
    // and whether or not an external source is present in the cell.
273
    double volume;
274
    switch (volume_estimator_) {
4,783,360✔
275
    case RandomRayVolumeEstimator::NAIVE:
691,200✔
276
      volume = volume_iteration;
691,200✔
277
      break;
691,200✔
278
    case RandomRayVolumeEstimator::SIMULATION_AVERAGED:
691,200✔
279
      volume = volume_simulation_avg;
691,200✔
280
      break;
691,200✔
281
    case RandomRayVolumeEstimator::HYBRID:
3,400,960✔
282
      if (external_source_present) {
3,400,960✔
283
        volume = volume_iteration;
13,520✔
284
      } else {
285
        volume = volume_simulation_avg;
3,387,440✔
286
      }
287
      break;
3,400,960✔
288
    default:
289
      fatal_error("Invalid volume estimator type");
290
    }
291

292
    int material = material_[sr];
4,783,360✔
293
    for (int g = 0; g < negroups_; g++) {
14,796,800✔
294
      int64_t idx = (sr * negroups_) + g;
10,013,440✔
295

296
      // There are three scenarios we need to consider:
297
      if (volume_iteration > 0.0) {
10,013,440✔
298
        // 1. If the FSR was hit this iteration, then the new flux is equal to
299
        // the flat source from the previous iteration plus the contributions
300
        // from rays passing through the source region (computed during the
301
        // transport sweep)
302
        set_flux_to_flux_plus_source(idx, volume, material, g);
10,013,424✔
303
      } else if (volume_simulation_avg > 0.0) {
16✔
304
        // 2. If the FSR was not hit this iteration, but has been hit some
305
        // previous iteration, then we need to make a choice about what
306
        // to do. Naively we will usually want to set the flux to be equal
307
        // to the reduced source. However, in fixed source problems where
308
        // there is a strong external source present in the cell, and where
309
        // the cell has a very low cross section, this approximation will
310
        // cause a huge upward bias in the flux estimate of the cell (in these
311
        // conditions, the flux estimate can be orders of magnitude too large).
312
        // Thus, to avoid this bias, if any external source is present
313
        // in the cell we will use the previous iteration's flux estimate. This
314
        // injects a small degree of correlation into the simulation, but this
315
        // is going to be trivial when the miss rate is a few percent or less.
316
        if (external_source_present) {
16✔
317
          set_flux_to_old_flux(idx);
318
        } else {
319
          set_flux_to_source(idx);
16✔
320
        }
321
      }
322
      // If the FSR was not hit this iteration, and it has never been hit in
323
      // any iteration (i.e., volume is zero), then we want to set this to 0
324
      // to avoid dividing anything by a zero volume. This happens implicitly
325
      // given that the new scalar flux arrays are set to zero each iteration.
326
    }
327
  }
328

329
  // Return the number of source regions that were hit this iteration
330
  return n_hits;
10,710✔
331
}
332

333
// Generates new estimate of k_eff based on the differences between this
334
// iteration's estimate of the scalar flux and the last iteration's estimate.
335
double FlatSourceDomain::compute_k_eff(double k_eff_old) const
1,700✔
336
{
337
  double fission_rate_old = 0;
1,700✔
338
  double fission_rate_new = 0;
1,700✔
339

340
  // Temperature and angle indices, if using multiple temperature
341
  // data sets and/or anisotropic data sets.
342
  // TODO: Currently assumes we are only using single temp/single
343
  // angle data.
344
  const int t = 0;
1,700✔
345
  const int a = 0;
1,700✔
346

347
  // Vector for gathering fission source terms for Shannon entropy calculation
348
  vector<float> p(n_source_regions_, 0.0f);
1,700✔
349

350
#pragma omp parallel for reduction(+ : fission_rate_old, fission_rate_new)
900✔
351
  for (int sr = 0; sr < n_source_regions_; sr++) {
217,440✔
352

353
    // If simulation averaged volume is zero, don't include this cell
354
    double volume = volume_[sr];
216,640✔
355
    if (volume == 0.0) {
216,640✔
356
      continue;
357
    }
358

359
    int material = material_[sr];
216,640✔
360

361
    double sr_fission_source_old = 0;
216,640✔
362
    double sr_fission_source_new = 0;
216,640✔
363

364
    for (int g = 0; g < negroups_; g++) {
1,487,360✔
365
      int64_t idx = (sr * negroups_) + g;
1,270,720✔
366
      double nu_sigma_f = data::mg.macro_xs_[material].get_xs(
1,270,720✔
367
        MgxsType::NU_FISSION, g, nullptr, nullptr, nullptr, t, a);
368
      sr_fission_source_old += nu_sigma_f * scalar_flux_old_[idx];
1,270,720✔
369
      sr_fission_source_new += nu_sigma_f * scalar_flux_new_[idx];
1,270,720✔
370
    }
371

372
    // Compute total fission rates in FSR
373
    sr_fission_source_old *= volume;
216,640✔
374
    sr_fission_source_new *= volume;
216,640✔
375

376
    // Accumulate totals
377
    fission_rate_old += sr_fission_source_old;
216,640✔
378
    fission_rate_new += sr_fission_source_new;
216,640✔
379

380
    // Store total fission rate in the FSR for Shannon calculation
381
    p[sr] = sr_fission_source_new;
216,640✔
382
  }
383

384
  double k_eff_new = k_eff_old * (fission_rate_new / fission_rate_old);
1,700✔
385

386
  double H = 0.0;
1,700✔
387
  // defining an inverse sum for better performance
388
  double inverse_sum = 1 / fission_rate_new;
1,700✔
389

390
#pragma omp parallel for reduction(+ : H)
900✔
391
  for (int sr = 0; sr < n_source_regions_; sr++) {
217,440✔
392
    // Only if FSR has non-negative and non-zero fission source
393
    if (p[sr] > 0.0f) {
216,640✔
394
      // Normalize to total weight of bank sites. p_i for better performance
395
      float p_i = p[sr] * inverse_sum;
92,800✔
396
      // Sum values to obtain Shannon entropy.
397
      H -= p_i * std::log2(p_i);
92,800✔
398
    }
399
  }
400

401
  // Adds entropy value to shared entropy vector in openmc namespace.
402
  simulation::entropy.push_back(H);
1,700✔
403

404
  return k_eff_new;
1,700✔
405
}
1,700✔
406

407
// This function is responsible for generating a mapping between random
408
// ray flat source regions (cell instances) and tally bins. The mapping
409
// takes the form of a "TallyTask" object, which accounts for one single
410
// score being applied to a single tally. Thus, a single source region
411
// may have anywhere from zero to many tally tasks associated with it ---
412
// meaning that the global "tally_task" data structure is in 2D. The outer
413
// dimension corresponds to the source element (i.e., each entry corresponds
414
// to a specific energy group within a specific source region), and the
415
// inner dimension corresponds to the tallying task itself. Mechanically,
416
// the mapping between FSRs and spatial filters is done by considering
417
// the location of a single known ray midpoint that passed through the
418
// FSR. I.e., during transport, the first ray to pass through a given FSR
419
// will write down its midpoint for use with this function. This is a cheap
420
// and easy way of mapping FSRs to spatial tally filters, but comes with
421
// the downside of adding the restriction that spatial tally filters must
422
// share boundaries with the physical geometry of the simulation (so as
423
// not to subdivide any FSR). It is acceptable for a spatial tally region
424
// to contain multiple FSRs, but not the other way around.
425

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

430
// Besides generating the mapping structure, this function also keeps track
431
// of whether or not all flat source regions have been hit yet. This is
432
// required, as there is no guarantee that all flat source regions will
433
// be hit every iteration, such that in the first few iterations some FSRs
434
// may not have a known position within them yet to facilitate mapping to
435
// spatial tally filters. However, after several iterations, if all FSRs
436
// have been hit and have had a tally map generated, then this status will
437
// be passed back to the caller to alert them that this function doesn't
438
// need to be called for the remainder of the simulation.
439

440
void FlatSourceDomain::convert_source_regions_to_tallies()
228✔
441
{
442
  openmc::simulation::time_tallies.start();
228✔
443

444
  // Tracks if we've generated a mapping yet for all source regions.
445
  bool all_source_regions_mapped = true;
228✔
446

447
// Attempt to generate mapping for all source regions
448
#pragma omp parallel for
114✔
449
  for (int sr = 0; sr < n_source_regions_; sr++) {
146,538✔
450

451
    // If this source region has not been hit by a ray yet, then
452
    // we aren't going to be able to map it, so skip it.
453
    if (!position_recorded_[sr]) {
146,424✔
454
      all_source_regions_mapped = false;
455
      continue;
456
    }
457

458
    // A particle located at the recorded midpoint of a ray
459
    // crossing through this source region is used to estabilish
460
    // the spatial location of the source region
461
    Particle p;
146,424✔
462
    p.r() = position_[sr];
146,424✔
463
    p.r_last() = position_[sr];
146,424✔
464
    bool found = exhaustive_find_cell(p);
146,424✔
465

466
    // Loop over energy groups (so as to support energy filters)
467
    for (int g = 0; g < negroups_; g++) {
344,256✔
468

469
      // Set particle to the current energy
470
      p.g() = g;
197,832✔
471
      p.g_last() = g;
197,832✔
472
      p.E() = data::mg.energy_bin_avg_[p.g()];
197,832✔
473
      p.E_last() = p.E();
197,832✔
474

475
      int64_t source_element = sr * negroups_ + g;
197,832✔
476

477
      // If this task has already been populated, we don't need to do
478
      // it again.
479
      if (tally_task_[source_element].size() > 0) {
197,832✔
480
        continue;
481
      }
482

483
      // Loop over all active tallies. This logic is essentially identical
484
      // to what happens when scanning for applicable tallies during
485
      // MC transport.
486
      for (auto i_tally : model::active_tallies) {
662,160✔
487
        Tally& tally {*model::tallies[i_tally]};
464,328✔
488

489
        // Initialize an iterator over valid filter bin combinations.
490
        // If there are no valid combinations, use a continue statement
491
        // to ensure we skip the assume_separate break below.
492
        auto filter_iter = FilterBinIter(tally, p);
464,328✔
493
        auto end = FilterBinIter(tally, true, &p.filter_matches());
464,328✔
494
        if (filter_iter == end)
464,328✔
495
          continue;
269,568✔
496

497
        // Loop over filter bins.
498
        for (; filter_iter != end; ++filter_iter) {
389,520✔
499
          auto filter_index = filter_iter.index_;
194,760✔
500
          auto filter_weight = filter_iter.weight_;
194,760✔
501

502
          // Loop over scores
503
          for (auto score_index = 0; score_index < tally.scores_.size();
509,472✔
504
               score_index++) {
505
            auto score_bin = tally.scores_[score_index];
314,712✔
506
            // If a valid tally, filter, and score combination has been found,
507
            // then add it to the list of tally tasks for this source element.
508
            TallyTask task(i_tally, filter_index, score_index, score_bin);
314,712✔
509
            tally_task_[source_element].push_back(task);
314,712✔
510

511
            // Also add this task to the list of volume tasks for this source
512
            // region.
513
            volume_task_[sr].insert(task);
314,712✔
514
          }
515
        }
516
      }
517
      // Reset all the filter matches for the next tally event.
518
      for (auto& match : p.filter_matches())
722,136✔
519
        match.bins_present_ = false;
524,304✔
520
    }
521
  }
146,424✔
522
  openmc::simulation::time_tallies.stop();
228✔
523

524
  mapped_all_tallies_ = all_source_regions_mapped;
228✔
525
}
228✔
526

527
// Set the volume accumulators to zero for all tallies
528
void FlatSourceDomain::reset_tally_volumes()
2,880✔
529
{
530
  if (volume_normalized_flux_tallies_) {
2,880✔
531
#pragma omp parallel for
1,080✔
532
    for (int i = 0; i < tally_volumes_.size(); i++) {
3,780✔
533
      auto& tensor = tally_volumes_[i];
2,700✔
534
      tensor.fill(0.0); // Set all elements of the tensor to 0.0
2,700✔
535
    }
536
  }
537
}
2,880✔
538

539
// In fixed source mode, due to the way that volumetric fixed sources are
540
// converted and applied as volumetric sources in one or more source regions,
541
// we need to perform an additional normalization step to ensure that the
542
// reported scalar fluxes are in units per source neutron. This allows for
543
// direct comparison of reported tallies to Monte Carlo flux results.
544
// This factor needs to be computed at each iteration, as it is based on the
545
// volume estimate of each FSR, which improves over the course of the
546
// simulation
547
double FlatSourceDomain::compute_fixed_source_normalization_factor() const
2,880✔
548
{
549
  // If we are not in fixed source mode, then there are no external sources
550
  // so no normalization is needed.
551
  if (settings::run_mode != RunMode::FIXED_SOURCE) {
2,880✔
552
    return 1.0;
600✔
553
  }
554

555
  // Step 1 is to sum over all source regions and energy groups to get the
556
  // total external source strength in the simulation.
557
  double simulation_external_source_strength = 0.0;
2,280✔
558
#pragma omp parallel for reduction(+ : simulation_external_source_strength)
1,140✔
559
  for (int sr = 0; sr < n_source_regions_; sr++) {
1,557,060✔
560
    int material = material_[sr];
1,555,920✔
561
    double volume = volume_[sr] * simulation_volume_;
1,555,920✔
562
    for (int e = 0; e < negroups_; e++) {
3,738,240✔
563
      // Temperature and angle indices, if using multiple temperature
564
      // data sets and/or anisotropic data sets.
565
      // TODO: Currently assumes we are only using single temp/single
566
      // angle data.
567
      const int t = 0;
2,182,320✔
568
      const int a = 0;
2,182,320✔
569
      double sigma_t = data::mg.macro_xs_[material].get_xs(
2,182,320✔
570
        MgxsType::TOTAL, e, nullptr, nullptr, nullptr, t, a);
571
      simulation_external_source_strength +=
2,182,320✔
572
        external_source_[sr * negroups_ + e] * sigma_t * volume;
2,182,320✔
573
    }
574
  }
575

576
  // Step 2 is to determine the total user-specified external source strength
577
  double user_external_source_strength = 0.0;
2,280✔
578
  for (auto& ext_source : model::external_sources) {
4,560✔
579
    user_external_source_strength += ext_source->strength();
2,280✔
580
  }
581

582
  // The correction factor is the ratio of the user-specified external source
583
  // strength to the simulation external source strength.
584
  double source_normalization_factor =
2,280✔
585
    user_external_source_strength / simulation_external_source_strength;
586

587
  return source_normalization_factor;
2,280✔
588
}
589

590
// Tallying in random ray is not done directly during transport, rather,
591
// it is done only once after each power iteration. This is made possible
592
// by way of a mapping data structure that relates spatial source regions
593
// (FSRs) to tally/filter/score combinations. The mechanism by which the
594
// mapping is done (and the limitations incurred) is documented in the
595
// "convert_source_regions_to_tallies()" function comments above. The present
596
// tally function simply traverses the mapping data structure and executes
597
// the scoring operations to OpenMC's native tally result arrays.
598

599
void FlatSourceDomain::random_ray_tally()
2,880✔
600
{
601
  openmc::simulation::time_tallies.start();
2,880✔
602

603
  // Reset our tally volumes to zero
604
  reset_tally_volumes();
2,880✔
605

606
  // Temperature and angle indices, if using multiple temperature
607
  // data sets and/or anisotropic data sets.
608
  // TODO: Currently assumes we are only using single temp/single
609
  // angle data.
610
  const int t = 0;
2,880✔
611
  const int a = 0;
2,880✔
612

613
  double source_normalization_factor =
614
    compute_fixed_source_normalization_factor();
2,880✔
615

616
// We loop over all source regions and energy groups. For each
617
// element, we check if there are any scores needed and apply
618
// them.
619
#pragma omp parallel for
1,440✔
620
  for (int sr = 0; sr < n_source_regions_; sr++) {
1,638,600✔
621
    // The fsr.volume_ is the unitless fractional simulation averaged volume
622
    // (i.e., it is the FSR's fraction of the overall simulation volume). The
623
    // simulation_volume_ is the total 3D physical volume in cm^3 of the
624
    // entire global simulation domain (as defined by the ray source box).
625
    // Thus, the FSR's true 3D spatial volume in cm^3 is found by multiplying
626
    // its fraction of the total volume by the total volume. Not important in
627
    // eigenvalue solves, but useful in fixed source solves for returning the
628
    // flux shape with a magnitude that makes sense relative to the fixed
629
    // source strength.
630
    double volume = volume_[sr] * simulation_volume_;
1,637,160✔
631

632
    double material = material_[sr];
1,637,160✔
633
    for (int g = 0; g < negroups_; g++) {
4,296,000✔
634
      int idx = sr * negroups_ + g;
2,658,840✔
635
      double flux = scalar_flux_new_[idx] * source_normalization_factor;
2,658,840✔
636

637
      // Determine numerical score value
638
      for (auto& task : tally_task_[idx]) {
7,686,240✔
639
        double score;
640
        switch (task.score_type) {
5,027,400✔
641

642
        case SCORE_FLUX:
2,643,480✔
643
          score = flux * volume;
2,643,480✔
644
          break;
2,643,480✔
645

646
        case SCORE_TOTAL:
647
          score = flux * volume *
648
                  data::mg.macro_xs_[material].get_xs(
649
                    MgxsType::TOTAL, g, NULL, NULL, NULL, t, a);
650
          break;
651

652
        case SCORE_FISSION:
1,191,960✔
653
          score = flux * volume *
2,383,920✔
654
                  data::mg.macro_xs_[material].get_xs(
1,191,960✔
655
                    MgxsType::FISSION, g, NULL, NULL, NULL, t, a);
656
          break;
1,191,960✔
657

658
        case SCORE_NU_FISSION:
1,191,960✔
659
          score = flux * volume *
2,383,920✔
660
                  data::mg.macro_xs_[material].get_xs(
1,191,960✔
661
                    MgxsType::NU_FISSION, g, NULL, NULL, NULL, t, a);
662
          break;
1,191,960✔
663

664
        case SCORE_EVENTS:
665
          score = 1.0;
666
          break;
667

668
        default:
669
          fatal_error("Invalid score specified in tallies.xml. Only flux, "
670
                      "total, fission, nu-fission, and events are supported in "
671
                      "random ray mode.");
672
          break;
673
        }
674

675
        // Apply score to the appropriate tally bin
676
        Tally& tally {*model::tallies[task.tally_idx]};
5,027,400✔
677
#pragma omp atomic
678
        tally.results_(task.filter_idx, task.score_idx, TallyResult::VALUE) +=
5,027,400✔
679
          score;
680
      }
681
    }
682

683
    // For flux tallies, the total volume of the spatial region is needed
684
    // for normalizing the flux. We store this volume in a separate tensor.
685
    // We only contribute to each volume tally bin once per FSR.
686
    if (volume_normalized_flux_tallies_) {
1,637,160✔
687
      for (const auto& task : volume_task_[sr]) {
4,248,720✔
688
        if (task.score_type == SCORE_FLUX) {
2,783,160✔
689
#pragma omp atomic
690
          tally_volumes_[task.tally_idx](task.filter_idx, task.score_idx) +=
1,860,840✔
691
            volume;
692
        }
693
      }
694
    }
695
  } // end FSR loop
696

697
  // Normalize any flux scores by the total volume of the FSRs scoring to that
698
  // bin. To do this, we loop over all tallies, and then all filter bins,
699
  // and then scores. For each score, we check the tally data structure to
700
  // see what index that score corresponds to. If that score is a flux score,
701
  // then we divide it by volume.
702
  if (volume_normalized_flux_tallies_) {
2,880✔
703
    for (int i = 0; i < model::tallies.size(); i++) {
7,560✔
704
      Tally& tally {*model::tallies[i]};
5,400✔
705
#pragma omp parallel for
2,700✔
706
      for (int bin = 0; bin < tally.n_filter_bins(); bin++) {
12,690✔
707
        for (int score_idx = 0; score_idx < tally.n_scores(); score_idx++) {
35,100✔
708
          auto score_type = tally.scores_[score_idx];
25,110✔
709
          if (score_type == SCORE_FLUX) {
25,110✔
710
            double vol = tally_volumes_[i](bin, score_idx);
9,990✔
711
            if (vol > 0.0) {
9,990✔
712
              tally.results_(bin, score_idx, TallyResult::VALUE) /= vol;
9,990✔
713
            }
714
          }
715
        }
716
      }
717
    }
718
  }
719

720
  openmc::simulation::time_tallies.stop();
2,880✔
721
}
2,880✔
722

723
void FlatSourceDomain::all_reduce_replicated_source_regions()
7,875✔
724
{
725
#ifdef OPENMC_MPI
726

727
  // If we only have 1 MPI rank, no need
728
  // to reduce anything.
729
  if (mpi::n_procs <= 1)
6,300✔
730
    return;
731

732
  simulation::time_bank_sendrecv.start();
6,300✔
733

734
  // The "position_recorded" variable needs to be allreduced (and maxed),
735
  // as whether or not a cell was hit will affect some decisions in how the
736
  // source is calculated in the next iteration so as to avoid dividing
737
  // by zero. We take the max rather than the sum as the hit values are
738
  // expected to be zero or 1.
739
  MPI_Allreduce(MPI_IN_PLACE, position_recorded_.data(), n_source_regions_,
6,300✔
740
    MPI_INT, MPI_MAX, mpi::intracomm);
741

742
  // The position variable is more complicated to reduce than the others,
743
  // as we do not want the sum of all positions in each cell, rather, we
744
  // want to just pick any single valid position. Thus, we perform a gather
745
  // and then pick the first valid position we find for all source regions
746
  // that have had a position recorded. This operation does not need to
747
  // be broadcast back to other ranks, as this value is only used for the
748
  // tally conversion operation, which is only performed on the master rank.
749
  // While this is expensive, it only needs to be done for active batches,
750
  // and only if we have not mapped all the tallies yet. Once tallies are
751
  // fully mapped, then the position vector is fully populated, so this
752
  // operation can be skipped.
753

754
  // First, we broadcast the fully mapped tally status variable so that
755
  // all ranks are on the same page
756
  int mapped_all_tallies_i = static_cast<int>(mapped_all_tallies_);
6,300✔
757
  MPI_Bcast(&mapped_all_tallies_i, 1, MPI_INT, 0, mpi::intracomm);
6,300✔
758

759
  // Then, we perform the gather of position data, if needed
760
  if (simulation::current_batch > settings::n_inactive &&
6,300✔
761
      !mapped_all_tallies_i) {
2,400✔
762

763
    // Master rank will gather results and pick valid positions
764
    if (mpi::master) {
190✔
765
      // Initialize temporary vector for receiving positions
766
      vector<vector<Position>> all_position;
95✔
767
      all_position.resize(mpi::n_procs);
95✔
768
      for (int i = 0; i < mpi::n_procs; i++) {
285✔
769
        all_position[i].resize(n_source_regions_);
190✔
770
      }
771

772
      // Copy master rank data into gathered vector for convenience
773
      all_position[0] = position_;
95✔
774

775
      // Receive all data into gather vector
776
      for (int i = 1; i < mpi::n_procs; i++) {
190✔
777
        MPI_Recv(all_position[i].data(), n_source_regions_ * 3, MPI_DOUBLE, i,
95✔
778
          0, mpi::intracomm, MPI_STATUS_IGNORE);
779
      }
780

781
      // Scan through gathered data and pick first valid cell posiiton
782
      for (int sr = 0; sr < n_source_regions_; sr++) {
122,115✔
783
        if (position_recorded_[sr] == 1) {
122,020✔
784
          for (int i = 0; i < mpi::n_procs; i++) {
122,155✔
785
            if (all_position[i][sr].x != 0.0 || all_position[i][sr].y != 0.0 ||
122,290✔
786
                all_position[i][sr].z != 0.0) {
135✔
787
              position_[sr] = all_position[i][sr];
122,020✔
788
              break;
122,020✔
789
            }
790
          }
791
        }
792
      }
793
    } else {
95✔
794
      // Other ranks just send in their data
795
      MPI_Send(position_.data(), n_source_regions_ * 3, MPI_DOUBLE, 0, 0,
95✔
796
        mpi::intracomm);
797
    }
798
  }
799

800
  // For the rest of the source region data, we simply perform an all reduce,
801
  // as these values will be needed on all ranks for transport during the
802
  // next iteration.
803
  MPI_Allreduce(MPI_IN_PLACE, volume_.data(), n_source_regions_, MPI_DOUBLE,
6,300✔
804
    MPI_SUM, mpi::intracomm);
805

806
  MPI_Allreduce(MPI_IN_PLACE, scalar_flux_new_.data(), n_source_elements_,
6,300✔
807
    MPI_DOUBLE, MPI_SUM, mpi::intracomm);
808

809
  simulation::time_bank_sendrecv.stop();
6,300✔
810
#endif
811
}
1,575✔
812

813
double FlatSourceDomain::evaluate_flux_at_point(
×
814
  Position r, int64_t sr, int g) const
815
{
816
  return scalar_flux_final_[sr * negroups_ + g] /
×
817
         (settings::n_batches - settings::n_inactive);
×
818
}
819

820
// Outputs all basic material, FSR ID, multigroup flux, and
821
// fission source data to .vtk file that can be directly
822
// loaded and displayed by Paraview. Note that .vtk binary
823
// files require big endian byte ordering, so endianness
824
// is checked and flipped if necessary.
825
void FlatSourceDomain::output_to_vtk() const
×
826
{
827
  // Rename .h5 plot filename(s) to .vtk filenames
828
  for (int p = 0; p < model::plots.size(); p++) {
×
829
    PlottableInterface* plot = model::plots[p].get();
×
830
    plot->path_plot() =
×
831
      plot->path_plot().substr(0, plot->path_plot().find_last_of('.')) + ".vtk";
×
832
  }
833

834
  // Print header information
835
  print_plot();
×
836

837
  // Outer loop over plots
838
  for (int p = 0; p < model::plots.size(); p++) {
×
839

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

843
    // Random ray plots only support voxel plots
844
    if (!openmc_plot) {
×
845
      warning(fmt::format("Plot {} is invalid plot type -- only voxel plotting "
×
846
                          "is allowed in random ray mode.",
847
        p));
848
      continue;
×
849
    } else if (openmc_plot->type_ != Plot::PlotType::voxel) {
×
850
      warning(fmt::format("Plot {} is invalid plot type -- only voxel plotting "
×
851
                          "is allowed in random ray mode.",
852
        p));
853
      continue;
×
854
    }
855

856
    int Nx = openmc_plot->pixels_[0];
×
857
    int Ny = openmc_plot->pixels_[1];
×
858
    int Nz = openmc_plot->pixels_[2];
×
859
    Position origin = openmc_plot->origin_;
×
860
    Position width = openmc_plot->width_;
×
861
    Position ll = origin - width / 2.0;
×
862
    double x_delta = width.x / Nx;
×
863
    double y_delta = width.y / Ny;
×
864
    double z_delta = width.z / Nz;
×
865
    std::string filename = openmc_plot->path_plot();
×
866

867
    // Perform sanity checks on file size
868
    uint64_t bytes = Nx * Ny * Nz * (negroups_ + 1 + 1 + 1) * sizeof(float);
×
869
    write_message(5, "Processing plot {}: {}... (Estimated size is {} MB)",
×
870
      openmc_plot->id(), filename, bytes / 1.0e6);
×
871
    if (bytes / 1.0e9 > 1.0) {
×
872
      warning("Voxel plot specification is very large (>1 GB). Plotting may be "
×
873
              "slow.");
874
    } else if (bytes / 1.0e9 > 100.0) {
×
875
      fatal_error("Voxel plot specification is too large (>100 GB). Exiting.");
×
876
    }
877

878
    // Relate voxel spatial locations to random ray source regions
879
    vector<int> voxel_indices(Nx * Ny * Nz);
×
880
    vector<Position> voxel_positions(Nx * Ny * Nz);
×
881

882
#pragma omp parallel for collapse(3)
883
    for (int z = 0; z < Nz; z++) {
884
      for (int y = 0; y < Ny; y++) {
885
        for (int x = 0; x < Nx; x++) {
886
          Position sample;
887
          sample.z = ll.z + z_delta / 2.0 + z * z_delta;
888
          sample.y = ll.y + y_delta / 2.0 + y * y_delta;
889
          sample.x = ll.x + x_delta / 2.0 + x * x_delta;
890
          Particle p;
891
          p.r() = sample;
892
          bool found = exhaustive_find_cell(p);
893
          int i_cell = p.lowest_coord().cell;
894
          int64_t source_region_idx =
895
            source_region_offsets_[i_cell] + p.cell_instance();
896
          voxel_indices[z * Ny * Nx + y * Nx + x] = source_region_idx;
897
          voxel_positions[z * Ny * Nx + y * Nx + x] = sample;
898
        }
899
      }
900
    }
901

902
    double source_normalization_factor =
903
      compute_fixed_source_normalization_factor();
×
904

905
    // Open file for writing
906
    std::FILE* plot = std::fopen(filename.c_str(), "wb");
×
907

908
    // Write vtk metadata
909
    std::fprintf(plot, "# vtk DataFile Version 2.0\n");
×
910
    std::fprintf(plot, "Dataset File\n");
×
911
    std::fprintf(plot, "BINARY\n");
×
912
    std::fprintf(plot, "DATASET STRUCTURED_POINTS\n");
×
913
    std::fprintf(plot, "DIMENSIONS %d %d %d\n", Nx, Ny, Nz);
×
914
    std::fprintf(plot, "ORIGIN 0 0 0\n");
×
915
    std::fprintf(plot, "SPACING %lf %lf %lf\n", x_delta, y_delta, z_delta);
×
916
    std::fprintf(plot, "POINT_DATA %d\n", Nx * Ny * Nz);
×
917

918
    // Plot multigroup flux data
919
    for (int g = 0; g < negroups_; g++) {
×
920
      std::fprintf(plot, "SCALARS flux_group_%d float\n", g);
×
921
      std::fprintf(plot, "LOOKUP_TABLE default\n");
×
922
      for (int i = 0; i < Nx * Ny * Nz; i++) {
×
923
        int64_t fsr = voxel_indices[i];
×
924
        int64_t source_element = fsr * negroups_ + g;
×
925
        float flux = evaluate_flux_at_point(voxel_positions[i], fsr, g);
×
926
        flux = convert_to_big_endian<float>(flux);
×
927
        std::fwrite(&flux, sizeof(float), 1, plot);
×
928
      }
929
    }
930

931
    // Plot FSRs
932
    std::fprintf(plot, "SCALARS FSRs float\n");
×
933
    std::fprintf(plot, "LOOKUP_TABLE default\n");
×
934
    for (int fsr : voxel_indices) {
×
935
      float value = future_prn(10, fsr);
×
936
      value = convert_to_big_endian<float>(value);
×
937
      std::fwrite(&value, sizeof(float), 1, plot);
×
938
    }
939

940
    // Plot Materials
941
    std::fprintf(plot, "SCALARS Materials int\n");
×
942
    std::fprintf(plot, "LOOKUP_TABLE default\n");
×
943
    for (int fsr : voxel_indices) {
×
944
      int mat = material_[fsr];
×
945
      mat = convert_to_big_endian<int>(mat);
×
946
      std::fwrite(&mat, sizeof(int), 1, plot);
×
947
    }
948

949
    // Plot fission source
950
    std::fprintf(plot, "SCALARS total_fission_source float\n");
×
951
    std::fprintf(plot, "LOOKUP_TABLE default\n");
×
952
    for (int i = 0; i < Nx * Ny * Nz; i++) {
×
953
      int64_t fsr = voxel_indices[i];
×
954

955
      float total_fission = 0.0;
×
956
      int mat = material_[fsr];
×
957
      for (int g = 0; g < negroups_; g++) {
×
958
        int64_t source_element = fsr * negroups_ + g;
×
959
        float flux = evaluate_flux_at_point(voxel_positions[i], fsr, g);
×
960
        float Sigma_f = data::mg.macro_xs_[mat].get_xs(
×
961
          MgxsType::FISSION, g, nullptr, nullptr, nullptr, 0, 0);
×
962
        total_fission += Sigma_f * flux;
×
963
      }
964
      total_fission = convert_to_big_endian<float>(total_fission);
×
965
      std::fwrite(&total_fission, sizeof(float), 1, plot);
×
966
    }
967

968
    std::fclose(plot);
×
969
  }
970
}
971

972
void FlatSourceDomain::apply_external_source_to_source_region(
1,802✔
973
  Discrete* discrete, double strength_factor, int64_t source_region)
974
{
975
  external_source_present_[source_region] = true;
1,802✔
976

977
  const auto& discrete_energies = discrete->x();
1,802✔
978
  const auto& discrete_probs = discrete->prob();
1,802✔
979

980
  for (int e = 0; e < discrete_energies.size(); e++) {
3,808✔
981
    int g = data::mg.get_group_index(discrete_energies[e]);
2,006✔
982
    external_source_[source_region * negroups_ + g] +=
2,006✔
983
      discrete_probs[e] * strength_factor;
2,006✔
984
  }
985
}
1,802✔
986

987
void FlatSourceDomain::apply_external_source_to_cell_instances(int32_t i_cell,
289✔
988
  Discrete* discrete, double strength_factor, int target_material_id,
989
  const vector<int32_t>& instances)
990
{
991
  Cell& cell = *model::cells[i_cell];
289✔
992

993
  if (cell.type_ != Fill::MATERIAL)
289✔
994
    return;
×
995

996
  for (int j : instances) {
31,331✔
997
    int cell_material_idx = cell.material(j);
31,042✔
998
    int cell_material_id = model::materials[cell_material_idx]->id();
31,042✔
999
    if (target_material_id == C_NONE ||
31,042✔
1000
        cell_material_id == target_material_id) {
1001
      int64_t source_region = source_region_offsets_[i_cell] + j;
1,802✔
1002
      apply_external_source_to_source_region(
1,802✔
1003
        discrete, strength_factor, source_region);
1004
    }
1005
  }
1006
}
1007

1008
void FlatSourceDomain::apply_external_source_to_cell_and_children(
323✔
1009
  int32_t i_cell, Discrete* discrete, double strength_factor,
1010
  int32_t target_material_id)
1011
{
1012
  Cell& cell = *model::cells[i_cell];
323✔
1013

1014
  if (cell.type_ == Fill::MATERIAL) {
323✔
1015
    vector<int> instances(cell.n_instances_);
289✔
1016
    std::iota(instances.begin(), instances.end(), 0);
289✔
1017
    apply_external_source_to_cell_instances(
289✔
1018
      i_cell, discrete, strength_factor, target_material_id, instances);
1019
  } else if (target_material_id == C_NONE) {
323✔
1020
    std::unordered_map<int32_t, vector<int32_t>> cell_instance_list =
1021
      cell.get_contained_cells(0, nullptr);
×
1022
    for (const auto& pair : cell_instance_list) {
×
1023
      int32_t i_child_cell = pair.first;
×
1024
      apply_external_source_to_cell_instances(i_child_cell, discrete,
×
1025
        strength_factor, target_material_id, pair.second);
×
1026
    }
1027
  }
1028
}
323✔
1029

1030
void FlatSourceDomain::count_external_source_regions()
255✔
1031
{
1032
  n_external_source_regions_ = 0;
255✔
1033
#pragma omp parallel for reduction(+ : n_external_source_regions_)
135✔
1034
  for (int sr = 0; sr < n_source_regions_; sr++) {
185,400✔
1035
    if (external_source_present_[sr]) {
185,280✔
1036
      n_external_source_regions_++;
848✔
1037
    }
1038
  }
1039
}
255✔
1040

1041
void FlatSourceDomain::convert_external_sources()
255✔
1042
{
1043
  // Loop over external sources
1044
  for (int es = 0; es < model::external_sources.size(); es++) {
510✔
1045
    Source* s = model::external_sources[es].get();
255✔
1046
    IndependentSource* is = dynamic_cast<IndependentSource*>(s);
255✔
1047
    Discrete* energy = dynamic_cast<Discrete*>(is->energy());
255✔
1048
    const std::unordered_set<int32_t>& domain_ids = is->domain_ids();
255✔
1049

1050
    double strength_factor = is->strength();
255✔
1051

1052
    if (is->domain_type() == Source::DomainType::MATERIAL) {
255✔
1053
      for (int32_t material_id : domain_ids) {
34✔
1054
        for (int i_cell = 0; i_cell < model::cells.size(); i_cell++) {
102✔
1055
          apply_external_source_to_cell_and_children(
85✔
1056
            i_cell, energy, strength_factor, material_id);
1057
        }
1058
      }
1059
    } else if (is->domain_type() == Source::DomainType::CELL) {
238✔
1060
      for (int32_t cell_id : domain_ids) {
34✔
1061
        int32_t i_cell = model::cell_map[cell_id];
17✔
1062
        apply_external_source_to_cell_and_children(
17✔
1063
          i_cell, energy, strength_factor, C_NONE);
1064
      }
1065
    } else if (is->domain_type() == Source::DomainType::UNIVERSE) {
221✔
1066
      for (int32_t universe_id : domain_ids) {
442✔
1067
        int32_t i_universe = model::universe_map[universe_id];
221✔
1068
        Universe& universe = *model::universes[i_universe];
221✔
1069
        for (int32_t i_cell : universe.cells_) {
442✔
1070
          apply_external_source_to_cell_and_children(
221✔
1071
            i_cell, energy, strength_factor, C_NONE);
1072
        }
1073
      }
1074
    }
1075
  } // End loop over external sources
1076

1077
  // Temperature and angle indices, if using multiple temperature
1078
  // data sets and/or anisotropic data sets.
1079
  // TODO: Currently assumes we are only using single temp/single angle data.
1080
  const int t = 0;
255✔
1081
  const int a = 0;
255✔
1082

1083
// Divide the fixed source term by sigma t (to save time when applying each
1084
// iteration)
1085
#pragma omp parallel for
135✔
1086
  for (int sr = 0; sr < n_source_regions_; sr++) {
185,400✔
1087
    int material = material_[sr];
185,280✔
1088
    for (int e = 0; e < negroups_; e++) {
403,968✔
1089
      double sigma_t = data::mg.macro_xs_[material].get_xs(
218,688✔
1090
        MgxsType::TOTAL, e, nullptr, nullptr, nullptr, t, a);
1091
      external_source_[sr * negroups_ + e] /= sigma_t;
218,688✔
1092
    }
1093
  }
1094
}
255✔
1095
void FlatSourceDomain::flux_swap()
10,710✔
1096
{
1097
  scalar_flux_old_.swap(scalar_flux_new_);
10,710✔
1098
}
10,710✔
1099

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