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

openmc-dev / openmc / 27644261812

16 Jun 2026 07:57PM UTC coverage: 81.298% (+0.02%) from 81.281%
27644261812

Pull #3971

github

web-flow
Merge 653e02769 into 02eb999af
Pull Request #3971: Delta tracking

18497 of 26817 branches covered (68.97%)

Branch coverage included in aggregate %.

601 of 656 new or added lines in 20 files covered. (91.62%)

3 existing lines in 2 files now uncovered.

59808 of 69502 relevant lines covered (86.05%)

49429742.75 hits per line

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

81.77
/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/majorant.h"
7
#include "openmc/material.h"
8
#include "openmc/nuclide.h"
9
#include "openmc/photon.h"
10
#include "openmc/search.h"
11
#include "openmc/settings.h"
12
#include "openmc/simulation.h"
13
#include "openmc/thermal.h"
14
#include "openmc/timer.h"
15
#include "openmc/universe.h"
16

17
namespace openmc {
18

19
//==============================================================================
20
// Global variables
21
//==============================================================================
22

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

27
} // namespace data
28

29
//==============================================================================
30
// Majorant implementation
31
//==============================================================================
32

33
Majorant::Majorant(int i_universe) : maj_universe_(i_universe)
195!
34
{
35
  // Find all materials contained in the majorant's universe. This also obtains
36
  // the maximum density multiplier applied to that material.
37
  std::set<int> unique_materials;
195!
38
  if (maj_universe_ == C_NONE || maj_universe_ >= model::universes.size()) {
195!
NEW
39
    fatal_error(fmt::format("Invalid majorant universe: {}", maj_universe_));
×
40
  }
41

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

46
    // If the cell is filled with a material, it won't have any sub-cells.
47
    if (uni_cell->type_ == Fill::MATERIAL) {
195!
48
      // Loop over instances. TODO: confirm if this is unecessary and use 0
49
      // instead?
NEW
50
      for (int instance = 0; instance < uni_cell->n_instances(); ++instance) {
×
NEW
51
        int i_material = uni_cell->material(instance);
×
52

53
        // Check to see if we've found the contained material yet. If not, add
54
        // to the set of materials discovered and add to the map of density
55
        // multipliers.
NEW
56
        if (unique_materials.count(i_material) == 0) {
×
NEW
57
          unique_materials.emplace(i_material);
×
NEW
58
          max_density_mult_[i_material] = uni_cell->density_mult(instance);
×
59
        } else {
60
          // We've found this material already. Need to take the maximum density
61
          // multiplier.
NEW
62
          max_density_mult_.at(i_material) = std::max(
×
NEW
63
            max_density_mult_.at(i_material), uni_cell->density_mult(instance));
×
64
        }
65
      }
66
    } else {
67
      // This cell is filled with a universe or lattice. Need to get the list of
68
      // cells and cell instances.
69
      const auto contained_cells = uni_cell->get_contained_cells();
195✔
70
      for (const auto& [i_con_cell, contained_instances] : contained_cells) {
585✔
71
        const auto& contained_cell = model::cells[i_con_cell];
390✔
72

73
        // Loop over contained cell instances.
74
        for (auto instance : contained_instances) {
1,950✔
75
          // Check to see if we've found the contained material instance yet. If
76
          // not, add to the set of materials discovered and add to the map of
77
          // density multipliers.
78
          int i_material = contained_cell->material(instance);
1,560!
79
          if (unique_materials.count(i_material) == 0) {
1,560✔
80
            unique_materials.emplace(i_material);
390✔
81
            max_density_mult_[i_material] =
390✔
82
              contained_cell->density_mult(instance);
390✔
83
          } else {
84
            // We've found this material already. Need to take the maximum
85
            // density multiplier for the contained instance.
86
            max_density_mult_.at(i_material) =
1,170✔
87
              std::max(max_density_mult_.at(i_material),
1,215✔
88
                contained_cell->density_mult(instance));
2,340✔
89
          }
90
        }
91
      }
92
    }
195✔
93
  }
