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

openmc-dev / openmc / 22231282333

20 Feb 2026 04:04PM UTC coverage: 81.861% (-0.2%) from 82.058%
22231282333

Pull #2693

github

web-flow
Merge e5188d202 into 53ce1910f
Pull Request #2693: Add reactivity control to coupled transport-depletion analyses

17278 of 24307 branches covered (71.08%)

Branch coverage included in aggregate %.

75 of 80 new or added lines in 4 files covered. (93.75%)

3462 existing lines in 80 files now uncovered.

57630 of 67199 relevant lines covered (85.76%)

49119197.82 hits per line

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

74.41
/src/material.cpp
1
#include "openmc/material.h"
2

3
#include <algorithm> // for min, max, sort, fill
4
#include <cassert>
5
#include <cmath>
6
#include <iterator>
7
#include <sstream>
8
#include <string>
9
#include <unordered_set>
10

11
#include "openmc/tensor.h"
12

13
#include "openmc/capi.h"
14
#include "openmc/container_util.h"
15
#include "openmc/cross_sections.h"
16
#include "openmc/error.h"
17
#include "openmc/file_utils.h"
18
#include "openmc/hdf5_interface.h"
19
#include "openmc/math_functions.h"
20
#include "openmc/message_passing.h"
21
#include "openmc/mgxs_interface.h"
22
#include "openmc/nuclide.h"
23
#include "openmc/photon.h"
24
#include "openmc/search.h"
25
#include "openmc/settings.h"
26
#include "openmc/simulation.h"
27
#include "openmc/string_utils.h"
28
#include "openmc/thermal.h"
29
#include "openmc/xml_interface.h"
30

