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

openmc-dev / openmc / 29109839871

10 Jul 2026 05:07PM UTC coverage: 81.355% (+0.06%) from 81.295%
29109839871

Pull #3971

github

web-flow
Merge b03e8c77b into 7256d5046
Pull Request #3971: Delta tracking

18567 of 26880 branches covered (69.07%)

Branch coverage included in aggregate %.

604 of 650 new or added lines in 20 files covered. (92.92%)

59962 of 69646 relevant lines covered (86.1%)

49564145.57 hits per line

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

85.98
/src/majorant.cpp
1
#include <fmt/core.h>
2

3
#include "openmc/capi.h"
4
#include "openmc/constants.h"
5
#include "openmc/geometry.h"
6
#include "openmc/interpolate.h"
7
#include "openmc/majorant.h"
8
#include "openmc/material.h"
9
#include "openmc/nuclide.h"
10
#include "openmc/photon.h"
11
#include "openmc/search.h"
12
#include "openmc/settings.h"
13
#include "openmc/simulation.h"
14
#include "openmc/thermal.h"
15
#include "openmc/timer.h"
16
#include "openmc/universe.h"
17

18
namespace openmc {
19

20
//==============================================================================
21
// Global variables
22
//==============================================================================
23

24
namespace data {
25
std::unique_ptr<NeutronMajorant> n_majorant;
26
std::unique_ptr<PhotonMajorant> p_majorant;
27

28
} // namespace data
29

30
//==============================================================================
31
// Majorant implementation
32
//==============================================================================
33

34
Majorant::Majorant(int i_universe) : maj_universe_(i_universe)
195!
35
{
36
  if (maj_universe_ == C_NONE || maj_universe_ >= model::universes.size()) {
195!
NEW
37
    fatal_error(fmt::format("Invalid majorant universe: {}", maj_universe_));
×
38
  }
39

40
  // First, find unique cells contained in this universe.
41
  std::unordered_set<int> unique_mat_cells;
195✔
42

43
  const auto& maj_uni = model::universes[maj_universe_];
195✔
44
  for (int i_cell : maj_uni->cells_) {
390✔
45
    const auto& uni_cell = model::cells[i_cell];
195!
46

47
    // If the cell is filled with a material, it won't have any sub-cells.
48
    if (uni_cell->type_ == Fill::MATERIAL) {
195!
NEW
49
      if (unique_mat_cells.count(i_cell) == 0) {
×
50
        unique_mat_cells.emplace(i_cell);
195!
51
      }
52
    } else {
53
      // This cell is filled with a universe or lattice. Need to get the list of
54
      // cells and cell instances.
55
      const auto contained_cells = uni_cell->get_contained_cells();
195✔
56
      for (const auto& [i_con_cell, contained_instances] : contained_cells) {
585!
57
        if (unique_mat_cells.count(i_con_cell) == 0) {
390!
58
          unique_mat_cells.emplace(i_con_cell);
390✔
59
        }
60
      }
61
    }
195✔
62
  }
63

64
  // Next, find all materials contained in the majorant's universe. This also
65
  // obtains the maximum density multiplier applied to that material.
66
  std::unordered_set<int> unique_materials;
195✔
67
  for (int i_cell : unique_mat_cells) {
585✔
68
    auto& cell = model::cells[i_cell];
390✔
69

70
    for (int instance = 0; instance < cell->n_instances(); ++instance) {
1,950✔
71
      int i_material = cell->material(instance);
1,560!
72
      // Skip over void materials.
73
      if (i_material == MATERIAL_VOID) {
1,560!
NEW
74
        continue;
×
75
      }
76

77
      // Check to see if we've found the contained material yet. If not, add
78
      // to the set of materials discovered and add to the map of density
79
      // multipliers.
80
      if (unique_materials.count(i_material) == 0) {
1,560✔
81
        unique_materials.emplace(i_material);
390✔
82
        max_density_mult_[i_material] = cell->density_mult(instance);
390✔
83
      } else {
84
        // We've found this material already. Need to take the maximum density
85
        // multiplier.
86
        max_density_mult_.at(i_material) = std::max(
2,340✔
87
          max_density_mult_.at(i_material), cell->density_mult(instance));
2,340✔
88
      }
89
    }
90
  }
91

92
  // Insert the elements from the set.
93
  contained_materials_.assign(unique_materials.begin(), unique_materials.end());
195✔
94
}
390✔
95

96
void Majorant::compute_majorant()
195✔
97
{
98
  // Fill with zeros.
99
  xs_.resize(grid_.energy.size(), 0.0);
195✔
100

101
  std::vector<double> material_maj_xs;
195✔
102
  for (int i_material : contained_materials_) {
585✔
103
    // Populate the per-material majorant cross section. We pass in
104
    // 'material_maj_xs' instead of returning a vector with the per-material
105
    // majorant every time to avoid costly reallocations and copy operations,
106
    // which have a fairly large impact on the time it takes to build the
107
    // majorant. The function 'fill_material_maj_xs(...)' is responsible for
108
    // resizing 'material_maj_xs' and populating each value at a given energy
109
    // grid point.
110
    fill_material_maj_xs(i_material, max_density_mult_.at(i_material),
390✔
111
      grid_.energy, material_maj_xs);
390✔
112

113
    // Compute the full majorant by taking the max over each material cross
114
    // section.
115
    for (int i_energy = 0; i_energy < xs_.size(); ++i_energy) {
23,903,520✔
116
      xs_[i_energy] = std::max(xs_[i_energy], material_maj_xs[i_energy]);
38,235,450✔
117
    }
118
  }
119
}
195✔
120

121
void Majorant::post_process_grid(Nuclide::EnergyGrid& grid) const
195✔
122
{
123
  // Fetch the minimum and maximum transport energies from the superclass.
124
  const double E_min = min_transport_energy();
195✔
125
  const double E_max = max_transport_energy();
195✔
126

127
  std::sort(grid.energy.begin(), grid.energy.end());
195✔
128
  auto unique_end = std::unique(grid.energy.begin(), grid.energy.end());
195✔
129
  grid.energy.resize(std::distance(grid.energy.begin(), unique_end));
195✔
130

131
  // Remove all values below the minimum neutron energy.
132
  auto min_it = grid.energy.begin();
195✔
133
  while (*min_it < E_min) {
195✔
134
    min_it++;
136,320✔
135
  }
136
  grid.energy.erase(grid.energy.begin(), min_it + 1);
195✔
137

138
  // Insert the minimum neutron energy at the beginning.
139
  grid.energy.insert(grid.energy.begin(), E_min);
195✔
140

141
  // Remove all values above the maximum neutron energy.
142
  auto max_it = --grid.energy.end();
195✔
143
  while (*max_it > E_max) {
195✔
144
    max_it--;
34,410✔
145
  }
146
  grid.energy.erase(max_it - 1, grid.energy.end());
195✔
147

148
  // Insert the maximum neutron energy at the end.
149
  grid.energy.insert(grid.energy.end(), E_max);
195✔
150
}
195✔
151

152
//==============================================================================
153
// NeutronMajorant implementation
154
//==============================================================================
155

156
NeutronMajorant::NeutronMajorant(int i_universe) : Majorant(i_universe) {}
150✔
157

158
double NeutronMajorant::calculate_neutron_xs(double energy) const
21,403,074✔
159
{
160
  const int i_grid = get_i_grid(energy, grid_);
21,403,074✔
161
  return interpolate_lin_lin(grid_.energy[i_grid], grid_.energy[i_grid + 1],
21,403,074✔
162
    xs_[i_grid], xs_[i_grid + 1], energy);
21,403,074✔
163
}
164

165
void NeutronMajorant::compute_unionized_grid()
150✔
166
{
167
  // This function generates a unionized cross section grid between smooth cross
168
  // sections and URR probability table grids.
169
  std::unordered_set<int> processed_nuclides;
150✔
170
  for (int i_mat : contained_materials_) {
450✔
171
    const auto& mat = model::materials[i_mat];
300✔
172
    for (auto i_nuclide : mat->nuclide_) {
900✔
173
      // Only unionize nuclides we haven't checked yet.
174
      if (processed_nuclides.count(i_nuclide) > 0) {
600✔
175
        continue;
150✔
176
      }
177

178
      const auto& nuclide = data::nuclides[i_nuclide];
450✔
179
      // ======================================================================
180
      // Unionizing the URR energy grids.
181
      if (nuclide->urr_present_ && settings::urr_ptables_on) {
450!
182
        for (const auto& nuc_urr : nuclide->urr_data_) {
300✔
183
          grid_.energy.insert(
150✔
184
            grid_.energy.end(), nuc_urr.energy_.begin(), nuc_urr.energy_.end());
150✔
185
        }
186
      }
187

188
      // ======================================================================
189
      // Unionize the smooth cross section energy grids.
190
      for (const auto& nuc_grid : nuclide->grid_) {
900✔
191
        grid_.energy.insert(
450✔
192
          grid_.energy.end(), nuc_grid.energy.begin(), nuc_grid.energy.end());
450✔
193
      }
194

195
      processed_nuclides.insert(i_nuclide);
450✔
196
    }
197
  }
198

199
  // Post-process the energy grid now that all points from nuclides are
200
  // included. This sorts the energy points, removes duplicates, and removes all
201
  // energies exceeding neutron transport bounds.
202
  post_process_grid(grid_);
150✔
203

204
  // Initialize the grid for fast lookups. This only applies to neutrons.
205
  grid_.init();
150✔
206
}
150✔
207

208
void NeutronMajorant::fill_material_maj_xs(int i_material,
300✔
209
  double max_density_mult, const std::vector<double>& to_grid,
210
  std::vector<double>& mat_maj) const
211
{
212
  const auto& mat = *model::materials[i_material];
300✔
213

214
  mat_maj.resize(to_grid.size());
300✔
215

216
#pragma omp parallel for
180✔
217
  for (int i_energy = 0; i_energy < to_grid.size(); ++i_energy) {
9,510,360✔
218
    mat_maj[i_energy] = 0.0;
9,510,240✔
219
    const double union_energy = to_grid[i_energy];
9,510,240✔
220

221
    int mat_sab_table_idx = 0;
9,510,240✔
222
    bool check_sab = (mat.thermal_tables_.size() > 0);
9,510,240✔
223

224
    for (int i = 0; i < mat.nuclide_.size(); ++i) {
28,530,720✔
225
      // ======================================================================
226
      // CHECK FOR S(A,B) TABLE
227
      int i_sab = C_NONE;
19,020,480✔
228
      double sab_frac = 0.0;
19,020,480✔
229

230
      // Check if this nuclide matches one of the S(a,b) tables specified.
231
      // This relies on thermal_tables_ being sorted by .index_nuclide
232
      if (check_sab) {
19,020,480✔
233
        const auto& sab {mat.thermal_tables_[mat_sab_table_idx]};
4,755,120!
234
        if (i == sab.index_nuclide) {
4,755,120!
235
          // Get index in sab_tables
236
          i_sab = sab.index_table;
4,755,120✔
237
          sab_frac = sab.fraction;
4,755,120✔
238

239
          // If particle energy is greater than the highest energy for the
240
          // S(a,b) table, then don't use the S(a,b) table
241
          if (union_energy > data::thermal_scatt[i_sab]->energy_max_) {
4,755,120✔
242
            i_sab = C_NONE;
4,675,080✔
243
          }
244

245
          // Increment position in thermal_tables_
246
          ++mat_sab_table_idx;
4,755,120✔
247

248
          // Don't check for S(a,b) tables if there are no more left
249
          if (mat_sab_table_idx == mat.thermal_tables_.size()) {
4,755,120!
250
            check_sab = false;
4,755,120✔
251
          }
252
        }
253
      }
254

255
      // ======================================================================
256
      // Compute the maximum smooth total cross section. This is either the
257
      // free gas cross section at energies larger than the Bragg edge, or
258
      // the bound cross section in the thermal scattering region.
259
      double micro_smooth_tot_xs = 0.0;
19,020,480✔
260
      if (i_sab >= 0) {
19,020,480✔
261
        // Thermal scattering cross sections using S(a,b) tables.
262
        micro_smooth_tot_xs = calculate_max_sab_tot_xs(
80,040✔
263
          mat.nuclide_[i], i_sab, sab_frac, union_energy);
80,040✔
264
      } else {
265
        // Free gas smooth cross section
266
        micro_smooth_tot_xs =
18,940,440✔
267
          calculate_max_smooth_xs(mat.nuclide_[i], union_energy);
18,940,440✔
268
      }
269

270
      // ======================================================================
271
      // Compute the URR cross section. This shouldn't intersect with the
272
      // S(a,b) cross section.
273
      double micro_urr_xs = calculate_max_urr_xs(
19,020,480✔
274
        mat.nuclide_[i], union_energy, micro_smooth_tot_xs);
19,020,480✔
275

276
      // ======================================================================
277
      // Accumulate the macroscopic cross section.
278
      mat_maj[i_energy] += std::max(micro_smooth_tot_xs, micro_urr_xs) *
19,026,240✔
279
                           mat.atom_density(i, max_density_mult);
19,020,480✔
280
    }
281
  }
282
}
300✔
283

284
double NeutronMajorant::calculate_max_smooth_xs(
47,351,100✔
285
  int i_nuclide, double energy) const
286
{
287
  const auto& nuc = *data::nuclides[i_nuclide];
47,351,100✔
288

289
  double max_smooth_tot_xs = 0.0;
47,351,100✔
290
  for (int i_temp = 0; i_temp < nuc.kTs_.size(); ++i_temp) {
94,702,200✔
291
    const auto& nuc_grid = nuc.grid_[i_temp];
47,351,100✔
292
    int i_grid = get_i_grid(energy, nuc_grid);
47,351,100✔
293
    auto total = nuc.xs_[i_temp].slice(openmc::tensor::all, 0);
47,351,100✔
294
    double xs = interpolate_lin_lin(nuc_grid.energy[i_grid],
47,351,100✔
295
      nuc_grid.energy[i_grid + 1], total[i_grid], total[i_grid + 1], energy);
47,351,100!
296
    max_smooth_tot_xs = std::max(max_smooth_tot_xs, xs);
94,702,200!
297
  }
47,351,100✔
298

299
  return max_smooth_tot_xs;
47,351,100✔
300
}
301

302
double NeutronMajorant::calculate_max_urr_xs(
47,551,200✔
303
  int i_nuclide, double energy, double smooth_xs) const
304
{
305
  // A tolerance on the URR check to make sure we include the URR energy grid
306
  // bounds.
307
  constexpr double URR_FUZZY_CHECK = 1e-6;
47,551,200✔
308

309
  const auto& nuc = *data::nuclides[i_nuclide];
47,551,200✔
310
  if (!nuc.urr_present_) {
47,551,200✔
311
    return 0.0;
312
  }
313

314
  double max_urr_xs = 0.0;
11,887,800✔
315
  for (const auto& urr : nuc.urr_data_) {
23,775,600✔
316
    if (!(urr.energy_in_bounds(energy - URR_FUZZY_CHECK) ||
11,887,800✔
317
          urr.energy_in_bounds(energy + URR_FUZZY_CHECK))) {
11,873,550✔
318
      continue;
11,873,400✔
319
    }
320

321
    int i_energy;
14,400✔
322
    if (energy <= urr.energy_.front()) {
14,400✔
323
      i_energy = 0;
324
    } else if (energy >= urr.energy_.back()) {
14,250✔
325
      i_energy = urr.energy_.size() - 2;
150✔
326
    } else {
327
      i_energy =
14,100✔
328
        lower_bound_index(&urr.energy_.front(), &urr.energy_.back(), energy);
14,100✔
329
    }
330

331
    // Find the maximum URR cross sections for the two bounding energy points.
332
    double max_urr_xs_E0 = 0.0;
14,400✔
333
    double max_urr_xs_E1 = 0.0;
14,400✔
334
    for (int i_cdf = 0; i_cdf < urr.n_cdf(); ++i_cdf) {
604,800!
335
      max_urr_xs_E0 =
576,000✔
336
        std::max(max_urr_xs_E0, urr.xs_values_(i_energy, i_cdf).total);
288,000!
337
      max_urr_xs_E1 =
576,000✔
338
        std::max(max_urr_xs_E1, urr.xs_values_(i_energy + 1, i_cdf).total);
576,000!
339
    }
340
    // Handle the rare case where the points could be negative.
341
    max_urr_xs_E0 = std::max(max_urr_xs_E0, 0.0);
14,400!
342
    max_urr_xs_E1 = std::max(max_urr_xs_E1, 0.0);
14,400!
343

344
    // Interpolate the bounding energy points.
345
    double interp_urr_xs = 0.0;
14,400✔
346
    if (urr.interp_ == Interpolation::lin_lin) {
14,400!
347
      interp_urr_xs = interpolate_lin_lin(urr.energy_[i_energy],
14,400✔
348
        urr.energy_[i_energy + 1], max_urr_xs_E0, max_urr_xs_E1, energy);
14,400✔
NEW
349
    } else if (urr.interp_ == Interpolation::log_log) {
×
NEW
350
      interp_urr_xs = interpolate_log_log(urr.energy_[i_energy],
×
NEW
351
        urr.energy_[i_energy + 1], max_urr_xs_E0, max_urr_xs_E1, energy);
×
352
    }
353

354
    // Multiply by the smooth cross section (after interpolation) if required.
355
    if (urr.multiply_smooth_) {
14,400!
356
      interp_urr_xs *= smooth_xs;
14,400✔
357
    }
358

359
    max_urr_xs = std::max({max_urr_xs, interp_urr_xs, smooth_xs});
14,400✔
360
  }
361

362
  return max_urr_xs;
363
}
364

365
double NeutronMajorant::calculate_max_sab_tot_xs(
200,100✔
366
  int i_nuclide, int i_sab, double sab_frac, double energy) const
367
{
368
  const auto& nuc = *data::nuclides[i_nuclide];
200,100✔
369
  const auto& thermal = *data::thermal_scatt[i_sab];
200,100✔
370

371
  // Loop over the nuclide's temperature grid to ensure we're consistent.
372
  double max_sab_total = 0.0;
200,100✔
373
  for (int i_nuc_temp = 0; i_nuc_temp < nuc.kTs_.size(); ++i_nuc_temp) {
400,200✔
374
    double nuc_kT = nuc.kTs_[i_nuc_temp] * nuc.kTs_[i_nuc_temp];
200,100!
375

376
    // Compute the elastic and inelastic scattering cross sections. The S(a,b)
377
    // cross sections are interpolated to match the nuclide temperature point.
378
    double thermal_elastic;
200,100✔
379
    double thermal_inelastic;
200,100✔
380
    const auto& tkTs = thermal.kTs_;
200,100✔
381
    if (tkTs.size() > 1) {
200,100!
NEW
382
      if (nuc_kT < tkTs.front()) {
×
NEW
383
        thermal.data_.front().calculate_xs(
×
384
          energy, &thermal_elastic, &thermal_inelastic);
NEW
385
      } else if (nuc_kT > tkTs.back()) {
×
NEW
386
        thermal.data_.back().calculate_xs(
×
387
          energy, &thermal_elastic, &thermal_inelastic);
388
      } else {
389
        // Find temperatures that bound the actual temperature
390
        int i_sab_temp = 0;
391
        while (
NEW
392
          tkTs[i_sab_temp + 1] < nuc_kT && i_sab_temp + 1 < tkTs.size() - 1) {
×
393
          ++i_sab_temp;
394
        }
395
        // Interpolate the scattering cross sections to the nuclide temperature
396
        // grid point.
NEW
397
        double T0_elastic, T1_elastic, T0_inelastic, T1_inelastic;
×
NEW
398
        thermal.data_[i_sab_temp].calculate_xs(
×
399
          energy, &T0_elastic, &T0_inelastic);
NEW
400
        thermal.data_[i_sab_temp + 1].calculate_xs(
×
401
          energy, &T1_elastic, &T1_inelastic);
NEW
402
        thermal_elastic = interpolate_lin_lin(tkTs[i_sab_temp],
×
NEW
403
          tkTs[i_sab_temp + 1], T0_elastic, T1_elastic, nuc_kT);
×
NEW
404
        thermal_inelastic = interpolate_lin_lin(tkTs[i_sab_temp],
×
NEW
405
          tkTs[i_sab_temp + 1], T0_inelastic, T1_inelastic, nuc_kT);
×
406
      }
407
    } else {
408
      thermal.data_[0].calculate_xs(
200,100✔
409
        energy, &thermal_elastic, &thermal_inelastic);
410
    }
411

412
    // Compute the free gas total and elastic cross sections interpolated on the
413
    // majorant grid.
414
    const auto& nuc_grid = nuc.grid_[i_nuc_temp];
200,100✔
415
    int i_grid = get_i_grid(energy, nuc_grid);
200,100✔
416
    const auto& free_tot = nuc.xs_[i_nuc_temp].slice(openmc::tensor::all, 0);
200,100✔
417
    const auto& free_ela = nuc.reactions_[0]->xs_[i_nuc_temp].value;
200,100!
418
    double tot_xs =
200,100✔
419
      interpolate_lin_lin(nuc_grid.energy[i_grid], nuc_grid.energy[i_grid + 1],
200,100!
420
        free_tot[i_grid], free_tot[i_grid + 1], energy);
200,100!
421
    double ela_xs =
200,100✔
422
      interpolate_lin_lin(nuc_grid.energy[i_grid], nuc_grid.energy[i_grid + 1],
200,100✔
423
        free_ela[i_grid], free_ela[i_grid + 1], energy);
200,100✔
424

425
    double thermal_xs = sab_frac * (thermal_elastic + thermal_inelastic);
200,100✔
426
    double sab_corrected_total = tot_xs + thermal_xs - sab_frac * ela_xs;
200,100✔
427
    max_sab_total = std::max(sab_corrected_total, max_sab_total);
200,100!
428
  }
200,100✔
429

430
  return max_sab_total;
200,100✔
431
}
432

433
int NeutronMajorant::get_i_grid(
68,954,274✔
434
  double energy, const Nuclide::EnergyGrid& grid) const
435
{
436
  // Find energy index on energy grid
437
  int i_log_union =
68,954,274✔
438
    std::log(energy / data::energy_min[i_neutron_]) / simulation::log_spacing;
68,954,274✔
439

440
  int i_grid;
68,954,274✔
441
  if (energy <= grid.energy.front()) {
68,954,274✔
442
    i_grid = 0;
443
  } else if (energy >= grid.energy.back()) {
68,953,674✔
444
    i_grid = grid.energy.size() - 2;
300✔
445
  } else {
446
    // Determine bounding indices based on which equal log-spaced
447
    // interval the energy is in
448
    int i_low = grid.grid_index[i_log_union];
68,953,374!
449
    int i_high = grid.grid_index[i_log_union + 1] + 1;
68,953,374✔
450

451
    // This catches the very rare case where floating point comparisons fail.
452
    if (i_low >= grid.energy.size() || i_high >= grid.energy.size()) {
68,953,374!
NEW
453
      i_grid = grid.energy.size() - 2;
×
454
    } else {
455
      // Perform binary search over reduced range
456
      i_grid = i_low + lower_bound_index(
68,953,374✔
457
                         &grid.energy[i_low], &grid.energy[i_high], energy);
68,953,374✔
458
    }
459
  }
460

461
  // check for rare case where two energy points are the same
462
  if (grid.energy[i_grid] == grid.energy[i_grid + 1])
68,954,274!
NEW
463
    ++i_grid;
×
464

465
  return i_grid;
68,954,274✔
466
}
467

468
//==============================================================================
469
// PhotonMajorant implementation
470
//==============================================================================
471

472
PhotonMajorant::PhotonMajorant(int i_universe) : Majorant(i_universe) {}
45✔
473

474
void PhotonMajorant::compute_unionized_grid()
45✔
475
{
476
  // This function generates a unionized cross section grid for all elements.
477
  std::unordered_set<int> processed_elements;
45✔
478
  for (int i_mat : contained_materials_) {
135✔
479
    const auto& mat = model::materials[i_mat];
90✔
480
    for (int i = 0; i < mat->nuclide_.size(); ++i) {
270✔
481
      // Only unionize elements we haven't checked yet.
482
      if (processed_elements.count(mat->element_[i]) > 0) {
180✔
483
        continue;
45✔
484
      }
485

486
      const auto& element = data::elements[mat->element_[i]];
135✔
487
      grid_.energy.insert(
135✔
488
        grid_.energy.end(), element->energy_.begin(), element->energy_.end());
135✔
489

490
      processed_elements.insert(mat->element_[i]);
135✔
491
    }
492
  }
493

494
  // Post-process the energy grid now that all points from photon interactions
495
  // are included. This sorts the energy points, removes duplicates, and removes
496
  // all energies exceeding photon transport bounds.
497
  post_process_grid(grid_);
45✔
498
}
45✔
499

500
double PhotonMajorant::calculate_photon_xs(double energy) const
19,625,903✔
501
{
502
  double log_energy = std::log(energy);
19,625,903✔
503
  int i_grid = get_i_grid<std::vector<double>>(log_energy, grid_.energy);
19,625,903✔
504

505
  // calculate interpolation factor
506
  double f = (log_energy - grid_.energy[i_grid]) /
19,625,903✔
507
             (grid_.energy[i_grid + 1] - grid_.energy[i_grid]);
19,625,903✔
508

509
  // interpolate the total cross section
510
  return std::exp(xs_[i_grid] + f * (xs_[i_grid + 1] - xs_[i_grid]));
19,625,903✔
511
}
512

513
void PhotonMajorant::fill_material_maj_xs(int i_material,
90✔
514
  double max_density_mult, const std::vector<double>& to_grid,
515
  std::vector<double>& mat_maj) const
516
{
517
  const auto& mat = *model::materials[i_material];
90✔
518

519
  mat_maj.resize(to_grid.size());
90✔
520

521
#pragma omp parallel for
54✔
522
  for (int i_energy = 0; i_energy < to_grid.size(); ++i_energy) {
51,048✔
523
    mat_maj[i_energy] = 0.0;
51,012✔
524
    const double union_log_energy = to_grid[i_energy];
51,012✔
525

526
    for (int i = 0; i < mat.nuclide_.size(); ++i) {
153,036✔
527
      const int i_element = mat.element_[i];
102,024✔
528

529
      mat_maj[i_energy] += calculate_elem_tot_xs(i_element, union_log_energy) *
102,024✔
530
                           mat.atom_density(i, max_density_mult);
102,024✔
531
    }
532
    mat_maj[i_energy] = std::log(mat_maj[i_energy]);
51,012✔
533
  }
534
}
90✔
535

536
double PhotonMajorant::calculate_elem_tot_xs(
255,060✔
537
  int i_element, double log_energy) const
538
{
539
  const auto& elem = *data::elements[i_element];
255,060✔
540
  int i_grid = get_i_grid<tensor::Tensor<double>>(log_energy, elem.energy_);
255,060✔
541

542
  // calculate interpolation factor
543
  double f = (log_energy - elem.energy_(i_grid)) /
255,060✔
544
             (elem.energy_(i_grid + 1) - elem.energy_(i_grid));
255,060✔
545

546
  // Calculate microscopic coherent cross section
547
  double coherent =
255,060✔
548
    std::exp(elem.coherent_(i_grid) +
510,120✔
549
             f * (elem.coherent_(i_grid + 1) - elem.coherent_(i_grid)));
255,060✔
550

551
  // Calculate microscopic incoherent cross section
552
  double incoherent =
255,060✔
553
    std::exp(elem.incoherent_(i_grid) +
255,060✔
554
             f * (elem.incoherent_(i_grid + 1) - elem.incoherent_(i_grid)));
255,060✔
555

556
  // Calculate microscopic photoelectric cross section
557
  double photoelectric = 0.0;
255,060✔
558
  tensor::View<const double> xs_lower = elem.cross_sections_.slice(i_grid);
255,060✔
559
  tensor::View<const double> xs_upper = elem.cross_sections_.slice(i_grid + 1);
255,060✔
560

561
  for (int i = 0; i < xs_upper.size(); ++i)
5,356,260✔
562
    if (xs_lower(i) != 0)
2,423,070✔
563
      photoelectric += std::exp(xs_lower(i) + f * (xs_upper(i) - xs_lower(i)));
2,271,060✔
564

565
  // Calculate microscopic pair production cross section
566
  double pair_production =
255,060✔
567
    std::exp(elem.pair_production_total_(i_grid) +
255,060✔
568
             f * (elem.pair_production_total_(i_grid + 1) -
255,060✔
569
                   elem.pair_production_total_(i_grid)));
255,060✔
570

571
  // Calculate microscopic total cross section
572
  return coherent + incoherent + photoelectric + pair_production;
255,060✔
573
}
510,120✔
574

575
//! Create a majorant cross section for photons or neutrons.
576
void create_majorants()
150✔
577
{
578
  simulation::time_build_majorant.start();
150✔
579

580
  write_message("Constructing a neutron majorant cross section");
150✔
581
  data::n_majorant = std::make_unique<NeutronMajorant>(model::root_universe);
150✔
582
  data::n_majorant->compute_unionized_grid();
150✔
583
  data::n_majorant->compute_majorant();
150✔
584

585
  if (settings::photon_transport) {
150✔
586
    write_message("Constructing a photon majorant cross section");
45✔
587
    data::p_majorant = std::make_unique<PhotonMajorant>(model::root_universe);
45✔
588
    data::p_majorant->compute_unionized_grid();
45✔
589
    data::p_majorant->compute_majorant();
45✔
590
  }
591

592
  simulation::time_build_majorant.stop();
150✔
593
}
150✔
594

595
//! Reset the photon and neutron majorant cross sections.
596
void reset_majorants()
7,977✔
597
{
598
  openmc::data::n_majorant.reset(nullptr);
7,977✔
599
  openmc::data::p_majorant.reset(nullptr);
7,977✔
600
}
7,977✔
601
} // 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

© 2026 Coveralls, Inc