94

95
  // Clear the contained materials vector and insert the elements from the set.
96
  for (auto i_mat : unique_materials) {
585✔
97
    contained_materials_.push_back(i_mat);
390✔
98
  }
99
}
195✔
100

101
void Majorant::compute_majorant()
195✔
102
{
103
  // Fill with zeros.
104
  xs_.resize(grid_.energy.size(), 0.0);
195✔
105

106
  std::vector<double> material_maj_xs;
195✔
107
  for (int i_material : contained_materials_) {
585✔
108
    // Populate the per-material majorant cross section.
109
    fill_material_maj_xs(i_material, max_density_mult_.at(i_material),
390✔
110
      grid_.energy, material_maj_xs);
390✔
111

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

120
void Majorant::post_process_grid(
195✔
121
  int particle_type, Nuclide::EnergyGrid& grid) const
122
{
123
  // Photons use a logarithmic energy grid.
124
  double E_min = data::energy_min[particle_type];
195✔
125
  double E_max = data::energy_max[particle_type];
195✔
126
  if (particle_type == ParticleType::photon().transport_index()) {
195✔
127
    E_min = std::log(E_min);
45✔
128
    E_max = std::log(E_max);
45✔
129
  }
130

131
  std::sort(grid.energy.begin(), grid.energy.end());
195✔
132
  auto unique_end = std::unique(grid.energy.begin(), grid.energy.end());
195✔
133
  grid.energy.resize(std::distance(grid.energy.begin(), unique_end));
195✔
134

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

142
  // Insert the minimum neutron energy at the beginning.
143
  grid.energy.insert(grid.energy.begin(), E_min);
195✔
144

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

152
  // Insert the maximum neutron energy at the end.
153
  grid.energy.insert(grid.energy.end(), E_max);
195✔
154
}
195✔
155

156
//==============================================================================
157
// NeutronMajorant implementation
158
//==============================================================================
159

160
NeutronMajorant::NeutronMajorant(int i_universe) : Majorant(i_universe) {}
150✔
161

162
double NeutronMajorant::calculate_neutron_xs(double energy) const
20,620,806✔
163
{
164
  const int i_grid = get_i_grid(energy, grid_);
20,620,806✔
165
  return interpolate_lin_1D(grid_.energy[i_grid], grid_.energy[i_grid + 1],
20,620,806✔
166
    xs_[i_grid], xs_[i_grid + 1], energy);
20,620,806✔
167
}
168

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

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

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

199
      processed_nuclides.insert(i_nuclide);
450✔
200
    }
201
  }
202

203
  // Post-process the energy grid now that all points from nuclides are
204
  // included. This sorts the energy points, removes duplicates, and removes all
205
  // energies exceeding neutron transport bounds.
206
  post_process_grid(i_neutron_, grid_);
150✔
207

208
  // Initialize the grid for fast lookups. This only applies to neutrons.
209
  grid_.init();
150✔
210
}
150✔
211

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

218
  mat_maj.resize(to_grid.size());
300✔
219

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

225
    int mat_sab_table_idx = 0;
9,510,240✔
226
    bool check_sab = (mat.thermal_tables_.size() > 0);
9,510,240✔
227

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

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

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

249
          // Increment position in thermal_tables_
250
          ++mat_sab_table_idx;
4,755,120✔
251

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

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

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

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

288
double NeutronMajorant::calculate_max_smooth_xs(
47,351,100✔
289
  int i_nuclide, double energy) const
290
{
291
  const auto& nuc = *data::nuclides[i_nuclide];
47,351,100✔
292

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

303
  return max_smooth_tot_xs;
47,351,100✔
304
}
305

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

313
  const auto& nuc = *data::nuclides[i_nuclide];
47,551,200✔
314
  if (!nuc.urr_present_) {
47,551,200✔
315
    return 0.0;
316
  }
317

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

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

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

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

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

360
    max_urr_xs = std::max(max_urr_xs, interp_urr_xs);