31
namespace openmc {
32

33
//==============================================================================
34
// Global variables
35
//==============================================================================
36

37
namespace model {
38

39
std::unordered_map<int32_t, int32_t> material_map;
40
vector<unique_ptr<Material>> materials;
41

42
} // namespace model
43

44
//==============================================================================
45
// Material implementation
46
//==============================================================================
47

48
Material::Material(pugi::xml_node node)
14,382 ✔
49
{
50
  index_ = model::materials.size(); // Avoids warning about narrowing
14,382 ✔
51

52
  if (check_for_node(node, "id")) {
14,382 !
53
    this->set_id(std::stoi(get_node_value(node, "id")));
14,382 ✔
54
  } else {
UNCOV
55
    fatal_error("Must specify id of material in materials XML file.");
×
56
  }
57

58
  if (check_for_node(node, "name")) {
14,382 ✔
59
    name_ = get_node_value(node, "name");
7,406 ✔
60
  }
61

62
  if (check_for_node(node, "cfg")) {
14,382 ✔
63
    auto cfg = get_node_value(node, "cfg");
14 ✔
64
    write_message(
14 ✔
65
      5, "NCrystal config string for material #{}: '{}'", this->id(), cfg);
14 ✔
66
    ncrystal_mat_ = NCrystalMat(cfg);
14 ✔
67
  }
14 ✔
68

69
  if (check_for_node(node, "depletable")) {
14,382 ✔
70
    depletable_ = get_node_value_bool(node, "depletable");
5,112 ✔
71
  }
72

73
  bool sum_density {false};
14,382 ✔
74
  pugi::xml_node density_node = node.child("density");
14,382 ✔
75
  std::string units;
14,382 ✔
76
  if (density_node) {
14,382 !
77
    units = get_node_value(density_node, "units");
14,382 ✔
78
    if (units == "sum") {
14,382 ✔
79
      sum_density = true;
2,149 ✔
80
    } else if (units == "macro") {
12,233 ✔
81
      if (check_for_node(density_node, "value")) {
2,495 !
82
        density_ = std::stod(get_node_value(density_node, "value"));
2,495 ✔
83
      } else {
UNCOV
84
        density_ = 1.0;
×
85
      }
86
    } else {
87
      double val = std::stod(get_node_value(density_node, "value"));
9,738 ✔
88
      if (val <= 0.0) {
9,738 !
UNCOV
89
        fatal_error("Need to specify a positive density on material " +
×
UNCOV
90
                    std::to_string(id_) + ".");
×
91
      }
92

93
      if (units == "g/cc" || units == "g/cm3") {
9,738 ✔
94
        density_ = -val;
9,410 ✔
95
      } else if (units == "kg/m3") {
328 ✔
96
        density_ = -1.0e-3 * val;
14 ✔
97
      } else if (units == "atom/b-cm") {
314 ✔
98
        density_ = val;
300 ✔
99
      } else if (units == "atom/cc" || units == "atom/cm3") {
14 !
100
        density_ = 1.0e-24 * val;
14 ✔
101
      } else {
UNCOV
102
        fatal_error("Unknown units '" + units + "' specified on material " +
×
UNCOV
103
                    std::to_string(id_) + ".");
×
104
      }
105
    }
106
  } else {
UNCOV
107
    fatal_error("Must specify <density> element in material " +
×
UNCOV
108
                std::to_string(id_) + ".");
×
109
  }
110

111
  if (node.child("element")) {
14,382 !
UNCOV
112
    fatal_error(
×
UNCOV
113
      "Unable to add an element to material " + std::to_string(id_) +
×
114
      " since the element option has been removed from the xml input. "
115
      "Elements can only be added via the Python API, which will expand "
116
      "elements into their natural nuclides.");
117
  }
118

119
  // =======================================================================
120
  // READ AND PARSE <nuclide> TAGS
121

122
  // Check to ensure material has at least one nuclide
123
  if (!check_for_node(node, "nuclide") &&
16,877 !
124
      !check_for_node(node, "macroscopic")) {
2,495 !
UNCOV
125
    fatal_error("No macroscopic data or nuclides specified on material " +
×
UNCOV
126
                std::to_string(id_));
×
127
  }
128

129
  // Create list of macroscopic x/s based on those specified, just treat
130
  // them as nuclides. This is all really a facade so the user thinks they
131
  // are entering in macroscopic data but the code treats them the same
132
  // as nuclides internally.
133
  // Get pointer list of XML <macroscopic>
134
  auto node_macros = node.children("macroscopic");
14,382 ✔
135
  int num_macros = std::distance(node_macros.begin(), node_macros.end());
14,382 ✔
136

137
  vector<std::string> names;
14,382 ✔
138
  vector<double> densities;
14,382 ✔
139
  if (settings::run_CE && num_macros > 0) {
14,382 !
UNCOV
140
    fatal_error("Macroscopic can not be used in continuous-energy mode.");
×
141
  } else if (num_macros > 1) {
14,382 !
142
    fatal_error("Only one macroscopic object permitted per material, " +
×
UNCOV
143
                std::to_string(id_));
×
144
  } else if (num_macros == 1) {
14,382 ✔
145
    pugi::xml_node node_nuc = *node_macros.begin();
2,495 ✔
146

147
    // Check for empty name on nuclide
148
    if (!check_for_node(node_nuc, "name")) {
2,495 !
UNCOV
149
      fatal_error("No name specified on macroscopic data in material " +
×
UNCOV
150
                  std::to_string(id_));
×
151
    }
152

153
    // store nuclide name
154
    std::string name = get_node_value(node_nuc, "name", false, true);
2,495 ✔
155
    names.push_back(name);
2,495 ✔
156

157
    // Set density for macroscopic data
158
    if (units == "macro") {
2,495 !
159
      densities.push_back(density_);
2,495 ✔
160
    } else {
UNCOV
161
      fatal_error("Units can only be macro for macroscopic data " + name);
×
162
    }
163
  } else {
2,495 ✔
164
    // Create list of nuclides based on those specified
165
    for (auto node_nuc : node.children("nuclide")) {
57,584 ✔
166
      // Check for empty name on nuclide
167
      if (!check_for_node(node_nuc, "name")) {
45,697 !
UNCOV
168
        fatal_error(
×
UNCOV
169
          "No name specified on nuclide in material " + std::to_string(id_));
×
170
      }
171

172
      // store nuclide name
173
      std::string name = get_node_value(node_nuc, "name", false, true);
45,697 ✔
174
      names.push_back(name);
45,697 ✔
175

176
      // Check if no atom/weight percents were specified or if both atom and
177
      // weight percents were specified
178
      if (units == "macro") {
45,697 !
UNCOV
179
        densities.push_back(density_);
×
180
      } else {
181
        bool has_ao = check_for_node(node_nuc, "ao");
45,697 ✔
182
        bool has_wo = check_for_node(node_nuc, "wo");
45,697 ✔
183

184
        if (!has_ao && !has_wo) {
45,697 !
UNCOV
185
          fatal_error(
×
UNCOV
186
            "No atom or weight percent specified for nuclide: " + name);
×
187
        } else if (has_ao && has_wo) {
45,697 !
188
          fatal_error("Cannot specify both atom and weight percents for a "
×
UNCOV
189
                      "nuclide: " +
×
190
                      name);
191
        }
192

193
        // Copy atom/weight percents
194
        if (has_ao) {
45,697 ✔
195
          densities.push_back(std::stod(get_node_value(node_nuc, "ao")));
38,257 ✔
196
        } else {
197
          densities.push_back(-std::stod(get_node_value(node_nuc, "wo")));
7,440 ✔
198
        }
199
      }
200
    }
45,697 ✔
201
  }
202

203
  // =======================================================================
204
  // READ AND PARSE <isotropic> element
205

206
  vector<std::string> iso_lab;
14,382 ✔
207
  if (check_for_node(node, "isotropic")) {
14,382 ✔
208
    iso_lab = get_node_array<std::string>(node, "isotropic");
168 ✔
209
  }
210

211
  // ========================================================================
212
  // COPY NUCLIDES TO ARRAYS IN MATERIAL
213

214
  // allocate arrays in Material object
215
  auto n = names.size();
14,382 ✔
216
  nuclide_.reserve(n);
14,382 ✔
217
  atom_density_ = tensor::Tensor<double>({n});
14,382 ✔
218
  if (settings::photon_transport)
14,382 ✔
219
    element_.reserve(n);
284 ✔
220

221
  for (int i = 0; i < n; ++i) {
62,574 ✔
222
    const auto& name {names[i]};
48,192 ✔
223

224
    // Check that this nuclide is listed in the nuclear data library
225
    // (cross_sections.xml for CE and the MGXS HDF5 for MG)
226
    if (settings::run_mode != RunMode::PLOTTING) {
48,192 ✔
227
      LibraryKey key {Library::Type::neutron, name};
47,408 ✔
228
      if (data::library_map.find(key) == data::library_map.end()) {
47,408 !
UNCOV
229
        fatal_error("Could not find nuclide " + name +
×
230
                    " in the "
231
                    "nuclear data library.");
232
      }
233
    }
47,408 ✔
234

235
    // If this nuclide hasn't been encountered yet, we need to add its name
236
    // and alias to the nuclide_dict
237
    if (data::nuclide_map.find(name) == data::nuclide_map.end()) {
48,192 ✔
238
      int index = data::nuclide_map.size();
29,264 ✔
239
      data::nuclide_map[name] = index;
29,264 ✔
240
      nuclide_.push_back(index);
29,264 ✔
241
    } else {
242
      nuclide_.push_back(data::nuclide_map[name]);
18,928 ✔
243
    }
244

245
    // If the corresponding element hasn't been encountered yet and photon
246
    // transport will be used, we need to add its symbol to the element_dict
247
    if (settings::photon_transport) {
48,192 ✔
248
      std::string element = to_element(name);
1,206 ✔
249

250
      // Make sure photon cross section data is available
251
      if (settings::run_mode != RunMode::PLOTTING) {
1,206 !
252
        LibraryKey key {Library::Type::photon, element};
1,206 ✔
253
        if (data::library_map.find(key) == data::library_map.end()) {
1,206 !
UNCOV
254
          fatal_error(
×
UNCOV
255
            "Could not find element " + element + " in cross_sections.xml.");
×
256
        }
257
      }
1,206 ✔
258

259
      if (data::element_map.find(element) == data::element_map.end()) {
1,206 ✔
260
        int index = data::element_map.size();
624 ✔
261
        data::element_map[element] = index;
624 ✔
262
        element_.push_back(index);
624 ✔
263
      } else {
264
        element_.push_back(data::element_map[element]);
582 ✔
265
      }
266
    }
1,206 ✔
267

268
    // Copy atom/weight percent
269
    atom_density_(i) = densities[i];
48,192 ✔
270
  }
271

272
  if (settings::run_CE) {
14,382 ✔
273
    // By default, isotropic-in-lab is not used
274
    if (iso_lab.size() > 0) {
11,705 ✔
275
      p0_.resize(n);
168 ✔
276

277
      // Apply isotropic-in-lab treatment to specified nuclides
278
      for (int j = 0; j < n; ++j) {
1,582 ✔
279
        for (const auto& nuc : iso_lab) {
7,350 !
280
          if (names[j] == nuc) {
7,350 ✔
281
            p0_[j] = true;
1,414 ✔
282
            break;
1,414 ✔
283
          }
284
        }
285
      }
286
    }
287
  }
288

289
  // Check to make sure either all atom percents or all weight percents are
290
  // given
291
  if (!((atom_density_ >= 0.0).all() || (atom_density_ <= 0.0).all())) {
14,382 !
UNCOV
292
    fatal_error(
×
UNCOV
293
      "Cannot mix atom and weight percents in material " + std::to_string(id_));
×
294
  }
295

296
  // Determine density if it is a sum value
297
  if (sum_density)
14,382 ✔
298
    density_ = atom_density_.sum();
2,149 ✔
299

300
  if (check_for_node(node, "temperature")) {
14,382 ✔
301
    temperature_ = std::stod(get_node_value(node, "temperature"));
1,725 ✔
302
  }
303

304
  if (check_for_node(node, "volume")) {
14,382 ✔
305
    volume_ = std::stod(get_node_value(node, "volume"));
1,824 ✔
306
  }
307

308
  // =======================================================================
309
  // READ AND PARSE <sab> TAG FOR THERMAL SCATTERING DATA
310
  if (settings::run_CE) {
14,382 ✔
311
    // Loop over <sab> elements
312

313
    vector<std::string> sab_names;
11,705 ✔
314
    for (auto node_sab : node.children("sab")) {
13,600 ✔
315
      // Determine name of thermal scattering table
316
      if (!check_for_node(node_sab, "name")) {
1,895 !
UNCOV
317
        fatal_error("Need to specify <name> for thermal scattering table.");
×
318
      }
319
      std::string name = get_node_value(node_sab, "name");
1,895 ✔
320
      sab_names.push_back(name);
1,895 ✔
321

322
      // Read the fraction of nuclei affected by this thermal scattering table
323
      double fraction = 1.0;
1,895 ✔
324
      if (check_for_node(node_sab, "fraction")) {
1,895 ✔
325
        fraction = std::stod(get_node_value(node_sab, "fraction"));
14 ✔
326
      }
327

328
      // Check that the thermal scattering table is listed in the
329
      // cross_sections.xml file
330
      if (settings::run_mode != RunMode::PLOTTING) {
1,895 ✔
331
        LibraryKey key {Library::Type::thermal, name};
1,847 ✔
332
        if (data::library_map.find(key) == data::library_map.end()) {
1,847 !
UNCOV
333
          fatal_error("Could not find thermal scattering data " + name +
×
334
                      " in cross_sections.xml file.");
335
        }
336
      }
1,847 ✔
337

338
      // Determine index of thermal scattering data in global
339
      // data::thermal_scatt array
340
      int index_table;
341
      if (data::thermal_scatt_map.find(name) == data::thermal_scatt_map.end()) {
1,895 ✔
342
        index_table = data::thermal_scatt_map.size();
1,232 ✔
343
        data::thermal_scatt_map[name] = index_table;
1,232 ✔
344
      } else {
345
        index_table = data::thermal_scatt_map[name];
663 ✔
346
      }
347

348
      // Add entry to thermal tables vector. For now, we put the nuclide index
349
      // as zero since we don't know which nuclides the table is being applied
350
      // to yet (this is assigned in init_thermal)
351
      thermal_tables_.push_back({index_table, 0, fraction});
1,895 ✔
352
    }
1,895 ✔
353
  }
11,705 ✔
354
}
14,382 ✔
355

356
Material::~Material()
14,422 ✔
357
{
358
  model::material_map.erase(id_);
14,422 ✔
359
}
14,422 ✔
360

UNCOV
361
Material& Material::clone()
×
362
{
363
  std::unique_ptr<Material> mat = std::make_unique<Material>();
×
364

365
  // set all other parameters to whatever the calling Material has
UNCOV
366
  mat->name_ = name_;
×
UNCOV
367
  mat->nuclide_ = nuclide_;
×
368
  mat->element_ = element_;
×
369
  mat->ncrystal_mat_ = ncrystal_mat_.clone();
×
370
  mat->atom_density_ = atom_density_;
×
371
  mat->density_ = density_;
×
372
  mat->density_gpcc_ = density_gpcc_;
×
373
  mat->volume_ = volume_;
×
374
  mat->fissionable() = fissionable_;
×
375
  mat->depletable() = depletable_;
×
376
  mat->p0_ = p0_;
×
377
  mat->mat_nuclide_index_ = mat_nuclide_index_;
×
378
  mat->thermal_tables_ = thermal_tables_;
×
379
  mat->temperature_ = temperature_;
×
380

381
  if (ttb_)
×
UNCOV
382
    mat->ttb_ = std::make_unique<Bremsstrahlung>(*ttb_);
×
383

384
  mat->index_ = model::materials.size();
×
UNCOV
385
  mat->set_id(C_NONE);
×
386
  model::materials.push_back(std::move(mat));
×
387
  return *model::materials.back();
×
388
}
×
389

390
void Material::finalize()
14,120 ✔
391
{
392
  // Set fissionable if any nuclide is fissionable
393
  if (settings::run_CE) {
14,120 ✔
394
    for (const auto& i_nuc : nuclide_) {
40,292 ✔
395
      if (data::nuclides[i_nuc]->fissionable_) {
33,966 ✔
396
        fissionable_ = true;
5,117 ✔
397
        break;
5,117 ✔
398
      }
399
    }
400

401
    // Generate material bremsstrahlung data for electrons and positrons
402
    if (settings::photon_transport &&
11,443 ✔
403
        settings::electron_treatment == ElectronTreatment::TTB) {
284 ✔
404
      this->init_bremsstrahlung();
244 ✔
405
    }
406

407
    // Assign thermal scattering tables
408
    this->init_thermal();
11,443 ✔
409
  }
410

411
  // Normalize density
412
  this->normalize_density();
14,120 ✔
413
}
14,120 ✔
414

415
void Material::normalize_density()
14,120 ✔
416
{
417
  bool percent_in_atom = (atom_density_(0) >= 0.0);
14,120 ✔
418
  bool density_in_atom = (density_ >= 0.0);
14,120 ✔
419

420
  for (int i = 0; i < nuclide_.size(); ++i) {
61,531 ✔
421
    // determine atomic weight ratio
422
    int i_nuc = nuclide_[i];
47,411 ✔
423
    double awr = settings::run_CE ? data::nuclides[i_nuc]->awr_
50,424 ✔
424
                                  : data::mg.nuclides_[i_nuc].awr;
3,013 ✔
425

426
    // if given weight percent, convert all values so that they are divided
427
    // by awr. thus, when a sum is done over the values, it's actually
428
    // sum(w/awr)
429
    if (!percent_in_atom)
47,411 ✔
430
      atom_density_(i) = -atom_density_(i) / awr;
7,440 ✔
431
  }
432

433
  // determine normalized atom percents. if given atom percents, this is
434
  // straightforward. if given weight percents, the value is w/awr and is
435
  // divided by sum(w/awr)
436
  atom_density_ /= atom_density_.sum();
14,120 ✔
437

438
  // Change density in g/cm^3 to atom/b-cm. Since all values are now in
439
  // atom percent, the sum needs to be re-evaluated as 1/sum(x*awr)
440
  if (!density_in_atom) {
14,120 ✔
441
    double sum_percent = 0.0;
9,158 ✔
442
    for (int i = 0; i < nuclide_.size(); ++i) {
40,225 ✔
443
      int i_nuc = nuclide_[i];
31,067 ✔
444
      double awr = settings::run_CE ? data::nuclides[i_nuc]->awr_
31,137 ✔
445
                                    : data::mg.nuclides_[i_nuc].awr;
70 ✔
446
      sum_percent += atom_density_(i) * awr;
31,067 ✔
447
    }
448
    sum_percent = 1.0 / sum_percent;
9,158 ✔
449
    density_ = -density_ * N_AVOGADRO / MASS_NEUTRON * sum_percent;
9,158 ✔
450
  }
451

452
  // Calculate nuclide atom densities
453
  atom_density_ *= density_;
14,120 ✔
454

455
  // Calculate density in [g/cm^3] and charge density in [e/b-cm]
456
  density_gpcc_ = 0.0;
14,120 ✔
457
  charge_density_ = 0.0;
14,120 ✔
458
  for (int i = 0; i < nuclide_.size(); ++i) {
61,531 ✔
459
    int i_nuc = nuclide_[i];
47,411 ✔
460
    double awr = settings::run_CE ? data::nuclides[i_nuc]->awr_ : 1.0;
47,411 ✔
461
    int z = settings::run_CE ? data::nuclides[i_nuc]->Z_ : 0.0;
47,411 ✔
462
    density_gpcc_ += atom_density_(i) * awr * MASS_NEUTRON / N_AVOGADRO;
47,411 ✔
463
    charge_density_ += atom_density_(i) * z;
47,411 ✔
464
  }
465
}
14,120 ✔
466

467
void Material::init_thermal()
16,961 ✔
468
{
469
  vector<ThermalTable> tables;
16,961 ✔
470

471
  std::unordered_set<int> already_checked;
16,961 ✔
472
  for (const auto& table : thermal_tables_) {
18,819 ✔
473
    // Make sure each S(a,b) table only gets checked once
474
    if (already_checked.find(table.index_table) != already_checked.end()) {
1,858 !
UNCOV
475
      continue;
×
476
    }
477
    already_checked.insert(table.index_table);
1,858 ✔
478

479
    // In order to know which nuclide the S(a,b) table applies to, we need
480
    // to search through the list of nuclides for one which has a matching
481
    // name
482
    bool found = false;
1,858 ✔
483
    for (int j = 0; j < nuclide_.size(); ++j) {
11,787 ✔
484
      const auto& name {data::nuclides[nuclide_[j]]->name_};
9,929 ✔
485
      if (contains(data::thermal_scatt[table.index_table]->nuclides_, name)) {
9,929 ✔
486
        tables.push_back({table.index_table, j, table.fraction});
1,914 ✔
487
        found = true;
1,914 ✔
488
      }
489
    }
490

491
    // Check to make sure thermal scattering table matched a nuclide
492
    if (!found) {
1,858 !
UNCOV
493
      fatal_error("Thermal scattering table " +
×
UNCOV
494
                  data::thermal_scatt[table.index_table]->name_ +
×
495
                  " did not match any nuclide on material " +
×
496
                  std::to_string(id_));
×
497
    }
498
  }
499

500
  // Make sure each nuclide only appears in one table.
501
  for (int j = 0; j < tables.size(); ++j) {
18,875 ✔
502
    for (int k = j + 1; k < tables.size(); ++k) {
2,138 ✔
503
      if (tables[j].index_nuclide == tables[k].index_nuclide) {
224 !
UNCOV
504
        int index = nuclide_[tables[j].index_nuclide];
×
UNCOV
505
        auto name = data::nuclides[index]->name_;
×
506
        fatal_error(
×
507
          name + " in material " + std::to_string(id_) +
×
508
          " was found "
509
          "in multiple thermal scattering tables. Each nuclide can appear in "
510
          "only one table per material.");
UNCOV
511
      }
×
512
    }
513
  }
514

515
  // If there are multiple S(a,b) tables, we need to make sure that the
516
  // entries in i_sab_nuclides are sorted or else they won't be applied
517
  // correctly in the cross_section module.
518
  std::sort(tables.begin(), tables.end(), [](ThermalTable a, ThermalTable b) {
154 ✔
519
    return a.index_nuclide < b.index_nuclide;
154 ✔
520
  });
521

522
  // Update the list of thermal tables
523
  thermal_tables_ = tables;
16,961 ✔
524
}
16,961 ✔
525

526
void Material::collision_stopping_power(double* s_col, bool positron)
488 ✔
527
{
528
  // Average electron number and average atomic weight
529
  double electron_density = 0.0;
488 ✔
530
  double mass_density = 0.0;
488 ✔
531

532
  // Log of the mean excitation energy of the material
533
  double log_I = 0.0;
488 ✔
534

535
  // Effective number of conduction electrons in the material
536
  double n_conduction = 0.0;
488 ✔
537

538
  // Oscillator strength and square of the binding energy for each oscillator
539
  // in material
540
  vector<double> f;
488 ✔
541
  vector<double> e_b_sq;
488 ✔
542

543
  for (int i = 0; i < element_.size(); ++i) {
2,340 ✔
544
    const auto& elm = *data::elements[element_[i]];
1,852 ✔
545
    double awr = data::nuclides[nuclide_[i]]->awr_;
1,852 ✔
546

547
    // Get atomic density of nuclide given atom/weight percent
548
    double atom_density =
549
      (atom_density_[0] > 0.0) ? atom_density_[i] : -atom_density_[i] / awr;
1,852 !
550

551
    electron_density += atom_density * elm.Z_;
1,852 ✔
552
    mass_density += atom_density * awr * MASS_NEUTRON;
1,852 ✔
553
    log_I += atom_density * elm.Z_ * std::log(elm.I_);
1,852 ✔
554

555
    for (int j = 0; j < elm.n_electrons_.size(); ++j) {
14,648 ✔
556
      if (elm.n_electrons_[j] < 0) {
12,796 ✔
557
        n_conduction -= elm.n_electrons_[j] * atom_density;
1,304 ✔
558
        continue;
1,304 ✔
559
      }
560
      e_b_sq.push_back(elm.ionization_energy_[j] * elm.ionization_energy_[j]);
11,492 ✔
561
      f.push_back(elm.n_electrons_[j] * atom_density);
11,492 ✔
562
    }
563
  }
564
  log_I /= electron_density;
488 ✔
565
  n_conduction /= electron_density;
488 ✔
566
  for (auto& f_i : f)
11,980 ✔
567
    f_i /= electron_density;
11,492 ✔
568

569
  // Get density in g/cm^3 if it is given in atom/b-cm
570
  double density = (density_ < 0.0) ? -density_ : mass_density / N_AVOGADRO;
488 ✔
571

572
  // Calculate the square of the plasma energy
573
  double e_p_sq =
488 ✔
574
    PLANCK_C * PLANCK_C * PLANCK_C * N_AVOGADRO * electron_density * density /
488 ✔
575
    (2.0 * PI * PI * FINE_STRUCTURE * MASS_ELECTRON_EV * mass_density);
488 ✔
576

577
  // Get the Sternheimer adjustment factor
578
  double rho =
579
    sternheimer_adjustment(f, e_b_sq, e_p_sq, n_conduction, log_I, 1.0e-6, 100);
488 ✔
580

581
  // Classical electron radius in cm
582
  constexpr double CM_PER_ANGSTROM {1.0e-8};
488 ✔
583
  constexpr double r_e =
488 ✔
584
    CM_PER_ANGSTROM * PLANCK_C / (2.0 * PI * FINE_STRUCTURE * MASS_ELECTRON_EV);
585

586
  // Constant in expression for collision stopping power
587
  constexpr double BARN_PER_CM_SQ {1.0e24};
488 ✔
588
  double c =
488 ✔
589
    BARN_PER_CM_SQ * 2.0 * PI * r_e * r_e * MASS_ELECTRON_EV * electron_density;
590

591
  // Loop over incident charged particle energies
592
  for (int i = 0; i < data::ttb_e_grid.size(); ++i) {
97,796 ✔
593
    double E = data::ttb_e_grid(i);
97,308 ✔
594

595
    // Get the density effect correction
596
    double delta =
597
      density_effect(f, e_b_sq, e_p_sq, n_conduction, rho, E, 1.0e-6, 100);
97,308 ✔
598

599
    // Square of the ratio of the speed of light to the velocity of the charged
600
    // particle
601
    double beta_sq = E * (E + 2.0 * MASS_ELECTRON_EV) /
97,308 ✔
602
                     ((E + MASS_ELECTRON_EV) * (E + MASS_ELECTRON_EV));
97,308 ✔
603

604
    double tau = E / MASS_ELECTRON_EV;
97,308 ✔
605

606
    double F;
607
    if (positron) {
97,308 ✔
608
      double t = tau + 2.0;
48,654 ✔
609
      F = std::log(4.0) - (beta_sq / 12.0) * (23.0 + 14.0 / t + 10.0 / (t * t) +
48,654 ✔
610
                                               4.0 / (t * t * t));
48,654 ✔
611
    } else {
612
      F = (1.0 - beta_sq) *
48,654 ✔
613
          (1.0 + tau * tau / 8.0 - (2.0 * tau + 1.0) * std::log(2.0));
48,654 ✔
614
    }
615

616
    // Calculate the collision stopping power for this energy
617
    s_col[i] =
97,308 ✔
618
      c / beta_sq *
97,308 ✔
619
      (2.0 * (std::log(E) - log_I) + std::log(1.0 + tau / 2.0) + F - delta);
97,308 ✔
620
  }
621
}
488 ✔
622

623
void Material::init_bremsstrahlung()
244 ✔
624
{
625
  // Create new object
626
  ttb_ = make_unique<Bremsstrahlung>();
244 ✔
627

628
  // Get the size of the energy grids
629
  auto n_k = data::ttb_k_grid.size();
244 ✔
630
  auto n_e = data::ttb_e_grid.size();
244 ✔
631

632
  // Determine number of elements
633
  int n = element_.size();
244 ✔
634

635
  for (int particle = 0; particle < 2; ++particle) {
732 ✔
636
    // Loop over logic twice, once for electron, once for positron
637
    BremsstrahlungData* ttb =
638
      (particle == 0) ? &ttb_->electron : &ttb_->positron;
488 ✔
639
    bool positron = (particle == 1);
488 ✔
640

641
    // Allocate arrays for TTB data
642
    ttb->pdf = tensor::zeros<double>({n_e, n_e});
488 ✔
643
    ttb->cdf = tensor::zeros<double>({n_e, n_e});
488 ✔
644
    ttb->yield = tensor::zeros<double>({n_e});
488 ✔
645

646
    // Allocate temporary arrays
647
    auto stopping_power_collision = tensor::zeros<double>({n_e});
488 ✔
648
    auto stopping_power_radiative = tensor::zeros<double>({n_e});
488 ✔
649
    auto dcs = tensor::zeros<double>({n_e, n_k});
488 ✔
650

651
    double Z_eq_sq = 0.0;
488 ✔
652
    double sum_density = 0.0;
488 ✔
653

654
    // Get the collision stopping power of the material
655
    this->collision_stopping_power(stopping_power_collision.data(), positron);
488 ✔
656

657
    // Calculate the molecular DCS and the molecular radiative stopping power
658
    // using Bragg's additivity rule.
659
    for (int i = 0; i < n; ++i) {
2,340 ✔
660
      // Get pointer to current element
661
      const auto& elm = *data::elements[element_[i]];
1,852 ✔
662
      double awr = data::nuclides[nuclide_[i]]->awr_;
1,852 ✔
663

664
      // Get atomic density and mass density of nuclide given atom/weight
665
      // percent
666
      double atom_density =
667
        (atom_density_[0] > 0.0) ? atom_density_[i] : -atom_density_[i] / awr;
1,852 !
668

669
      // Calculate the "equivalent" atomic number Zeq of the material
670
      Z_eq_sq += atom_density * elm.Z_ * elm.Z_;
1,852 ✔
671
      sum_density += atom_density;
1,852 ✔
672

673
      // Accumulate material DCS
674
      dcs += (atom_density * elm.Z_ * elm.Z_) * elm.dcs_;
1,852 ✔
675

676
      // Accumulate material radiative stopping power
677
      stopping_power_radiative += atom_density * elm.stopping_power_radiative_;
1,852 ✔
678
    }
679
    Z_eq_sq /= sum_density;
488 ✔
680

681
    // Calculate the positron DCS and radiative stopping power. These are
682
    // obtained by multiplying the electron DCS and radiative stopping powers by
683
    // a factor r, which is a numerical approximation of the ratio of the
684
    // radiative stopping powers for positrons and electrons. Source: F. Salvat,
685
    // J. M. Fernández-Varea, and J. Sempau, "PENELOPE-2011: A Code System for
686
    // Monte Carlo Simulation of Electron and Photon Transport," OECD-NEA,
687
    // Issy-les-Moulineaux, France (2011).
688
    if (positron) {
488 ✔
689
      for (int i = 0; i < n_e; ++i) {
48,898 ✔
690
        double t = std::log(
48,654 ✔
691
          1.0 + 1.0e6 * data::ttb_e_grid(i) / (Z_eq_sq * MASS_ELECTRON_EV));
48,654 ✔
692
        double r =
693
          1.0 -
694
          std::exp(-1.2359e-1 * t + 6.1274e-2 * std::pow(t, 2) -
48,654 ✔
695
                   3.1516e-2 * std::pow(t, 3) + 7.7446e-3 * std::pow(t, 4) -
48,654 ✔
696
                   1.0595e-3 * std::pow(t, 5) + 7.0568e-5 * std::pow(t, 6) -
48,654 ✔
697
                   1.808e-6 * std::pow(t, 7));
48,654 ✔
698
        stopping_power_radiative(i) *= r;
48,654 ✔
699
        tensor::View<double> dcs_i = dcs.slice(i);
48,654 ✔
700
        dcs_i *= r;
48,654 ✔
701
      }
48,654 ✔
702
    }
703

704
    // Total material stopping power
705
    tensor::Tensor<double> stopping_power =
706
      stopping_power_collision + stopping_power_radiative;
488 ✔
707

708
    // Loop over photon energies
709
    auto f = tensor::zeros<double>({n_e});
488 ✔
710
    auto z = tensor::zeros<double>({n_e});
488 ✔
711
    for (int i = 0; i < n_e - 1; ++i) {
97,308 ✔
712
      double w = data::ttb_e_grid(i);
96,820 ✔
713

714
      // Loop over incident particle energies
715
      for (int j = i; j < n_e; ++j) {
9,849,222 ✔
716
        double e = data::ttb_e_grid(j);
9,752,402 ✔
717

718
        // Reduced photon energy
719
        double k = w / e;
9,752,402 ✔
720

721
        // Find the lower bounding index of the reduced photon energy
722
        int i_k = lower_bound_index(
9,752,402 ✔
723
          data::ttb_k_grid.cbegin(), data::ttb_k_grid.cend(), k);
9,752,402 ✔
724

725
        // Get the interpolation bounds
726
        double k_l = data::ttb_k_grid(i_k);
9,752,402 ✔
727
        double k_r = data::ttb_k_grid(i_k + 1);
9,752,402 ✔
728
        double x_l = dcs(j, i_k);
9,752,402 ✔
729
        double x_r = dcs(j, i_k + 1);
9,752,402 ✔
730

731
        // Find the value of the DCS using linear interpolation in reduced
732
        // photon energy k
733
        double x = x_l + (k - k_l) * (x_r - x_l) / (k_r - k_l);
9,752,402 ✔
734

735
        // Square of the ratio of the speed of light to the velocity of the
736
        // charged particle
737
        double beta_sq = e * (e + 2.0 * MASS_ELECTRON_EV) /
9,752,402 ✔
738
                         ((e + MASS_ELECTRON_EV) * (e + MASS_ELECTRON_EV));
9,752,402 ✔
739

740
        // Compute the integrand of the PDF
741
        f(j) = x / (beta_sq * stopping_power(j) * w);
9,752,402 ✔
742
      }
743

744
      // Number of points to integrate
745
      int n = n_e - i;
96,820 ✔
746

747
      // Integrate the PDF using cubic spline integration over the incident
748
      // particle energy
749
      if (n > 2) {
96,820 ✔
750
        spline(n, &data::ttb_e_grid(i), &f(i), &z(i));
96,332 ✔
751

752
        double c = 0.0;
96,332 ✔
753
        for (int j = i; j < n_e - 1; ++j) {
9,751,426 ✔
754
          c += spline_integrate(n, &data::ttb_e_grid(i), &f(i), &z(i),
9,655,094 ✔
755
            data::ttb_e_grid(j), data::ttb_e_grid(j + 1));
9,655,094 ✔
756

757
          ttb->pdf(j + 1, i) = c;
9,655,094 ✔
758
        }
759

760
        // Integrate the last two points using trapezoidal rule in log-log space
761
      } else {
762
        double e_l = std::log(data::ttb_e_grid(i));
488 ✔
763
        double e_r = std::log(data::ttb_e_grid(i + 1));
488 ✔
764
        double x_l = std::log(f(i));
488 ✔
765
        double x_r = std::log(f(i + 1));
488 ✔
766

767
        ttb->pdf(i + 1, i) =
488 ✔
768
          0.5 * (e_r - e_l) * (std::exp(e_l + x_l) + std::exp(e_r + x_r));
488 ✔
769
      }
770
    }
771

772
    // Loop over incident particle energies
773
    for (int j = 1; j < n_e; ++j) {
97,308 ✔
774
      // Set last element of PDF to small non-zero value to enable log-log
775
      // interpolation
776
      ttb->pdf(j, j) = std::exp(-500.0);
96,820 ✔
777

778
      // Loop over photon energies
779
      double c = 0.0;
96,820 ✔
780
      for (int i = 0; i < j; ++i) {
9,752,402 ✔
781
        // Integrate the CDF from the PDF using the fact that the PDF is linear
782
        // in log-log space
783
        double w_l = std::log(data::ttb_e_grid(i));
9,655,582 ✔
784
        double w_r = std::log(data::ttb_e_grid(i + 1));
9,655,582 ✔
785
        double x_l = std::log(ttb->pdf(j, i));
9,655,582 ✔
786
        double x_r = std::log(ttb->pdf(j, i + 1));
9,655,582 ✔
787
        double beta = (x_r - x_l) / (w_r - w_l);
9,655,582 ✔
788
        double a = beta + 1.0;
9,655,582 ✔
789
        c += std::exp(w_l + x_l) / a * std::expm1(a * (w_r - w_l));
9,655,582 ✔
790
        ttb->cdf(j, i + 1) = c;
9,655,582 ✔
791
      }
792

793
      // Set photon number yield
794
      ttb->yield(j) = c;
96,820 ✔
795
    }
796

797
    // Use logarithm of number yield since it is log-log interpolated
798
    ttb->yield =
799
      tensor::where(ttb->yield > 0.0, tensor::log(ttb->yield), -500.0);
488 ✔
800
  }
488 ✔
801
}
244 ✔
802

803
void Material::init_nuclide_index()
17,652 ✔
804
{
805
  int n = settings::run_CE ? data::nuclides.size() : data::mg.nuclides_.size();
17,652 ✔
806
  mat_nuclide_index_.resize(n);
17,652 ✔
807
  std::fill(mat_nuclide_index_.begin(), mat_nuclide_index_.end(), C_NONE);
17,652 ✔
808
  for (int i = 0; i < nuclide_.size(); ++i) {
104,774 ✔
809
    mat_nuclide_index_[nuclide_[i]] = i;
87,122 ✔
810
  }
811
}
17,652 ✔
812

813
void Material::calculate_xs(Particle& p) const
2,147,483,647 ✔
814
{
815
  // Set all material macroscopic cross sections to zero
816
  p.macro_xs().total = 0.0;
2,147,483,647 ✔
817
  p.macro_xs().absorption = 0.0;
2,147,483,647 ✔
818
  p.macro_xs().fission = 0.0;
2,147,483,647 ✔
819
  p.macro_xs().nu_fission = 0.0;
2,147,483,647 ✔
820

821
  if (p.type().is_neutron()) {
2,147,483,647 ✔
822
    this->calculate_neutron_xs(p);
2,147,483,647 ✔
823
  } else if (p.type().is_photon()) {
65,462,152 ✔
824
    this->calculate_photon_xs(p);
16,452,693 ✔
825
  }
826
}
2,147,483,647 ✔
827

828
void Material::calculate_neutron_xs(Particle& p) const
2,147,483,647 ✔
829
{
830
  // Find energy index on energy grid
831
  int neutron = ParticleType::neutron().transport_index();
2,147,483,647 ✔
832
  int i_grid =
833
    std::log(p.E() / data::energy_min[neutron]) / simulation::log_spacing;
2,147,483,647 ✔
834

835
  // Determine if this material has S(a,b) tables
836
  bool check_sab = (thermal_tables_.size() > 0);
2,147,483,647 ✔
837

838
  // Initialize position in i_sab_nuclides
839
  int j = 0;
2,147,483,647 ✔
840

841
  // Calculate NCrystal cross section
842
  double ncrystal_xs = -1.0;
2,147,483,647 ✔
843
  if (ncrystal_mat_ && p.E() < NCRYSTAL_MAX_ENERGY) {
2,147,483,647 !
844
    ncrystal_xs = ncrystal_mat_.xs(p);
10,144,390 ✔
845
  }
846

847
  // Add contribution from each nuclide in material
848
  for (int i = 0; i < nuclide_.size(); ++i) {
2,147,483,647 ✔
849
    // ======================================================================
850
    // CHECK FOR S(A,B) TABLE
851

852
    int i_sab = C_NONE;
2,147,483,647 ✔
853
    double sab_frac = 0.0;
2,147,483,647 ✔
854

855
    // Check if this nuclide matches one of the S(a,b) tables specified.
856
    // This relies on thermal_tables_ being sorted by .index_nuclide
857
    if (check_sab) {
2,147,483,647 ✔
858
      const auto& sab {thermal_tables_[j]};
753,826,219 ✔
859
      if (i == sab.index_nuclide) {
753,826,219 ✔
860
        // Get index in sab_tables
861
        i_sab = sab.index_table;
380,396,619 ✔
862
        sab_frac = sab.fraction;
380,396,619 ✔
863

864
        // If particle energy is greater than the highest energy for the
865
        // S(a,b) table, then don't use the S(a,b) table
866
        if (p.E() > data::thermal_scatt[i_sab]->energy_max_)
380,396,619 ✔
867
          i_sab = C_NONE;
226,892,148 ✔
868

869
        // Increment position in thermal_tables_
870
        ++j;
380,396,619 ✔
871

872
        // Don't check for S(a,b) tables if there are no more left
873
        if (j == thermal_tables_.size())
380,396,619 ✔
874
          check_sab = false;
380,184,309 ✔
875
      }
876
    }
877

878
    // ======================================================================
879
    // CALCULATE MICROSCOPIC CROSS SECTION
880

881
    // Get nuclide index
882
    int i_nuclide = nuclide_[i];
2,147,483,647 ✔
883

884
    // Update microscopic cross section for this nuclide
885
    p.update_neutron_xs(i_nuclide, i_grid, i_sab, sab_frac, ncrystal_xs);
2,147,483,647 ✔
886
    auto& micro = p.neutron_xs(i_nuclide);
2,147,483,647 ✔
887

888
    // ======================================================================
889
    // ADD TO MACROSCOPIC CROSS SECTION
890

891
    // Copy atom density of nuclide in material
892
    double atom_density = this->atom_density(i, p.density_mult());
2,147,483,647 ✔
893

894
    // Add contributions to cross sections
895
    p.macro_xs().total += atom_density * micro.total;
2,147,483,647 ✔
896
    p.macro_xs().absorption += atom_density * micro.absorption;
2,147,483,647 ✔
897
    p.macro_xs().fission += atom_density * micro.fission;
2,147,483,647 ✔
898
    p.macro_xs().nu_fission += atom_density * micro.nu_fission;
2,147,483,647 ✔
899
  }
900
}
2,147,483,647 ✔
901

902
void Material::calculate_photon_xs(Particle& p) const
16,452,693 ✔
903
{
904
  p.macro_xs().coherent = 0.0;
16,452,693 ✔
905
  p.macro_xs().incoherent = 0.0;
16,452,693 ✔
906
  p.macro_xs().photoelectric = 0.0;
16,452,693 ✔
907
  p.macro_xs().pair_production = 0.0;
16,452,693 ✔
908

909
  // Add contribution from each nuclide in material
910
  for (int i = 0; i < nuclide_.size(); ++i) {
89,487,130 ✔
911
    // ========================================================================
912
    // CALCULATE MICROSCOPIC CROSS SECTION
913

914
    // Determine microscopic cross sections for this nuclide
915
    int i_element = element_[i];
73,034,437 ✔
916

917
    // Calculate microscopic cross section for this nuclide
918
    const auto& micro {p.photon_xs(i_element)};
73,034,437 ✔
919
    if (p.E() != micro.last_E) {
73,034,437 ✔
920
      data::elements[i_element]->calculate_xs(p);
39,035,339 ✔
921
    }
922

923
    // ========================================================================
924
    // ADD TO MACROSCOPIC CROSS SECTION
925

926
    // Copy atom density of nuclide in material
927
    double atom_density = this->atom_density(i, p.density_mult());
73,034,437 ✔
928

929
    // Add contributions to material macroscopic cross sections
930
    p.macro_xs().total += atom_density * micro.total;
73,034,437 ✔
931
    p.macro_xs().coherent += atom_density * micro.coherent;
73,034,437 ✔
932
    p.macro_xs().incoherent += atom_density * micro.incoherent;
73,034,437 ✔
933
    p.macro_xs().photoelectric += atom_density * micro.photoelectric;
73,034,437 ✔
934
    p.macro_xs().pair_production += atom_density * micro.pair_production;
73,034,437 ✔
935
  }
936
}
16,452,693 ✔
937

938
void Material::set_id(int32_t id)
14,422 ✔
939
{
940
  assert(id >= 0 || id == C_NONE);
11,451 !
941

942
  // Clear entry in material map if an ID was already assigned before
943
  if (id_ != C_NONE) {
14,422 !
UNCOV
944
    model::material_map.erase(id_);
×
945
    id_ = C_NONE;
×
946
  }
947

948
  // Make sure no other material has same ID
949
  if (model::material_map.find(id) != model::material_map.end()) {
14,422 !
UNCOV
950
    throw std::runtime_error {
×
951
      "Two materials have the same ID: " + std::to_string(id)};
×
952
  }
953

954
  // If no ID specified, auto-assign next ID in sequence
955
  if (id == C_NONE) {
14,422 !
UNCOV
956
    id = 0;
×
957
    for (const auto& m : model::materials) {
×
958
      id = std::max(id, m->id_);
×
959
    }
UNCOV
960
    ++id;
×
961
  }
962

963
  // Update ID and entry in material map
964
  id_ = id;
14,422 ✔
965
  model::material_map[id] = index_;
14,422 ✔
966
}
14,422 ✔
967

968
void Material::set_density(double density, const std::string& units)
5,698 ✔
969
{
970
  assert(density >= 0.0);
4,616 !
971

972
  if (nuclide_.empty()) {
5,698 !
UNCOV
973
    throw std::runtime_error {"No nuclides exist in material yet."};
×
974
  }
975

976
  if (units == "atom/b-cm") {
5,698 ✔
977
    // Set total density based on value provided
978
    density_ = density;
5,658 ✔
979

980
    // Determine normalized atom percents
981
    double sum_percent = atom_density_.sum();
5,658 ✔
982
    atom_density_ /= sum_percent;
5,658 ✔
983

984
    // Recalculate nuclide atom densities based on given density
985
    atom_density_ *= density;
5,658 ✔
986

987
    // Calculate density in g/cm^3 and charge density in [e/b-cm]
988
    density_gpcc_ = 0.0;
5,658 ✔
989
    charge_density_ = 0.0;
5,658 ✔
990
    for (int i = 0; i < nuclide_.size(); ++i) {
58,032 ✔
991
      int i_nuc = nuclide_[i];
52,374 ✔
992
      double awr = data::nuclides[i_nuc]->awr_;
52,374 ✔
993
      int z = settings::run_CE ? data::nuclides[i_nuc]->Z_ : 0.0;
52,374 !
994
      density_gpcc_ += atom_density_(i) * awr * MASS_NEUTRON / N_AVOGADRO;
52,374 ✔
995
      charge_density_ += atom_density_(i) * z;
52,374 ✔
996
    }
997
  } else if (units == "g/cm3" || units == "g/cc") {
40 !
998
    // Determine factor by which to change densities
999
    double previous_density_gpcc = density_gpcc_;
30 ✔
1000
    double f = density / previous_density_gpcc;
30 ✔
1001

1002
    // Update densities
1003
    density_gpcc_ = density;
30 ✔
1004
    density_ *= f;
30 ✔
1005
    atom_density_ *= f;
30 ✔
1006
    charge_density_ *= f;
30 ✔
1007
  } else {
1008
    throw std::invalid_argument {
10 ✔
1009
      "Invalid units '" + std::string(units.data()) + "' specified."};
20 ✔
1010
  }
1011
}
5,688 ✔
1012

1013
void Material::set_densities(
5,518 ✔
1014
  const vector<std::string>& name, const vector<double>& density)
1015
{
1016
  auto n = name.size();
5,518 ✔
1017
  assert(n > 0);
4,472 !
1018
  assert(n == density.size());
4,472 !
1019

1020
  if (n != nuclide_.size()) {
5,518 ✔
1021
    nuclide_.resize(n);
1,272 ✔
1022
    atom_density_ = tensor::zeros<double>({n});
1,272 ✔
1023
    if (settings::photon_transport)
1,272 !
UNCOV
1024
      element_.resize(n);
×
1025
  }
1026

1027
  double sum_density = 0.0;
5,518 ✔
1028
  for (int64_t i = 0; i < n; ++i) {
57,272 ✔
1029
    const auto& nuc {name[i]};
51,754 ✔
1030
    if (data::nuclide_map.find(nuc) == data::nuclide_map.end()) {
51,754 !
UNCOV
1031
      int err = openmc_load_nuclide(nuc.c_str(), nullptr, 0);
×
1032
      if (err < 0)
×
1033
        throw std::runtime_error {openmc_err_msg};
×
1034
    }
1035

1036
    nuclide_[i] = data::nuclide_map.at(nuc);
51,754 ✔
1037
    assert(density[i] > 0.0);
41,992 !
1038
    atom_density_(i) = density[i];
51,754 ✔
1039
    sum_density += density[i];
51,754 ✔
1040

1041
    if (settings::photon_transport) {
51,754 !
UNCOV
1042
      auto element_name = to_element(nuc);
×
1043
      element_[i] = data::element_map.at(element_name);
×
1044
    }
×
1045
  }
1046

1047
  // Set total density to the sum of the vector
1048
  this->set_density(sum_density, "atom/b-cm");
5,518 ✔
1049

1050
  // Generate material bremsstrahlung data for electrons and positrons
1051
  if (settings::photon_transport &&
5,518 !
UNCOV
1052
      settings::electron_treatment == ElectronTreatment::TTB) {
×
1053
    this->init_bremsstrahlung();
×
1054
  }
1055

1056
  // Assign S(a,b) tables
1057
  this->init_thermal();
5,518 ✔
1058
}
5,518 ✔
1059

1060
double Material::volume() const
50 ✔
1061
{
1062
  if (volume_ < 0.0) {
50 ✔
1063
    throw std::runtime_error {
10 ✔
1064
      "Volume for material with ID=" + std::to_string(id_) + " not set."};
20 ✔
1065
  }
1066
  return volume_;
40 ✔
1067
}
1068

1069
double Material::temperature() const
18,120 ✔
1070
{
1071
  // If material doesn't have an assigned temperature, use global default
1072
  return temperature_ >= 0 ? temperature_ : settings::temperature_default;
18,120 ✔
1073
}
1074

1075
void Material::to_hdf5(hid_t group) const
11,761 ✔
1076
{
1077
  hid_t material_group = create_group(group, "material " + std::to_string(id_));
11,761 ✔
1078

1079
  write_attribute(material_group, "depletable", static_cast<int>(depletable()));
11,761 ✔
1080
  if (volume_ > 0.0) {
11,761 ✔
1081
    write_attribute(material_group, "volume", volume_);
1,814 ✔
1082
  }
1083
  if (temperature_ > 0.0) {
11,761 ✔
1084
    write_attribute(material_group, "temperature", temperature_);
1,677 ✔
1085
  }
1086
  write_dataset(material_group, "name", name_);
11,761 ✔
1087
  write_dataset(material_group, "atom_density", density_);
11,761 ✔
1088

1089
  // Copy nuclide/macro name for each nuclide to vector
1090
  vector<std::string> nuc_names;
11,761 ✔
1091
  vector<std::string> macro_names;
11,761 ✔
1092
  vector<double> nuc_densities;
11,761 ✔
1093
  if (settings::run_CE) {
11,761 ✔
1094
    for (int i = 0; i < nuclide_.size(); ++i) {
49,528 ✔
1095
      int i_nuc = nuclide_[i];
39,447 ✔
1096
      nuc_names.push_back(data::nuclides[i_nuc]->name_);
39,447 ✔
1097
      nuc_densities.push_back(atom_density_(i));
39,447 ✔
1098
    }
1099
  } else {
1100
    for (int i = 0; i < nuclide_.size(); ++i) {
3,390 ✔
1101
      int i_nuc = nuclide_[i];
1,710 ✔
1102
      if (data::mg.nuclides_[i_nuc].awr != MACROSCOPIC_AWR) {
1,710 ✔
1103
        nuc_names.push_back(data::mg.nuclides_[i_nuc].name);
80 ✔
1104
        nuc_densities.push_back(atom_density_(i));
80 ✔
1105
      } else {
1106
        macro_names.push_back(data::mg.nuclides_[i_nuc].name);
1,630 ✔
1107
      }
1108
    }
1109
  }
1110

1111
  // Write vector to 'nuclides'
1112
  if (!nuc_names.empty()) {
11,761 ✔
1113
    write_dataset(material_group, "nuclides", nuc_names);
10,131 ✔
1114
    write_dataset(material_group, "nuclide_densities", nuc_densities);
10,131 ✔
1115
  }
1116

1117
  // Write vector to 'macroscopics'
1118
  if (!macro_names.empty()) {
11,761 ✔
1119
    write_dataset(material_group, "macroscopics", macro_names);
1,630 ✔
1120
  }
1121

1122
  if (!thermal_tables_.empty()) {
11,761 ✔
1123
    vector<std::string> sab_names;
1,461 ✔
1124
    for (const auto& table : thermal_tables_) {
2,982 ✔
1125
      sab_names.push_back(data::thermal_scatt[table.index_table]->name_);
1,521 ✔
1126
    }
1127
    write_dataset(material_group, "sab_names", sab_names);
1,461 ✔
1128
  }
1,461 ✔
1129

1130
  close_group(material_group);
11,761 ✔
1131
}
11,761 ✔
1132

1133
void Material::export_properties_hdf5(hid_t group) const
120 ✔
1134
{
1135
  hid_t material_group = create_group(group, "material " + std::to_string(id_));
120 ✔
1136
  write_attribute(material_group, "atom_density", density_);
120 ✔
1137
  write_attribute(material_group, "mass_density", density_gpcc_);
120 ✔
1138
  close_group(material_group);
120 ✔
1139
}
120 ✔
1140

1141
void Material::import_properties_hdf5(hid_t group)
90 ✔
1142
{
1143
  hid_t material_group = open_group(group, "material " + std::to_string(id_));
90 ✔
1144
  double density;
1145
  read_attribute(material_group, "atom_density", density);
90 ✔
1146
  this->set_density(density, "atom/b-cm");
90 ✔
1147
  close_group(material_group);
90 ✔
1148
}
90 ✔
1149

1150
void Material::add_nuclide(const std::string& name, double density)
10 ✔
1151
{
1152
  // Check if nuclide is already in material
1153
  for (int i = 0; i < nuclide_.size(); ++i) {
50 ✔
1154
    int i_nuc = nuclide_[i];
40 ✔
1155
    if (data::nuclides[i_nuc]->name_ == name) {
40 !
UNCOV
1156
      double awr = data::nuclides[i_nuc]->awr_;
×
1157
      density_ += density - atom_density_(i);
×
1158
      density_gpcc_ +=
×
1159
        (density - atom_density_(i)) * awr * MASS_NEUTRON / N_AVOGADRO;
×
1160
      atom_density_(i) = density;
×
1161
      return;
×
1162
    }
1163
  }
1164

1165
  // If nuclide wasn't found, extend nuclide/density arrays
1166
  int err = openmc_load_nuclide(name.c_str(), nullptr, 0);
10 ✔
1167
  if (err < 0)
10 !
UNCOV
1168
    throw std::runtime_error {openmc_err_msg};
×
1169

1170
  // Append new nuclide/density
1171
  int i_nuc = data::nuclide_map[name];
10 ✔
1172
  nuclide_.push_back(i_nuc);
10 ✔
1173

1174
  // Append new element if photon transport is on
1175
  if (settings::photon_transport) {
10 !
UNCOV
1176
    int i_elem = data::element_map[to_element(name)];
×
1177
    element_.push_back(i_elem);
×
1178
  }
1179

1180
  auto n = nuclide_.size();
10 ✔
1181

1182
  // Create copy of atom_density_ array with one extra entry
1183
  tensor::Tensor<double> atom_density = tensor::zeros<double>({n});
10 ✔
1184
  atom_density.slice(tensor::range(0, n - 1)) = atom_density_;
10 ✔
1185
  atom_density(n - 1) = density;
10 ✔
1186
  atom_density_ = atom_density;
10 ✔
1187

1188
  density_ += density;
10 ✔
1189
  density_gpcc_ +=
10 ✔
1190
    density * data::nuclides[i_nuc]->awr_ * MASS_NEUTRON / N_AVOGADRO;
10 ✔
1191
}
10 ✔
1192

1193
//==============================================================================
1194
// Non-method functions
1195
//==============================================================================
1196

1197
double sternheimer_adjustment(const vector<double>& f,
488 ✔
1198
  const vector<double>& e_b_sq, double e_p_sq, double n_conduction,
1199
  double log_I, double tol, int max_iter)
1200
{
1201
  // Get the total number of oscillators
1202
  int n = f.size();
488 ✔
1203

1204
  // Calculate the Sternheimer adjustment factor using Newton's method
1205
  double rho = 2.0;
488 ✔
1206
  int iter;
1207
  for (iter = 0; iter < max_iter; ++iter) {
1,952 !
1208
    double rho_0 = rho;
1,952 ✔
1209

1210
    // Function to find the root of and its derivative
1211
    double g = 0.0;
1,952 ✔
1212
    double gp = 0.0;
1,952 ✔
1213

1214
    for (int i = 0; i < n; ++i) {
48,428 ✔
1215
      // Square of resonance energy of a bound-shell oscillator
1216
      double e_r_sq = e_b_sq[i] * rho * rho + 2.0 / 3.0 * f[i] * e_p_sq;
46,476 ✔
1217
      g += f[i] * std::log(e_r_sq);
46,476 ✔
1218
      gp += e_b_sq[i] * f[i] * rho / e_r_sq;
46,476 ✔
1219
    }
1220
    // Include conduction electrons
1221
    if (n_conduction > 0.0) {
1,952 ✔
1222
      g += n_conduction * std::log(n_conduction * e_p_sq);
1,616 ✔
1223
    }
1224

1225
    // Set the next guess: rho_n+1 = rho_n - g(rho_n)/g'(rho_n)
1226
    rho -= (g - 2.0 * log_I) / (2.0 * gp);
1,952 ✔
1227

1228
    // If the initial guess is too large, rho can be negative
1229
    if (rho < 0.0)
1,952 !
UNCOV
1230
      rho = rho_0 / 2.0;
×
1231

1232
    // Check for convergence
1233
    if (std::abs(rho - rho_0) / rho_0 < tol)
1,952 ✔
1234
      break;
488 ✔
1235
  }
1236
  // Did not converge
1237
  if (iter >= max_iter) {
488 !
UNCOV
1238
    warning("Maximum Newton-Raphson iterations exceeded.");
×
1239
    rho = 1.0e-6;
×
1240
  }
1241
  return rho;
488 ✔
1242
}
1243

1244
double density_effect(const vector<double>& f, const vector<double>& e_b_sq,
97,308 ✔
1245
  double e_p_sq, double n_conduction, double rho, double E, double tol,
1246
  int max_iter)
1247
{
1248
  // Get the total number of oscillators
1249
  int n = f.size();
97,308 ✔
1250

1251
  // Square of the ratio of the speed of light to the velocity of the charged
1252
  // particle
1253
  double beta_sq = E * (E + 2.0 * MASS_ELECTRON_EV) /
97,308 ✔
1254
                   ((E + MASS_ELECTRON_EV) * (E + MASS_ELECTRON_EV));
97,308 ✔
1255

1256
  // For nonmetals, delta = 0 for beta < beta_0, where beta_0 is obtained by
1257
  // setting the frequency w = 0.
1258
  double beta_0_sq = 0.0;
97,308 ✔
1259
  if (n_conduction == 0.0) {
97,308 ✔
1260
    for (int i = 0; i < n; ++i) {
126,400 ✔
1261
      beta_0_sq += f[i] * e_p_sq / (e_b_sq[i] * rho * rho);
107,200 ✔
1262
    }
1263
    beta_0_sq = 1.0 / (1.0 + beta_0_sq);
19,200 ✔
1264
  }
1265
  double delta = 0.0;
97,308 ✔
1266
  if (beta_sq < beta_0_sq)
97,308 ✔
1267
    return delta;
9,888 ✔
1268

1269
  // Compute the square of the frequency w^2 using Newton's method, with the
1270
  // initial guess of w^2 equal to beta^2 * gamma^2
1271
  double w_sq = E / MASS_ELECTRON_EV * (E / MASS_ELECTRON_EV + 2);
87,420 ✔
1272
  int iter;
1273
  for (iter = 0; iter < max_iter; ++iter) {
586,388 !
1274
    double w_sq_0 = w_sq;
586,388 ✔
1275

1276
    // Function to find the root of and its derivative
1277
    double g = 0.0;
586,388 ✔
1278
    double gp = 0.0;
586,388 ✔
1279

1280
    for (int i = 0; i < n; ++i) {
17,029,748 ✔
1281
      double c = e_b_sq[i] * rho * rho / e_p_sq + w_sq;
16,443,360 ✔
1282
      g += f[i] / c;
16,443,360 ✔
1283
      gp -= f[i] / (c * c);
16,443,360 ✔
1284
    }
1285
    // Include conduction electrons
1286
    g += n_conduction / w_sq;
586,388 ✔
1287
    gp -= n_conduction / (w_sq * w_sq);
586,388 ✔
1288

1289
    // Set the next guess: w_n+1 = w_n - g(w_n)/g'(w_n)
1290
    w_sq -= (g + 1.0 - 1.0 / beta_sq) / gp;
586,388 ✔
1291

1292
    // If the initial guess is too large, w can be negative
1293
    if (w_sq < 0.0)
586,388 ✔
1294
      w_sq = w_sq_0 / 2.0;
125,364 ✔
1295

1296
    // Check for convergence
1297
    if (std::abs(w_sq - w_sq_0) / w_sq_0 < tol)
586,388 ✔
1298
      break;
87,420 ✔
1299
  }
1300
  // Did not converge
1301
  if (iter >= max_iter) {
87,420 !
UNCOV
1302
    warning("Maximum Newton-Raphson iterations exceeded: setting density "
×
1303
            "effect correction to zero.");
UNCOV
1304
    return delta;
×
1305
  }
1306

1307
  // Solve for the density effect correction
1308
  for (int i = 0; i < n; ++i) {
2,326,388 ✔
1309
    double l_sq = e_b_sq[i] * rho * rho / e_p_sq + 2.0 / 3.0 * f[i];
2,238,968 ✔
1310
    delta += f[i] * std::log((l_sq + w_sq) / l_sq);
2,238,968 ✔
1311
  }
1312
  // Include conduction electrons
1313
  if (n_conduction > 0.0) {
87,420 ✔
1314
    delta += n_conduction * std::log((n_conduction + w_sq) / n_conduction);
78,108 ✔
1315
  }
1316

1317
  return delta - w_sq * (1.0 - beta_sq);
87,420 ✔
1318
}
1319

1320
void read_materials_xml()
1,296 ✔
1321
{
1322
  write_message("Reading materials XML file...", 5);
1,296 ✔
1323

1324
  pugi::xml_document doc;
1,296 ✔
1325

1326
  // Check if materials.xml exists
1327
  std::string filename = settings::path_input + "materials.xml";
1,296 ✔
1328
  if (!file_exists(filename)) {
1,296 !
UNCOV
1329
    fatal_error("Material XML file '" + filename + "' does not exist!");
×
1330
  }
1331

1332
  // Parse materials.xml file and get root element
1333
  doc.load_file(filename.c_str());
1,296 ✔
1334

1335
  // Loop over XML material elements and populate the array.
1336
  pugi::xml_node root = doc.document_element();
1,296 ✔
1337

1338
  read_materials_xml(root);
1,296 ✔
1339
}
1,296 ✔
1340

1341
void read_materials_xml(pugi::xml_node root)
7,463 ✔
1342
{
1343
  for (pugi::xml_node material_node : root.children("material")) {
21,837 ✔
1344
    model::materials.push_back(make_unique<Material>(material_node));
14,374 ✔
1345
  }
1346
  model::materials.shrink_to_fit();
7,463 ✔
1347
}
7,463 ✔
1348

1349
void free_memory_material()
7,582 ✔
1350
{
1351
  model::materials.clear();
7,582 ✔
1352
  model::material_map.clear();
7,582 ✔
1353
}
7,582 ✔
1354

1355
//==============================================================================
1356
// C API
1357
//==============================================================================
1358

1359
extern "C" int openmc_get_material_index(int32_t id, int32_t* index)
7,614 ✔
1360
{
1361
  auto it = model::material_map.find(id);
7,614 ✔
1362
  if (it == model::material_map.end()) {
7,614 ✔
1363
    set_errmsg("No material exists with ID=" + std::to_string(id) + ".");
10 ✔
1364
    return OPENMC_E_INVALID_ID;
10 ✔
1365
  } else {
1366
    *index = it->second;
7,604 ✔
1367
    return 0;
7,604 ✔
1368
  }
1369
}
1370

1371
extern "C" int openmc_material_add_nuclide(
10 ✔
1372
  int32_t index, const char* name, double density)
1373
{
1374
  int err = 0;
10 ✔
1375
  if (index >= 0 && index < model::materials.size()) {
10 !
1376
    try {
1377
      model::materials[index]->add_nuclide(name, density);
10 ✔
UNCOV
1378
    } catch (const std::runtime_error& e) {
×
1379
      return OPENMC_E_DATA;
×
1380
    }
×
1381
  } else {
UNCOV
1382
    set_errmsg("Index in materials array is out of bounds.");
×
1383
    return OPENMC_E_OUT_OF_BOUNDS;
×
1384
  }
1385
  return err;
10 ✔
1386
}
1387

1388
extern "C" int openmc_material_get_densities(
250 ✔
1389
  int32_t index, const int** nuclides, const double** densities, int* n)
1390
{
1391
  if (index >= 0 && index < model::materials.size()) {
250 !
1392
    auto& mat = model::materials[index];
250 ✔
1393
    if (!mat->nuclides().empty()) {
250 !
1394
      *nuclides = mat->nuclides().data();
250 ✔
1395
      *densities = mat->densities().data();
250 ✔
1396
      *n = mat->nuclides().size();
250 ✔
1397
      return 0;
250 ✔
1398
    } else {
UNCOV
1399
      set_errmsg("Material atom density array has not been allocated.");
×
1400
      return OPENMC_E_ALLOCATE;
×
1401
    }
1402
  } else {
UNCOV
1403
    set_errmsg("Index in materials array is out of bounds.");
×
1404
    return OPENMC_E_OUT_OF_BOUNDS;
×
1405
  }
1406
}
1407

1408
extern "C" int openmc_material_get_density(int32_t index, double* density)
30 ✔
1409
{
1410
  if (index >= 0 && index < model::materials.size()) {
30 !
1411
    auto& mat = model::materials[index];
30 ✔
1412
    *density = mat->density_gpcc();
30 ✔
1413
    return 0;
30 ✔
1414
  } else {
UNCOV
1415
    set_errmsg("Index in materials array is out of bounds.");
×
1416
    return OPENMC_E_OUT_OF_BOUNDS;
×
1417
  }
1418
}
1419

UNCOV
1420
extern "C" int openmc_material_get_fissionable(int32_t index, bool* fissionable)
×
1421
{
UNCOV
1422
  if (index >= 0 && index < model::materials.size()) {
×
1423
    *fissionable = model::materials[index]->fissionable();
×
1424
    return 0;
×
1425
  } else {
UNCOV
1426
    set_errmsg("Index in materials array is out of bounds.");
×
1427
    return OPENMC_E_OUT_OF_BOUNDS;
×
1428
  }
1429
}
1430

1431
extern "C" int openmc_material_get_id(int32_t index, int32_t* id)
9,060 ✔
1432
{
1433
  if (index >= 0 && index < model::materials.size()) {
9,060 !
1434
    *id = model::materials[index]->id();
9,060 ✔
1435
    return 0;
9,060 ✔
1436
  } else {
UNCOV
1437
    set_errmsg("Index in materials array is out of bounds.");
×
1438
    return OPENMC_E_OUT_OF_BOUNDS;
×
1439
  }
1440
}
1441

1442
extern "C" int openmc_material_get_temperature(
60 ✔
1443
  int32_t index, double* temperature)
1444
{
1445
  if (index < 0 || index >= model::materials.size()) {
60 !
UNCOV
1446
    set_errmsg("Index in materials array is out of bounds.");
×
1447
    return OPENMC_E_OUT_OF_BOUNDS;
×
1448
  }
1449
  *temperature = model::materials[index]->temperature();
60 ✔
1450
  return 0;
60 ✔
1451
}
1452

1453
extern "C" int openmc_material_get_volume(int32_t index, double* volume)
50 ✔
1454
{
1455
  if (index >= 0 && index < model::materials.size()) {
50 !
1456
    try {
1457
      *volume = model::materials[index]->volume();
50 ✔
1458
    } catch (const std::exception& e) {
10 !
1459
      set_errmsg(e.what());
10 ✔
1460
      return OPENMC_E_UNASSIGNED;
10 ✔
1461
    }
10 ✔
1462
    return 0;
40 ✔
1463
  } else {
UNCOV
1464
    set_errmsg("Index in materials array is out of bounds.");
×
1465
    return OPENMC_E_OUT_OF_BOUNDS;
×
1466
  }
1467
}
1468

1469
extern "C" int openmc_material_set_density(
90 ✔
1470
  int32_t index, double density, const char* units)
1471
{
1472
  if (index >= 0 && index < model::materials.size()) {
90 !
1473
    try {
1474
      model::materials[index]->set_density(density, units);
110 ✔
1475
    } catch (const std::exception& e) {
10 !
1476
      set_errmsg(e.what());
10 ✔
1477
      return OPENMC_E_UNASSIGNED;
10 ✔
1478
    }
10 ✔
1479
  } else {
UNCOV
1480
    set_errmsg("Index in materials array is out of bounds.");
×
1481
    return OPENMC_E_OUT_OF_BOUNDS;
×
1482
  }
1483
  return 0;
80 ✔
1484
}
1485

1486
extern "C" int openmc_material_set_densities(
5,518 ✔
1487
  int32_t index, int n, const char** name, const double* density)
1488
{
1489
  if (index >= 0 && index < model::materials.size()) {
5,518 !
1490
    try {
1491
      model::materials[index]->set_densities(
16,554 ✔
1492
        {name, name + n}, {density, density + n});
11,036 ✔
UNCOV
1493
    } catch (const std::exception& e) {
×
1494
      set_errmsg(e.what());
×
1495
      return OPENMC_E_UNASSIGNED;
×
1496
    }
×
1497
  } else {
UNCOV
1498
    set_errmsg("Index in materials array is out of bounds.");
×
1499
    return OPENMC_E_OUT_OF_BOUNDS;
×
1500
  }
1501
  return 0;
5,518 ✔
1502
}
1503

1504
extern "C" int openmc_material_set_id(int32_t index, int32_t id)
40 ✔
1505
{
1506
  if (index >= 0 && index < model::materials.size()) {
40 !
1507
    try {
1508
      model::materials.at(index)->set_id(id);
40 ✔
UNCOV
1509
    } catch (const std::exception& e) {
×
1510
      set_errmsg(e.what());
×
1511
      return OPENMC_E_UNASSIGNED;
×
1512
    }
×
1513
  } else {
UNCOV
1514
    set_errmsg("Index in materials array is out of bounds.");
×
1515
    return OPENMC_E_OUT_OF_BOUNDS;
×
1516
  }
1517
  return 0;
40 ✔
1518
}
1519

1520
extern "C" int openmc_material_get_name(int32_t index, const char** name)
180 ✔
1521
{
1522
  if (index < 0 || index >= model::materials.size()) {
180 !
UNCOV
1523
    set_errmsg("Index in materials array is out of bounds.");
×
1524
    return OPENMC_E_OUT_OF_BOUNDS;
×
1525
  }
1526

1527
  *name = model::materials[index]->name().data();
180 ✔
1528

1529
  return 0;
180 ✔
1530
}
1531

1532
extern "C" int openmc_material_set_name(int32_t index, const char* name)
10 ✔
1533
{
1534
  if (index < 0 || index >= model::materials.size()) {
10 !
UNCOV
1535
    set_errmsg("Index in materials array is out of bounds.");
×
1536
    return OPENMC_E_OUT_OF_BOUNDS;
×
1537
  }
1538

1539
  model::materials[index]->set_name(name);
10 ✔
1540

1541
  return 0;
10 ✔
1542
}
1543

1544
extern "C" int openmc_material_set_volume(int32_t index, double volume)
44 ✔
1545
{
1546
  if (index >= 0 && index < model::materials.size()) {
44 !
1547
    auto& m {model::materials[index]};
44 ✔
1548
    if (volume >= 0.0) {
44 !
1549
      m->volume_ = volume;
44 ✔
1550
      return 0;
44 ✔
1551
    } else {
UNCOV
1552
      set_errmsg("Volume must be non-negative");
×
1553
      return OPENMC_E_INVALID_ARGUMENT;
×
1554
    }
1555
  } else {
UNCOV
1556
    set_errmsg("Index in materials array is out of bounds.");
×
1557
    return OPENMC_E_OUT_OF_BOUNDS;
×
1558
  }
1559
}
1560

1561
extern "C" int openmc_material_get_depletable(int32_t index, bool* depletable)
20 ✔
1562
{
1563
  if (index < 0 || index >= model::materials.size()) {
20 !
UNCOV
1564
    set_errmsg("Index in materials array is out of bounds.");
×
1565
    return OPENMC_E_OUT_OF_BOUNDS;
×
1566
  }
1567

1568
  *depletable = model::materials[index]->depletable();
20 ✔
1569

1570
  return 0;
20 ✔
1571
}
1572

1573
extern "C" int openmc_material_set_depletable(int32_t index, bool depletable)
10 ✔
1574
{
1575
  if (index < 0 || index >= model::materials.size()) {
10 !
UNCOV
1576
    set_errmsg("Index in materials array is out of bounds.");
×
1577
    return OPENMC_E_OUT_OF_BOUNDS;
×
1578
  }
1579

1580
  model::materials[index]->depletable() = depletable;
10 ✔
1581

1582
  return 0;
10 ✔
1583
}
1584

1585
extern "C" int openmc_extend_materials(
40 ✔
1586
  int32_t n, int32_t* index_start, int32_t* index_end)
1587
{
1588
  if (index_start)
40 !
1589
    *index_start = model::materials.size();
40 ✔
1590
  if (index_end)
40 !
UNCOV
1591
    *index_end = model::materials.size() + n - 1;
×
1592
  for (int32_t i = 0; i < n; i++) {
80 ✔
1593
    model::materials.push_back(make_unique<Material>());
40 ✔
1594
  }
1595
  return 0;
40 ✔
1596
}
1597

1598
extern "C" size_t n_materials()
110 ✔
1599
{
1600
  return model::materials.size();
110 ✔
1601
}
1602

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