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

openmc-dev / openmc / 31265430262

08 Aug 2026 03:49PM UTC coverage: 81.463% (+0.03%) from 81.429%
31265430262

Pull #3971

github

web-flow
Merge ca0a9521d into 83a5a51ae
Pull Request #3971: Delta tracking

18857 of 27294 branches covered (69.09%)

Branch coverage included in aggregate %.

601 of 645 new or added lines in 20 files covered. (93.18%)

543 existing lines in 18 files now uncovered.

60850 of 70550 relevant lines covered (86.25%)

50508854.62 hits per line

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

78.83
/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,324,553 ✔
45
{
46
  // Get type of library
47
  if (check_for_node(node, "type")) {
7,324,553 !
48
    auto type = get_node_value(node, "type");
7,324,553 ✔
49
    if (type == "neutron") {
7,324,553 ✔
50
      type_ = Type::neutron;
3,207,351 ✔
51
    } else if (type == "thermal") {
4,117,202 ✔
52
      type_ = Type::thermal;
151,816 ✔
53
    } else if (type == "photon") {
3,965,386 ✔
54
      type_ = Type::photon;
758,200 ✔
55
    } else if (type == "wmp") {
3,207,186 !
56
      type_ = Type::wmp;
3,207,186 ✔
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,324,553 !
66
    materials_ = get_node_array<std::string>(node, "materials");
7,324,553 ✔
67
  }
68

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

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

81
  if (!file_exists(path_)) {
7,324,553 !
UNCOV
82
    warning("Cross section library " + path_ + " does not exist.");
×
83
  }
84
}
7,324,553 ✔
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)
9,126 ✔
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")) {
9,126 ✔
112
    // No cross_sections.xml file specified in settings.xml, check
113
    // environment variable
114
    if (settings::run_CE) {
7,593 ✔
115
      char* envvar = std::getenv("OPENMC_CROSS_SECTIONS");
7,571 ✔
116
      if (!envvar) {
7,571 !
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,571 ✔
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) {
9,126 ✔
156
    read_ce_cross_sections_xml();
7,747 ✔
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;
9,126 ✔
164
  for (const auto& lib : data::libraries) {
7,337,272 ✔
165
    for (const auto& name : lib.materials_) {
14,656,292 ✔
166
      LibraryKey key {lib.type_, name};
7,328,146 ✔
167
      data::library_map.insert({key, i});
7,328,146 ✔
168
    }
7,328,146 ✔
169
    ++i;
7,328,146 ✔
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,171 ✔
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
}
9,126 ✔
181

182
void read_ce_cross_sections(const vector<vector<double>>& nuc_temps,
7,747 ✔
183
  const vector<vector<double>>& thermal_temps)
184
{
185
  std::unordered_set<std::string> already_read;
7,747 ✔
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,747 ✔
190
  vector<std::string> thermal_names(data::thermal_scatt_map.size());
7,747 ✔
191
  for (const auto& kv : data::nuclide_map) {
40,255 ✔
192
    nuclide_names[kv.second] = kv.first;
65,016 ✔
193
  }
194
  for (const auto& kv : data::thermal_scatt_map) {
9,336 ✔
195
    thermal_names[kv.second] = kv.first;
3,178 ✔
196
  }
197

198
  // Read cross sections
199
  for (const auto& mat : model::materials) {
21,613 ✔
200
    for (int i_nuc : mat->nuclide_) {
67,376 ✔
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];
53,510 ✔
205

206
      // If we've already read this nuclide, skip it
207
      if (already_read.find(name) != already_read.end())
53,510 ✔
208
        continue;
21,002 ✔
209

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

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

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

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

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

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

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

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

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

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

259
  // Show minimum/maximum temperature
260
  write_message(
7,747 ✔
261
    4, "Minimum neutron data temperature: {} K", data::temperature_min);
262
  write_message(
7,747 ✔
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,747 ✔
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,494 ✔
281

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

291
  if (std::filesystem::is_directory(filename)) {
7,747 !
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,747 ✔
297

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

306
  std::string directory;
7,747 ✔
307
  if (check_for_node(root, "directory")) {
7,747 !
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,747 ✔
314
      directory = filename.parent_path().string();
15,142 ✔
315
    } else {
316
      directory = settings::path_input;
176 ✔
317
    }
318
  }
319

320
  for (const auto& node_library : root.children("library")) {
14,656,853 ✔
321
    data::libraries.emplace_back(node_library, directory);
7,324,553 ✔
322
  }
323

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

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

341
      // Read continuous-energy cross sections from HDF5
342
      read_ce_cross_sections(nuc_temps, thermal_temps);
7,747 ✔
343
    } else {
7,747 ✔
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();
9,126 ✔
350
  }
351
}
9,245 ✔
352

353
void library_clear()
9,374 ✔
354
{
355
  data::libraries.clear();
9,374 ✔
356
  data::library_map.clear();
9,374 ✔
357
}
9,374 ✔
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