28,800!
361
  }
362

363
  return max_urr_xs;
11,887,800✔
364
}
365

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

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

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

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

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

431
  return max_sab_total;
200,100✔
432
}
433

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

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

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

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

466
  return i_grid;
68,172,006✔
467
}
468

469
//==============================================================================
470
// PhotonMajorant implementation
471
//==============================================================================
472

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

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

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

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

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

501
double PhotonMajorant::calculate_photon_xs(double energy) const
19,662,423✔
502
{
503
  double log_energy = std::log(energy);
19,662,423✔
504
  int i_grid = get_i_grid(log_energy, grid_.energy);
19,662,423✔
505

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

577
int PhotonMajorant::get_i_grid(
19,662,423✔
578
  double log_energy, const std::vector<double>& energy_grid) const
579
{
580
  int n_grid = energy_grid.size();
19,662,423✔
581
  int i_grid;
19,662,423✔
582
  if (log_energy <= energy_grid[0]) {
19,662,423✔
583
    i_grid = 0;
584
  } else if (log_energy > energy_grid[n_grid - 1]) {
19,606,785!
NEW
585
    i_grid = n_grid - 2;
×
586
  } else {
587
    // We use upper_bound_index here because sometimes photons are created with
588
    // energies that exactly match a grid point
589
    i_grid =
19,606,785✔
590
      upper_bound_index(energy_grid.cbegin(), energy_grid.cend(), log_energy);
19,606,785✔
591
  }
592

593
  // check for case where two energy points are the same
594
  if (energy_grid[i_grid] == energy_grid[i_grid + 1])
19,662,423!
NEW
595
    ++i_grid;
×
596

597
  return i_grid;
19,662,423✔
598
}
599

600
int PhotonMajorant::get_i_grid(
255,060✔
601
  double log_energy, const tensor::Tensor<double>& energy_grid) const
602
{
603
  int n_grid = energy_grid.size();
255,060!
604
  int i_grid;
255,060✔
605
  if (log_energy <= energy_grid[0]) {
255,060!
606
    i_grid = 0;
607
  } else if (log_energy > energy_grid(n_grid - 1)) {
255,060!
NEW
608
    i_grid = n_grid - 2;
×
609
  } else {
610
    // We use upper_bound_index here because sometimes photons are created with
611
    // energies that exactly match a grid point
612
    i_grid =
255,060✔
613
      upper_bound_index(energy_grid.cbegin(), energy_grid.cend(), log_energy);
255,060✔
614
  }
615

616
  // check for case where two energy points are the same
617
  if (energy_grid(i_grid) == energy_grid(i_grid + 1))
255,060!
NEW
618
    ++i_grid;
×
619

620
  return i_grid;
255,060✔
621
}
622

623
//! Create a majorant cross section for photons or neutrons.
624
void create_majorants()
150✔
625
{
626
  simulation::time_build_majorant.start();
150✔
627

628
  write_message("Constructing a neutron majorant cross section");
150✔
629
  data::n_majorant = std::make_unique<NeutronMajorant>(model::root_universe);
150✔
630
  data::n_majorant->compute_unionized_grid();
150✔
631
  data::n_majorant->compute_majorant();
150✔
632

633
  if (settings::photon_transport) {
150✔
634
    write_message("Constructing a photon majorant cross section");
45✔
635
    data::p_majorant = std::make_unique<PhotonMajorant>(model::root_universe);
45✔
636
    data::p_majorant->compute_unionized_grid();
45✔
637
    data::p_majorant->compute_majorant();
45✔
638
  }
639

640
  simulation::time_build_majorant.stop();
150✔
641
}
150✔
642

643
//! Reset the photon and neutron majorant cross sections.
644
void reset_majorants()
7,863✔
645
{
646
  openmc::data::n_majorant.reset(nullptr);
7,863✔
647
  openmc::data::p_majorant.reset(nullptr);
7,863✔
648
}
7,863✔
649
} // 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