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

openmc-dev / openmc / 32734756650

24 Aug 2026 01:47PM UTC coverage: 81.299% (-0.1%) from 81.425%
32734756650

Pull #3675

github

web-flow
Merge d355de710 into 7ecd3a961
Pull Request #3675: Extend level scattering to support incident photons

18557 of 27030 branches covered (68.65%)

Branch coverage included in aggregate %.

55 of 77 new or added lines in 4 files covered. (71.43%)

738 existing lines in 26 files now uncovered.

60342 of 70018 relevant lines covered (86.18%)

49966364.8 hits per line

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

78.39
/src/cross_sections.cpp
1
#include "openmc/cross_sections.h"
2

3
#include "openmc/capi.h"
4
#include "openmc/constants.h"
5
#include "openmc/container_util.h"
6
#include "openmc/error.h"
7
#include "openmc/file_utils.h"
8
#include "openmc/geometry_aux.h"
9
#include "openmc/hdf5_interface.h"
10
#include "openmc/material.h"
11
#include "openmc/message_passing.h"
12
#include "openmc/mgxs_interface.h"
13
#include "openmc/nuclide.h"
14
#include "openmc/photon.h"
15
#include "openmc/settings.h"
16
#include "openmc/simulation.h"
17
#include "openmc/thermal.h"
18
#include "openmc/timer.h"
19
#include "openmc/wmp.h"
20
#include "openmc/xml_interface.h"
21

22
#include "pugixml.hpp"
23

24
#include <cstdlib> // for getenv
25
#include <filesystem>
26
#include <unordered_set>
27

