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

openmc-dev / openmc / 15914061027

26 Jun 2025 10:49PM UTC coverage: 85.241% (+0.002%) from 85.239%
15914061027

Pull #3461

github

web-flow
Merge 09b49b487 into 5c1021446
Pull Request #3461: Refactor and Harden Configuration Management

48 of 56 new or added lines in 1 file covered. (85.71%)

280 existing lines in 11 files now uncovered.

52596 of 61703 relevant lines covered (85.24%)

36708109.72 hits per line

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

85.28
/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/string_utils.h"
18
#include "openmc/thermal.h"
19
#include "openmc/timer.h"
20
#include "openmc/wmp.h"
21
#include "openmc/xml_interface.h"
22

23
#include "pugixml.hpp"
24

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

29
namespace openmc {
30

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

35
namespace data {
36

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

41
//==============================================================================
42
// Library methods
43
//==============================================================================
44

45
Library::Library(pugi::xml_node node, const std::string& directory)
4,998,425✔
46
{
47
  // Get type of library
48
  if (check_for_node(node, "type")) {
4,998,425✔
49
    auto type = get_node_value(node, "type");
4,998,425✔
50
    if (type == "neutron") {
4,998,425✔
51
      type_ = Type::neutron;
2,188,767✔
52
    } else if (type == "thermal") {
2,809,658✔
53
      type_ = Type::thermal;
103,656✔
54
    } else if (type == "photon") {
2,706,002✔
55
      type_ = Type::photon;
517,400✔
56
    } else if (type == "wmp") {
2,188,602✔
57
      type_ = Type::wmp;
2,188,602✔
58
    } else {
59
      fatal_error("Unrecognized library type: " + type);
×
60
    }
61
  } else {
4,998,425✔
62
    fatal_error("Missing library type");
×
63
  }
64

65
  // Get list of materials
66
  if (check_for_node(node, "materials")) {
4,998,425✔
67
    materials_ = get_node_array<std::string>(node, "materials");
4,998,425✔
68
  }
69

70
  // determine path of cross section table
71
  if (!check_for_node(node, "path")) {
4,998,425✔
72
    fatal_error("Missing library path");
×
73
  }
74
  std::string path = get_node_value(node, "path");
4,998,425✔
75

76
  if (starts_with(path, "/")) {
4,998,425✔
77
    path_ = path;
×
78
  } else if (ends_with(directory, "/")) {
4,998,425✔
79
    path_ = directory + path;
×
80
  } else if (!directory.empty()) {
4,998,425✔
81
    path_ = directory + "/" + path;
4,998,425✔
82
  } else {
UNCOV
83
    path_ = path;
×
84
  }
85

86
  if (!file_exists(path_)) {
4,998,425✔
87
    warning("Cross section library " + path_ + " does not exist.");
×
88
  }
89
}
4,998,425✔
90

91
//==============================================================================
92
// Non-member functions
93
//==============================================================================
94

95
void read_cross_sections_xml()
1,318✔
96
{
97
  pugi::xml_document doc;
1,318✔
98
  std::string filename = settings::path_input + "materials.xml";
1,318✔
99
  // Check if materials.xml exists
100
  if (!file_exists(filename)) {
1,318✔
101
    fatal_error("Material XML file '" + filename + "' does not exist.");
×
102
  }
103
  // Parse materials.xml file
104
  doc.load_file(filename.c_str());
1,318✔
105

106
  auto root = doc.document_element();
1,318✔
107

108
  read_cross_sections_xml(root);
1,318✔
109
}
1,318✔
110

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

147
    // If no '/' found, the file is probably in the input directory
148
    auto pos = settings::path_cross_sections.rfind("/");
1,270✔
149
    if (pos == std::string::npos && !settings::path_input.empty()) {
1,270✔
150
      settings::path_cross_sections =
151
        settings::path_input + "/" + settings::path_cross_sections;
×
152
    }
153
  }
154

155
  // Now that the cross_sections.xml or mgxs.h5 has been located, read it in
156
  if (settings::run_CE) {
6,433✔
157
    read_ce_cross_sections_xml();
5,339✔
158
  } else {
159
    data::mg.read_header(settings::path_cross_sections);
1,094✔
160
    put_mgxs_header_data_to_globals();
1,094✔
161
  }
162

163
  // Establish mapping between (type, material) and index in libraries
164
  int i = 0;
6,433✔
165
  for (const auto& lib : data::libraries) {
5,007,740✔
166
    for (const auto& name : lib.materials_) {
10,002,614✔
167
      LibraryKey key {lib.type_, name};
5,001,307✔
168
      data::library_map.insert({key, i});
5,001,307✔
169
    }
5,001,307✔
170
    ++i;
5,001,307✔
171
  }
172

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

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

188
  // Construct a vector of nuclide names because we haven't loaded nuclide data
189
  // yet, but we need to know the name of the i-th nuclide
190
  vector<std::string> nuclide_names(data::nuclide_map.size());
5,339✔
191
  vector<std::string> thermal_names(data::thermal_scatt_map.size());
5,339✔
192
  for (const auto& kv : data::nuclide_map) {
29,557✔
193
    nuclide_names[kv.second] = kv.first;
24,218✔
194
  }
195
  for (const auto& kv : data::thermal_scatt_map) {
6,388✔
196
    thermal_names[kv.second] = kv.first;
1,049✔
197
  }
198

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

207
      // If we've already read this nuclide, skip it
208
      if (already_read.find(name) != already_read.end())
47,023✔
209
        continue;
22,805✔
210

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

216
      already_read.insert(name);
24,218✔
217
    }
218
  }
219

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

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

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

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

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

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

250
    // Finish setting up materials (normalizing densities, etc.)
251
    mat->finalize();
11,062✔
252
  } // materials
253

254
  if (settings::photon_transport &&
5,339✔
255
      settings::electron_treatment == ElectronTreatment::TTB) {
281✔
256
    // Take logarithm of energies since they are log-log interpolated
257
    data::ttb_e_grid = xt::log(data::ttb_e_grid);
259✔
258
  }
259

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

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

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

292
  if (std::filesystem::is_directory(filename)) {
5,339✔
293
    fatal_error("OPENMC_CROSS_SECTIONS is set to a directory. "
×
294
                "It should be set to an XML file.");
295
  }
296

297
  write_message("Reading cross sections XML file...", 5);
5,339✔
298

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

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

321
  for (const auto& node_library : root.children("library")) {
5,003,764✔
322
    data::libraries.emplace_back(node_library, directory);
4,998,425✔
323
  }
324

325
  // Make sure file was not empty
326
  if (data::libraries.empty()) {
5,339✔
327
    fatal_error(
×
328
      "No cross section libraries present in cross_sections.xml file.");
329
  }
330
}
5,339✔
331

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

342
      // Read continuous-energy cross sections from HDF5
343
      read_ce_cross_sections(nuc_temps, thermal_temps);
5,339✔
344
    } else {
5,339✔
345
      // Create material macroscopic data for MGXS
346
      set_mg_interface_nuclides_and_temps();
1,094✔
347
      data::mg.init();
1,094✔
348
      mark_fissionable_mgxs_materials();
1,094✔
349
    }
350
    simulation::time_read_xs.stop();
6,433✔
351
  }
352
}
6,709✔
353

354
void library_clear()
6,814✔
355
{
356
  data::libraries.clear();
6,814✔
357
  data::library_map.clear();
6,814✔
358
}
6,814✔
359

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