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

openmc-dev / openmc / 22110756672

17 Feb 2026 06:31PM UTC coverage: 81.831% (+0.1%) from 81.721%
22110756672

Pull #3813

github

web-flow
Merge 47dc7194f into 977ade79a
Pull Request #3813: Check for positive radii

17268 of 24298 branches covered (71.07%)

Branch coverage included in aggregate %.

16 of 16 new or added lines in 1 file covered. (100.0%)

1065 existing lines in 28 files now uncovered.

57520 of 67095 relevant lines covered (85.73%)

45595325.95 hits per line

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

61.16
/src/thermal.cpp
1
#include "openmc/thermal.h"
2

3
#include <algorithm> // for sort, move, min, max, find
4
#include <cmath>     // for round, sqrt, abs
5

6
#include "openmc/tensor.h"
7
#include <fmt/core.h>
8

9
#include "openmc/constants.h"
10
#include "openmc/endf.h"
11
#include "openmc/error.h"
12
#include "openmc/random_lcg.h"
13
#include "openmc/search.h"
14
#include "openmc/secondary_correlated.h"
15
#include "openmc/secondary_thermal.h"
16
#include "openmc/settings.h"
17
#include "openmc/string_utils.h"
18

19
namespace openmc {
20

21
//==============================================================================
22
// Global variables
23
//==============================================================================
24

25
namespace data {
26
std::unordered_map<std::string, int> thermal_scatt_map;
27
vector<unique_ptr<ThermalScattering>> thermal_scatt;
28
} // namespace data
29

30
//==============================================================================
31
// ThermalScattering implementation
32
//==============================================================================
33

34
ThermalScattering::ThermalScattering(
1,185✔
35
  hid_t group, const vector<double>& temperature)
1,185✔
36
{
37
  // Get name of table from group
38
  name_ = object_name(group);
1,185✔
39

40
  // Get rid of leading '/'
41
  name_ = name_.substr(1);
1,185✔
42

43
  read_attribute(group, "atomic_weight_ratio", awr_);
1,185✔
44
  read_attribute(group, "energy_max", energy_max_);
1,185✔
45
  read_attribute(group, "nuclides", nuclides_);
1,185✔
46

47
  // Read temperatures
48
  hid_t kT_group = open_group(group, "kTs");
1,185✔
49

50
  // Determine temperatures available
51
  auto dset_names = dataset_names(kT_group);
1,185✔
52
  auto n = dset_names.size();
1,185✔
53
  auto temps_available = tensor::Tensor<double>({n});
1,185✔
54
  for (int i = 0; i < dset_names.size(); ++i) {
2,370✔
55
    // Read temperature value
56
    double T;
57
    read_dataset(kT_group, dset_names[i].data(), T);
1,185✔
58
    temps_available[i] = std::round(T / K_BOLTZMANN);
1,185✔
59
  }
60
  std::sort(temps_available.begin(), temps_available.end());
1,185✔
61

62
  // Determine actual temperatures to read -- start by checking whether a
63
  // temperature range was given, in which case all temperatures in the range
64
  // are loaded irrespective of what temperatures actually appear in the model
65
  vector<int> temps_to_read;
1,185✔
66
  if (settings::temperature_range[1] > 0.0) {
1,185✔
67
    for (const auto& T : temps_available) {
120✔
68
      if (settings::temperature_range[0] <= T &&
120!
69
          T <= settings::temperature_range[1]) {
60!
70
        temps_to_read.push_back(std::round(T));
60✔
71
      }
72
    }
73
  }
74

75
  switch (settings::temperature_method) {
1,185!
76
  case TemperatureMethod::NEAREST:
1,165✔
77
    // Determine actual temperatures to read
78
    for (const auto& T : temperature) {
2,330✔
79

80
      auto i_closest = tensor::abs(temps_available - T).argmin();
1,165✔
81
      auto temp_actual = temps_available[i_closest];
1,165✔
82
      if (std::abs(temp_actual - T) < settings::temperature_tolerance) {
1,165!
83
        if (std::find(temps_to_read.begin(), temps_to_read.end(),
1,165✔
84
              std::round(temp_actual)) == temps_to_read.end()) {
2,330✔
85
          temps_to_read.push_back(std::round(temp_actual));
1,105✔
86
        }
87
      } else {
UNCOV
88
        fatal_error(fmt::format(
×
89
          "Nuclear data library does not contain cross sections "
90
          "for {}  at or near {} K. Available temperatures "
91
          "are {} K. Consider making use of openmc.Settings.temperature "
92
          "to specify how intermediate temperatures are treated.",
93
          name_, std::round(T), concatenate(temps_available)));
×
94
      }
95
    }
96
    break;
1,165✔
97

98
  case TemperatureMethod::INTERPOLATION:
20✔
99
    // If temperature interpolation or multipole is selected, get a list of
100
    // bounding temperatures for each actual temperature present in the model
101
    for (const auto& T : temperature) {
40✔
102
      bool found = false;
20✔
103
      for (int j = 0; j < temps_available.size() - 1; ++j) {
20!
UNCOV
104
        if (temps_available[j] <= T && T < temps_available[j + 1]) {
×
UNCOV
105
          int T_j = temps_available[j];
×
UNCOV
106
          int T_j1 = temps_available[j + 1];
×
UNCOV
107
          if (std::find(temps_to_read.begin(), temps_to_read.end(), T_j) ==
×
UNCOV
108
              temps_to_read.end()) {
×
109
            temps_to_read.push_back(T_j);
×
110
          }
111
          if (std::find(temps_to_read.begin(), temps_to_read.end(), T_j1) ==
×
112
              temps_to_read.end()) {
×
113
            temps_to_read.push_back(T_j1);
×
114
          }
UNCOV
115
          found = true;
×
116
        }
117
      }
118
      if (!found) {
20!
119
        // If no pairs found, check if the desired temperature falls within
120
        // bounds' tolerance
121
        if (std::abs(T - temps_available[0]) <=
20!
122
            settings::temperature_tolerance) {
123
          if (std::find(temps_to_read.begin(), temps_to_read.end(),
20✔
124
                temps_available[0]) == temps_to_read.end()) {
40!
125
            temps_to_read.push_back(temps_available[0]);
20✔
126
          }
UNCOV
127
        } else if (std::abs(T - temps_available[n - 1]) <=
×
128
                   settings::temperature_tolerance) {
UNCOV
129
          if (std::find(temps_to_read.begin(), temps_to_read.end(),
×
UNCOV
130
                temps_available[n - 1]) == temps_to_read.end()) {
×
UNCOV
131
            temps_to_read.push_back(temps_available[n - 1]);
×
132
          }
133
        } else {
134
          fatal_error(
×
135
            fmt::format("Nuclear data library does not contain cross "
×
136
                        "sections for {} at temperatures that bound {} K.",
UNCOV
137
              name_, std::round(T)));
×
138
        }
139
      }
140
    }
141
  }
142

143
  // Sort temperatures to read
144
  std::sort(temps_to_read.begin(), temps_to_read.end());
1,185✔
145

146
  auto n_temperature = temps_to_read.size();
1,185✔
147
  kTs_.reserve(n_temperature);
1,185✔
148
  data_.reserve(n_temperature);
1,185✔
149

150
  for (auto T : temps_to_read) {
2,370✔
151
    // Get temperature as a string
152
    std::string temp_str = fmt::format("{}K", T);
947✔
153

154
    // Read exact temperature value
155
    double kT;
156
    read_dataset(kT_group, temp_str.data(), kT);
1,185✔
157
    kTs_.push_back(kT);
1,185✔
158

159
    // Open group for this temperature
160
    hid_t T_group = open_group(group, temp_str.data());
1,185✔
161
    data_.emplace_back(T_group);
1,185✔
162
    close_group(T_group);
1,185✔
163
  }
1,185✔
164

165
  close_group(kT_group);
1,185✔
166
}
1,185✔
167

168
void ThermalScattering::calculate_xs(double E, double sqrtkT, int* i_temp,
135,139,924✔
169
  double* elastic, double* inelastic, uint64_t* seed) const
170
{
171
  // Determine temperature for S(a,b) table
172
  double kT = sqrtkT * sqrtkT;
135,139,924✔
173
  int i = 0;
135,139,924✔
174

175
  auto n = kTs_.size();
135,139,924✔
176
  if (n > 1) {
135,139,924!
UNCOV
177
    if (settings::temperature_method == TemperatureMethod::NEAREST) {
×
UNCOV
178
      while (kTs_[i + 1] < kT && i + 1 < n - 1)
×
UNCOV
179
        ++i;
×
180
      // Pick closer of two bounding temperatures
UNCOV
181
      if (kT - kTs_[i] > kTs_[i + 1] - kT)
×
182
        ++i;
×
183
    } else {
184
      // If current kT outside of the bounds of available, snap to the bound
UNCOV
185
      if (kT < kTs_.front()) {
×
186
        i = 0;
×
187
      } else if (kT > kTs_.back()) {
×
UNCOV
188
        i = kTs_.size() - 1;
×
189
      } else {
190
        // Find temperatures that bound the actual temperature
191
        while (kTs_[i + 1] < kT && i + 1 < n - 1)
×
192
          ++i;
×
193
        // Randomly sample between temperature i and i+1
UNCOV
194
        double f = (kT - kTs_[i]) / (kTs_[i + 1] - kTs_[i]);
×
UNCOV
195
        if (f > prn(seed))
×
196
          ++i;
×
197
      }
198
    }
199
  }
200

201
  // Set temperature index
202
  *i_temp = i;
135,139,924✔
203

204
  // Calculate cross sections for ith temperature
205
  data_[i].calculate_xs(E, elastic, inelastic);
135,139,924✔
206
}
135,139,924✔
207

UNCOV
208
bool ThermalScattering::has_nuclide(const char* name) const
×
209
{
UNCOV
210
  std::string nuc {name};
×
UNCOV
211
  return std::find(nuclides_.begin(), nuclides_.end(), nuc) != nuclides_.end();
×
UNCOV
212
}
×
213

214
//==============================================================================
215
// ThermalData implementation
216
//==============================================================================
217

218
ThermalData::ThermalData(hid_t group)
1,185✔
219
{
220
  // Coherent/incoherent elastic data
221
  if (object_exists(group, "elastic")) {
1,185✔
222
    // Read cross section data
223
    hid_t elastic_group = open_group(group, "elastic");
124✔
224

225
    // Read elastic cross section
226
    elastic_.xs = read_function(elastic_group, "xs");
124✔
227

228
    // Read angle-energy distribution
229
    hid_t dgroup = open_group(elastic_group, "distribution");
124✔
230
    std::string temp;
124✔
231
    read_attribute(dgroup, "type", temp);
124✔
232
    if (temp == "coherent_elastic") {
124✔
233
      auto xs = dynamic_cast<CoherentElasticXS*>(elastic_.xs.get());
84!
234
      elastic_.distribution = make_unique<CoherentElasticAE>(*xs);
84✔
235
    } else if (temp == "incoherent_elastic") {
40!
UNCOV
236
      elastic_.distribution = make_unique<IncoherentElasticAE>(dgroup);
×
237
    } else if (temp == "incoherent_elastic_discrete") {
40!
UNCOV
238
      auto xs = dynamic_cast<Tabulated1D*>(elastic_.xs.get());
×
239
      elastic_.distribution =
UNCOV
240
        make_unique<IncoherentElasticAEDiscrete>(dgroup, xs->x());
×
241
    } else if (temp == "mixed_elastic") {
40!
242
      // Get coherent/incoherent cross sections
243
      auto mixed_xs = dynamic_cast<Sum1D*>(elastic_.xs.get());
40!
244
      const auto& coh_xs =
245
        dynamic_cast<const CoherentElasticXS*>(mixed_xs->functions(0).get());
40!
246
      const auto& incoh_xs = mixed_xs->functions(1).get();
40✔
247

248
      // Create mixed elastic distribution
249
      elastic_.distribution =
250
        make_unique<MixedElasticAE>(dgroup, *coh_xs, *incoh_xs);
40✔
251
    }
252

253
    close_group(elastic_group);
124✔
254
  }
124✔
255

256
  // Inelastic data
257
  if (object_exists(group, "inelastic")) {
1,185!
258
    // Read type of inelastic data
259
    hid_t inelastic_group = open_group(group, "inelastic");
1,185✔
260

261
    // Read inelastic cross section
262
    inelastic_.xs = read_function(inelastic_group, "xs");
1,185✔
263

264
    // Read angle-energy distribution
265
    hid_t dgroup = open_group(inelastic_group, "distribution");
1,185✔
266
    std::string temp;
1,185✔
267
    read_attribute(dgroup, "type", temp);
1,185✔
268
    if (temp == "incoherent_inelastic") {
1,185✔
269
      inelastic_.distribution = make_unique<IncoherentInelasticAE>(dgroup);
40✔
270
    } else if (temp == "incoherent_inelastic_discrete") {
1,145!
271
      auto xs = dynamic_cast<Tabulated1D*>(inelastic_.xs.get());
1,145!
272
      inelastic_.distribution =
273
        make_unique<IncoherentInelasticAEDiscrete>(dgroup, xs->x());
1,145✔
274
    }
275

276
    close_group(inelastic_group);
1,185✔
277
  }
1,185✔
278
}
1,185✔
279

280
void ThermalData::calculate_xs(
135,139,924✔
281
  double E, double* elastic, double* inelastic) const
282
{
283
  // Calculate thermal elastic scattering cross section
284
  if (elastic_.xs) {
135,139,924✔
285
    *elastic = (*elastic_.xs)(E);
4,448,610✔
286
  } else {
287
    *elastic = 0.0;
130,691,314✔
288
  }
289

290
  // Calculate thermal inelastic scattering cross section
291
  *inelastic = (*inelastic_.xs)(E);
135,139,924✔
292
}
135,139,924✔
293

294
void ThermalData::sample(const NuclideMicroXS& micro_xs, double E,
115,883,521✔
295
  double* E_out, double* mu, uint64_t* seed)
296
{
297
  // Determine whether inelastic or elastic scattering will occur
298
  if (prn(seed) < micro_xs.thermal_elastic / micro_xs.thermal) {
115,883,521✔
299
    elastic_.distribution->sample(E, *E_out, *mu, seed);
163,050✔
300
  } else {
301
    inelastic_.distribution->sample(E, *E_out, *mu, seed);
115,720,471✔
302
  }
303

304
  // Because of floating-point roundoff, it may be possible for mu to be
305
  // outside of the range [-1,1). In these cases, we just set mu to exactly
306
  // -1 or 1
307
  if (std::abs(*mu) > 1.0)
115,883,521!
UNCOV
308
    *mu = std::copysign(1.0, *mu);
×
309
}
115,883,521✔
310

311
void free_memory_thermal()
7,505✔
312
{
313
  data::thermal_scatt.clear();
7,505✔
314
  data::thermal_scatt_map.clear();
7,505✔
315
}
7,505✔
316

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