28
namespace openmc {
29

30
//==============================================================================
31
// Global variable declarations
32
//==============================================================================
33

34
namespace data {
35

36
std::map<LibraryKey, std::size_t> library_map;
37
vector<Library> libraries;
38
} // namespace data
39

40
//==============================================================================
41
// Library methods
42
//==============================================================================
43

44
Library::Library(pugi::xml_node node, const std::string& directory)
7,179,653✔
45
{
46
  // Get type of library
47
  if (check_for_node(node, "type")) {
7,179,653!
48
    auto type = get_node_value(node, "type");
7,179,653✔
49
    if (type == "neutron") {
7,179,653✔
50
      type_ = Type::neutron;
3,143,901✔
51
    } else if (type == "thermal") {
4,035,752✔
52
      type_ = Type::thermal;
148,816✔
53
    } else if (type == "photon") {
3,886,936✔
54
      type_ = Type::photon;
743,200✔
55
    } else if (type == "wmp") {
3,143,736!
56
      type_ = Type::wmp;
3,143,736✔
57
    } else {
UNCOV
58
      fatal_error("Unrecognized library type: " + type);
×
59
    }
UNCOV
60
  } else {
×
61
    fatal_error("Missing library type");
×
62
  }
63

64
  // Get list of materials
65
  if (check_for_node(node, "materials")) {
7,179,653!
66
    materials_ = get_node_array<std::string>(node, "materials");
7,179,653✔
67
  }
68

69
  // determine path of cross section table
70
  if (!check_for_node(node, "path")) {
7,179,653!
UNCOV
71
    fatal_error("Missing library path");
×
72
  }
73
  std::filesystem::path path(get_node_value(node, "path"));
7,179,653✔
74

75
  if (path.is_absolute() || directory.empty()) {
7,179,653!
76
    path_ = path.string();
10,967✔
77
  } else {
78
    path_ = (std::filesystem::path(directory) / path).string();
21,506,058✔
79
  }
80

81
  if (!file_exists(path_)) {
7,179,653!
UNCOV
82
    warning("Cross section library " + path_ + " does not exist.");
×
83
  }
84
}
7,179,653✔
85

86
//==============================================================================
87
// Non-member functions
88
//==============================================================================
89

90
void read_cross_sections_xml()
1,380✔
91
{
92
  pugi::xml_document doc;
1,380✔
93
  std::string filename = settings::path_input + "materials.xml";
1,380✔
94
  // Check if materials.xml exists
95
  if (!file_exists(filename)) {
1,380!
UNCOV
96
    fatal_error("Material XML file '" + filename + "' does not exist.");
×
97
  }
98
  // Parse materials.xml file
99
  doc.load_file(filename.c_str());
1,380✔
100

101
  auto root = doc.document_element();
1,380✔
102

103
  read_cross_sections_xml(root);
1,380✔
104
}
1,380✔
105

106
void read_cross_sections_xml(pugi::xml_node root)
8,976✔
107
{
108
  // Find cross_sections.xml file -- the first place to look is the
109
  // materials.xml file. If no file is found there, then we check the
110
  // OPENMC_CROSS_SECTIONS environment variable
111
  if (!check_for_node(root, "cross_sections")) {
8,976✔
112
    // No cross_sections.xml file specified in settings.xml, check
113
    // environment variable
114
    if (settings::run_CE) {
7,443✔
115
      char* envvar = std::getenv("OPENMC_CROSS_SECTIONS");
7,421✔
116
      if (!envvar) {
7,421!
UNCOV
117
        fatal_error(
×
118
          "No cross_sections.xml file was specified in "
119
          "materials.xml or in the OPENMC_CROSS_SECTIONS"
120
          " environment variable. OpenMC needs such a file to identify "
121
          "where to find data libraries. Please consult the"
122
          " user's guide at https://docs.openmc.org/ for "
123
          "information on how to set up data libraries.");
124
      }
125
      settings::path_cross_sections = envvar;
7,421✔
126
    } else {
127
      char* envvar = std::getenv("OPENMC_MG_CROSS_SECTIONS");
22✔
128
      if (!envvar) {
22!
UNCOV
129
        fatal_error(
×
130
          "No mgxs.h5 file was specified in "
131
          "materials.xml or in the OPENMC_MG_CROSS_SECTIONS environment "
132
          "variable. OpenMC needs such a file to identify where to "
133
          "find MG cross section libraries. Please consult the user's "
134
          "guide at https://docs.openmc.org for information on "
135
          "how to set up MG cross section libraries.");
136
      }
137
      settings::path_cross_sections = envvar;
22✔
138
    }
139
  } else {
140
    settings::path_cross_sections = get_node_value(root, "cross_sections");
1,533✔
141

142
    // If no directory component is given, the file is probably in the input
143
    // directory. Note that this has to be determined with std::filesystem
144
    // rather than by searching for a '/' so that Windows paths (which use a
145
    // different separator and may be prefixed by a drive letter) work too.
146
    std::filesystem::path p(settings::path_cross_sections);
1,533✔
147
    if (p.is_relative() && !p.has_parent_path() &&
1,533!
148
        !settings::path_input.empty()) {
1,383!
UNCOV
149
      settings::path_cross_sections =
×
150
        (std::filesystem::path(settings::path_input) / p).string();
×
151
    }
152
  }
1,533✔
153

154
  // Now that the cross_sections.xml or mgxs.h5 has been located, read it in
155
  if (settings::run_CE) {
8,976✔
156
    read_ce_cross_sections_xml();
7,597✔
157
  } else {
158
    data::mg.read_header(settings::path_cross_sections);
1,379✔
159
    put_mgxs_header_data_to_globals();
1,379✔
160
  }
161

162
  // Establish mapping between (type, material) and index in libraries
163
  int i = 0;
8,976✔
164
  for (const auto& lib : data::libraries) {
7,192,222✔
165
    for (const auto& name : lib.materials_) {
14,366,492✔
166
      LibraryKey key {lib.type_, name};
7,183,246✔
167
      data::library_map.insert({key, i});
7,183,246✔
168
    }
7,183,246✔
169
    ++i;
7,183,246✔
170
  }
171

172
  // Check that 0K nuclides are listed in the cross_sections.xml file
173
  for (const auto& name : settings::res_scat_nuclides) {
9,021✔
174
    LibraryKey key {Library::Type::neutron, name};
45✔
175
    if (data::library_map.find(key) == data::library_map.end()) {
45!
UNCOV
176
      fatal_error("Could not find resonant scatterer " + name +
×
177
                  " in cross_sections.xml file!");
178
    }
179
  }
45✔
180
}
8,976✔
181

182
void read_ce_cross_sections(const vector<vector<double>>& nuc_temps,
7,597✔
183
  const vector<vector<double>>& thermal_temps)
184
{
185
  std::unordered_set<std::string> already_read;
7,597✔
186

187
  // Construct a vector of nuclide names because we haven't loaded nuclide data
188
  // yet, but we need to know the name of the i-th nuclide
189
  vector<std::string> nuclide_names(data::nuclide_map.size());
7,597✔
190
  vector<std::string> thermal_names(data::thermal_scatt_map.size());
7,597✔
191
  for (const auto& kv : data::nuclide_map) {
39,655✔
192
    nuclide_names[kv.second] = kv.first;
64,116✔
193
  }
194
  for (const auto& kv : data::thermal_scatt_map) {
9,036✔
195
    thermal_names[kv.second] = kv.first;
2,878✔
196
  }
197

198
  // Read cross sections
199
  for (const auto& mat : model::materials) {
21,163✔
200
    for (int i_nuc : mat->nuclide_) {
66,476✔
201
      // Find name of corresponding nuclide. Because we haven't actually loaded
202
      // data, we don't have the name available, so instead we search through
203
      // all key/value pairs in nuclide_map
204
      std::string& name = nuclide_names[i_nuc];
52,910✔
205

206
      // If we've already read this nuclide, skip it
207
      if (already_read.find(name) != already_read.end())
52,910✔
208
        continue;
20,852✔
209

210
      const auto& temps = nuc_temps[i_nuc];
32,058✔
211
      int err = openmc_load_nuclide(name.c_str(), temps.data(), temps.size());
32,058✔
212
      if (err < 0)
32,058!
UNCOV
213
        throw std::runtime_error {get_errmsg()};
×
214

215
      already_read.insert(name);
32,058✔
216
    }
217
  }
218

219
  // Perform final tasks -- reading S(a,b) tables, normalizing densities
220
  for (auto& mat : model::materials) {
21,163✔
221
    for (const auto& table : mat->thermal_tables_) {
15,716✔
222
      // Get name of S(a,b) table
223
      int i_table = table.index_table;
2,150✔
224
      std::string& name = thermal_names[i_table];
2,150✔
225

226
      if (already_read.find(name) == already_read.end()) {
2,150✔
227
        LibraryKey key {Library::Type::thermal, name};
1,439✔
228
        int idx = data::library_map[key];
1,439✔
229
        std::string& filename = data::libraries[idx].path_;
1,439✔
230

231
        write_message(6, "Reading {} from {}", name, filename);
1,439✔
232

233
        // Open file and make sure version matches
234
        hid_t file_id = file_open(filename, 'r');
1,439✔
235
        check_data_version(file_id);
1,439✔
236

237
        // Read thermal scattering data from HDF5
238
        hid_t group = open_group(file_id, name.c_str());
1,439✔
239
        data::thermal_scatt.push_back(
2,878✔
240
          make_unique<ThermalScattering>(group, thermal_temps[i_table]));
2,878✔
241
        close_group(group);
1,439✔
242
        file_close(file_id);
1,439✔
243

244
        // Add name to dictionary
245
        already_read.insert(name);
1,439✔
246
      }
1,439✔
247
    } // thermal_tables_
248

249
    // Finish setting up materials (normalizing densities, etc.)
250
    mat->finalize();
13,566✔
251
  } // materials
252

253
  if (settings::photon_transport &&
7,597✔
254
      settings::electron_treatment == ElectronTreatment::TTB) {
582✔
255
    // Take logarithm of energies since they are log-log interpolated
256
    data::ttb_e_grid = tensor::log(data::ttb_e_grid);
994✔
257
  }
258

259
  // Show minimum/maximum temperature
260
  write_message(
7,597✔
261
    4, "Minimum neutron data temperature: {} K", data::temperature_min);
262
  write_message(
7,597✔
263
    4, "Maximum neutron data temperature: {} K", data::temperature_max);
264

265
  // If the user wants multipole, make sure we found a multipole library.
266
  if (settings::temperature_multipole) {
7,597✔
267
    bool mp_found = false;
140✔
268
    for (const auto& nuc : data::nuclides) {
140!
269
      if (nuc->multipole_) {
140!
270
        mp_found = true;
271
        break;
272
      }
273
    }
274
    if (mpi::master && !mp_found) {
140!
UNCOV
275
      warning("Windowed multipole functionality is turned on, but no multipole "
×
276
              "libraries were found. Make sure that windowed multipole data is "
277
              "present in your cross_sections.xml file.");
278
    }
279
  }
280
}
15,194✔
281

282
void read_ce_cross_sections_xml()
7,597✔
283
{
284
  // Check if cross_sections.xml exists
285
  std::filesystem::path filename(settings::path_cross_sections);
7,597✔
286
  if (!std::filesystem::exists(filename)) {
7,597!
UNCOV
287
    fatal_error(
×
288
      "Cross sections XML file '" + filename.string() + "' does not exist.");
×
289
  }
290

291
  if (std::filesystem::is_directory(filename)) {
7,597!
UNCOV
292
    fatal_error("OPENMC_CROSS_SECTIONS is set to a directory. "
×
293
                "It should be set to an XML file.");
294
  }
295

296
  write_message("Reading cross sections XML file...", 5);
7,597✔
297

298
  // Parse cross_sections.xml file
299
  pugi::xml_document doc;
7,597✔
300
  auto result = doc.load_file(filename.c_str());
7,597✔
301
  if (!result) {
7,597!
UNCOV
302
    fatal_error("Error processing cross_sections.xml file.");
×
303
  }
304
  auto root = doc.document_element();
7,597✔
305

306
  std::string directory;
7,597✔
307
  if (check_for_node(root, "directory")) {
7,597!
308
    // Copy directory information if present
UNCOV
309
    directory = get_node_value(root, "directory");
×
310
  } else {
311
    // If no directory is listed in cross_sections.xml, by default select the
312
    // directory in which the cross_sections.xml file resides
313
    if (filename.has_parent_path()) {
7,597✔
314
      directory = filename.parent_path().string();
14,842✔
315
    } else {
316
      directory = settings::path_input;
176✔
317
    }
318
  }
319

320
  for (const auto& node_library : root.children("library")) {
14,366,903✔
321
    data::libraries.emplace_back(node_library, directory);
7,179,653✔
322
  }
323

324
  // Make sure file was not empty
325
  if (data::libraries.empty()) {
7,597!
UNCOV
326
    fatal_error(
×
327
      "No cross section libraries present in cross_sections.xml file.");
328
  }
329
}
15,194✔
330

331
void finalize_cross_sections()
9,095✔
332
{
333
  if (settings::run_mode != RunMode::PLOTTING) {
9,095✔
334
    simulation::time_read_xs.start();
8,976✔
335
    if (settings::run_CE) {
8,976✔
336
      // Determine desired temperatures for each nuclide and S(a,b) table
337
      double_2dvec nuc_temps(data::nuclide_map.size());
7,597✔
338
      double_2dvec thermal_temps(data::thermal_scatt_map.size());
7,597✔
339
      get_temperatures(nuc_temps, thermal_temps);
7,597✔
340

341
      // Read continuous-energy cross sections from HDF5
342
      read_ce_cross_sections(nuc_temps, thermal_temps);
7,597✔
343
    } else {
7,597✔
344
      // Create material macroscopic data for MGXS
345
      set_mg_interface_nuclides_and_temps();
1,379✔
346
      data::mg.init();
1,379✔
347
      mark_fissionable_mgxs_materials();
1,379✔
348
    }
349
    simulation::time_read_xs.stop();
8,976✔
350
  }
351
}
9,095✔
352

353
void library_clear()
9,224✔
354
{
355
  data::libraries.clear();
9,224✔
356
  data::library_map.clear();
9,224✔
357
}
9,224✔
358

359
} // namespace openmc
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc