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

openmc-dev / openmc / 17361657497

31 Aug 2025 07:54PM UTC coverage: 84.729%. First build
17361657497

Pull #3550

github

web-flow
Merge 98ca32e81 into 00edc7769
Pull Request #3550: [Point Detector] Add distribution get_pdf functionality

7 of 356 new or added lines in 10 files covered. (1.97%)

52947 of 62490 relevant lines covered (84.73%)

38803853.85 hits per line

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

68.59
/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
#include <fstream>
6
#include <iomanip>
7

8
#include "xtensor/xarray.hpp"
9
#include "xtensor/xbuilder.hpp"
10
#include "xtensor/xmath.hpp"
11
#include "xtensor/xsort.hpp"
12
#include "xtensor/xtensor.hpp"
13
#include "xtensor/xview.hpp"
14
#include <fmt/core.h>
15

16
#include "openmc/constants.h"
17
#include "openmc/endf.h"
18
#include "openmc/error.h"
19
#include "openmc/random_lcg.h"
20
#include "openmc/search.h"
21
#include "openmc/secondary_correlated.h"
22
#include "openmc/secondary_thermal.h"
23
#include "openmc/settings.h"
24
#include "openmc/string_utils.h"
25

26
namespace openmc {
27

28
//==============================================================================
29
// Global variables
30
//==============================================================================
31

32
namespace data {
33
std::unordered_map<std::string, int> thermal_scatt_map;
34
vector<unique_ptr<ThermalScattering>> thermal_scatt;
35
} // namespace data
36

37
//==============================================================================
38
// ThermalScattering implementation
39
//==============================================================================
40

41
ThermalScattering::ThermalScattering(
1,218✔
42
  hid_t group, const vector<double>& temperature)
1,218✔
43
{
44
  // Get name of table from group
45
  name_ = object_name(group);
1,218✔
46

47
  // Get rid of leading '/'
48
  name_ = name_.substr(1);
1,218✔
49

50
  read_attribute(group, "atomic_weight_ratio", awr_);
1,218✔
51
  read_attribute(group, "energy_max", energy_max_);
1,218✔
52
  read_attribute(group, "nuclides", nuclides_);
1,218✔
53

54
  // Read temperatures
55
  hid_t kT_group = open_group(group, "kTs");
1,218✔
56

57
  // Determine temperatures available
58
  auto dset_names = dataset_names(kT_group);
1,218✔
59
  auto n = dset_names.size();
1,218✔
60
  auto temps_available = xt::empty<double>({n});
1,218✔
61
  for (int i = 0; i < dset_names.size(); ++i) {
2,436✔
62
    // Read temperature value
63
    double T;
64
    read_dataset(kT_group, dset_names[i].data(), T);
1,218✔
65
    temps_available[i] = std::round(T / K_BOLTZMANN);
1,218✔
66
  }
67
  std::sort(temps_available.begin(), temps_available.end());
1,218✔
68

69
  // Determine actual temperatures to read -- start by checking whether a
70
  // temperature range was given, in which case all temperatures in the range
71
  // are loaded irrespective of what temperatures actually appear in the model
72
  vector<int> temps_to_read;
1,218✔
73
  if (settings::temperature_range[1] > 0.0) {
1,218✔
74
    for (const auto& T : temps_available) {
×
75
      if (settings::temperature_range[0] <= T &&
×
76
          T <= settings::temperature_range[1]) {
×
77
        temps_to_read.push_back(std::round(T));
×
78
      }
79
    }
80
  }
81

82
  switch (settings::temperature_method) {
1,218✔
83
  case TemperatureMethod::NEAREST:
1,196✔
84
    // Determine actual temperatures to read
85
    for (const auto& T : temperature) {
2,392✔
86

87
      auto i_closest = xt::argmin(xt::abs(temps_available - T))[0];
1,196✔
88
      auto temp_actual = temps_available[i_closest];
1,196✔
89
      if (std::abs(temp_actual - T) < settings::temperature_tolerance) {
1,196✔
90
        if (std::find(temps_to_read.begin(), temps_to_read.end(),
1,196✔
91
              std::round(temp_actual)) == temps_to_read.end()) {
2,392✔
92
          temps_to_read.push_back(std::round(temp_actual));
1,196✔
93
        }
94
      } else {
95
        fatal_error(fmt::format(
×
96
          "Nuclear data library does not contain cross sections "
97
          "for {}  at or near {} K. Available temperatures "
98
          "are {} K. Consider making use of openmc.Settings.temperature "
99
          "to specify how intermediate temperatures are treated.",
100
          name_, std::round(T), concatenate(temps_available)));
×
101
      }
102
    }
103
    break;
1,196✔
104

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

150
  // Sort temperatures to read
151
  std::sort(temps_to_read.begin(), temps_to_read.end());
1,218✔
152

153
  auto n_temperature = temps_to_read.size();
1,218✔
154
  kTs_.reserve(n_temperature);
1,218✔
155
  data_.reserve(n_temperature);
1,218✔
156

157
  for (auto T : temps_to_read) {
2,436✔
158
    // Get temperature as a string
159
    std::string temp_str = fmt::format("{}K", T);
1,023✔
160

161
    // Read exact temperature value
162
    double kT;
163
    read_dataset(kT_group, temp_str.data(), kT);
1,218✔
164
    kTs_.push_back(kT);
1,218✔
165

166
    // Open group for this temperature
167
    hid_t T_group = open_group(group, temp_str.data());
1,218✔
168
    data_.emplace_back(T_group);
1,218✔
169
    close_group(T_group);
1,218✔
170
  }
1,218✔
171

172
  close_group(kT_group);
1,218✔
173
}
1,218✔
174

175
void ThermalScattering::calculate_xs(double E, double sqrtkT, int* i_temp,
100,195,166✔
176
  double* elastic, double* inelastic, uint64_t* seed) const
177
{
178
  // Determine temperature for S(a,b) table
179
  double kT = sqrtkT * sqrtkT;
100,195,166✔
180
  int i = 0;
100,195,166✔
181

182
  auto n = kTs_.size();
100,195,166✔
183
  if (n > 1) {
100,195,166✔
184
    if (settings::temperature_method == TemperatureMethod::NEAREST) {
×
185
      while (kTs_[i + 1] < kT && i + 1 < n - 1)
×
186
        ++i;
×
187
      // Pick closer of two bounding temperatures
188
      if (kT - kTs_[i] > kTs_[i + 1] - kT)
×
189
        ++i;
×
190
    } else {
191
      // If current kT outside of the bounds of available, snap to the bound
192
      if (kT < kTs_.front()) {
×
193
        i = 0;
×
194
      } else if (kT > kTs_.back()) {
×
195
        i = kTs_.size() - 1;
×
196
      } else {
197
        // Find temperatures that bound the actual temperature
198
        while (kTs_[i + 1] < kT && i + 1 < n - 1)
×
199
          ++i;
×
200
        // Randomly sample between temperature i and i+1
201
        double f = (kT - kTs_[i]) / (kTs_[i + 1] - kTs_[i]);
×
202
        if (f > prn(seed))
×
203
          ++i;
×
204
      }
205
    }
206
  }
207

208
  // Set temperature index
209
  *i_temp = i;
100,195,166✔
210

211
  // Calculate cross sections for ith temperature
212
  data_[i].calculate_xs(E, elastic, inelastic);
100,195,166✔
213
}
100,195,166✔
214

215
bool ThermalScattering::has_nuclide(const char* name) const
×
216
{
217
  std::string nuc {name};
×
218
  return std::find(nuclides_.begin(), nuclides_.end(), nuc) != nuclides_.end();
×
219
}
220

221
//==============================================================================
222
// ThermalData implementation
223
//==============================================================================
224

225
ThermalData::ThermalData(hid_t group)
1,218✔
226
{
227
  // Coherent/incoherent elastic data
228
  if (object_exists(group, "elastic")) {
1,218✔
229
    // Read cross section data
230
    hid_t elastic_group = open_group(group, "elastic");
140✔
231

232
    // Read elastic cross section
233
    elastic_.xs = read_function(elastic_group, "xs");
140✔
234

235
    // Read angle-energy distribution
236
    hid_t dgroup = open_group(elastic_group, "distribution");
140✔
237
    std::string temp;
140✔
238
    read_attribute(dgroup, "type", temp);
140✔
239
    if (temp == "coherent_elastic") {
140✔
240
      auto xs = dynamic_cast<CoherentElasticXS*>(elastic_.xs.get());
96✔
241
      elastic_.distribution = make_unique<CoherentElasticAE>(*xs);
96✔
242
    } else if (temp == "incoherent_elastic") {
44✔
243
      elastic_.distribution = make_unique<IncoherentElasticAE>(dgroup);
×
244
    } else if (temp == "incoherent_elastic_discrete") {
44✔
245
      auto xs = dynamic_cast<Tabulated1D*>(elastic_.xs.get());
×
246
      elastic_.distribution =
247
        make_unique<IncoherentElasticAEDiscrete>(dgroup, xs->x());
×
248
    } else if (temp == "mixed_elastic") {
44✔
249
      // Get coherent/incoherent cross sections
250
      auto mixed_xs = dynamic_cast<Sum1D*>(elastic_.xs.get());
44✔
251
      const auto& coh_xs =
252
        dynamic_cast<const CoherentElasticXS*>(mixed_xs->functions(0).get());
44✔
253
      const auto& incoh_xs = mixed_xs->functions(1).get();
44✔
254

255
      // Create mixed elastic distribution
256
      elastic_.distribution =
257
        make_unique<MixedElasticAE>(dgroup, *coh_xs, *incoh_xs);
44✔
258
    }
259

260
    close_group(elastic_group);
140✔
261
  }
140✔
262

263
  // Inelastic data
264
  if (object_exists(group, "inelastic")) {
1,218✔
265
    // Read type of inelastic data
266
    hid_t inelastic_group = open_group(group, "inelastic");
1,218✔
267

268
    // Read inelastic cross section
269
    inelastic_.xs = read_function(inelastic_group, "xs");
1,218✔
270

271
    // Read angle-energy distribution
272
    hid_t dgroup = open_group(inelastic_group, "distribution");
1,218✔
273
    std::string temp;
1,218✔
274
    read_attribute(dgroup, "type", temp);
1,218✔
275
    if (temp == "incoherent_inelastic") {
1,218✔
276
      inelastic_.distribution = make_unique<IncoherentInelasticAE>(dgroup);
44✔
277
    } else if (temp == "incoherent_inelastic_discrete") {
1,174✔
278
      auto xs = dynamic_cast<Tabulated1D*>(inelastic_.xs.get());
1,174✔
279
      inelastic_.distribution =
280
        make_unique<IncoherentInelasticAEDiscrete>(dgroup, xs->x());
1,174✔
281
    }
282

283
    close_group(inelastic_group);
1,218✔
284
  }
1,218✔
285
}
1,218✔
286

287
void ThermalData::calculate_xs(
100,195,166✔
288
  double E, double* elastic, double* inelastic) const
289
{
290
  // Calculate thermal elastic scattering cross section
291
  if (elastic_.xs) {
100,195,166✔
292
    *elastic = (*elastic_.xs)(E);
4,900,302✔
293
  } else {
294
    *elastic = 0.0;
95,294,864✔
295
  }
296

297
  // Calculate thermal inelastic scattering cross section
298
  *inelastic = (*inelastic_.xs)(E);
100,195,166✔
299
}
100,195,166✔
300

301
void ThermalData::sample(const NuclideMicroXS& micro_xs, double E,
82,539,400✔
302
  double* E_out, double* mu, uint64_t* seed)
303
{
304
  // Determine whether inelastic or elastic scattering will occur
305
  if (prn(seed) < micro_xs.thermal_elastic / micro_xs.thermal) {
82,539,400✔
306
    elastic_.distribution->sample(E, *E_out, *mu, seed);
183,293✔
307
  } else {
308
    inelastic_.distribution->sample(E, *E_out, *mu, seed);
82,356,107✔
309
  }
310

311
  // Because of floating-point roundoff, it may be possible for mu to be
312
  // outside of the range [-1,1). In these cases, we just set mu to exactly
313
  // -1 or 1
314
  if (std::abs(*mu) > 1.0)
82,539,400✔
315
    *mu = std::copysign(1.0, *mu);
×
316
}
82,539,400✔
317

NEW
318
double ThermalData::get_pdf(const NuclideMicroXS& micro_xs, double E,
×
319
  double& E_out, double mu, uint64_t* seed)
320
{
321
  AngleEnergy* angleEnergyPtr;
322

NEW
323
  if (prn(seed) < micro_xs.thermal_elastic / micro_xs.thermal) {
×
NEW
324
    angleEnergyPtr = elastic_.distribution.get();
×
325
  } else {
NEW
326
    angleEnergyPtr = inelastic_.distribution.get();
×
327
  }
328

NEW
329
  return angleEnergyPtr->get_pdf(E, E_out, mu, seed);
×
330
}
331

332
void free_memory_thermal()
7,887✔
333
{
334
  data::thermal_scatt.clear();
7,887✔
335
  data::thermal_scatt_map.clear();
7,887✔
336
}
7,887✔
337

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