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

openmc-dev / openmc / 24201667475

09 Apr 2026 04:34PM UTC coverage: 81.306% (-0.03%) from 81.337%
24201667475

Pull #3702

github

web-flow
Merge 85e3d3a8b into 1eb368bbd
Pull Request #3702: Random Ray Kinetic Simulation Mode

18160 of 26198 branches covered (69.32%)

Branch coverage included in aggregate %.

935 of 1075 new or added lines in 20 files covered. (86.98%)

74 existing lines in 8 files now uncovered.

58936 of 68624 relevant lines covered (85.88%)

52648244.8 hits per line

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

71.6
/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)
16,354✔
49
{
50
  index_ = model::materials.size(); // Avoids warning about narrowing
16,354✔
51

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

58
  if (check_for_node(node, "name")) {
16,354✔
59
    name_ = get_node_value(node, "name");
8,678✔
60
  }
61

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

69
  if (check_for_node(node, "depletable")) {
16,354✔
70
    depletable_ = get_node_value_bool(node, "depletable");
5,762✔
71
  }
72

73
  bool sum_density {false};
16,354✔
74
  pugi::xml_node density_node = node.child("density");
16,354✔
75
  std::string units;
16,354✔
76
  if (density_node) {
16,354!
77
    units = get_node_value(density_node, "units");
16,354✔
78
    if (settings::kinetic_simulation) {
16,354✔
79
      if (check_for_node(density_node, "value_timeseries")) {
510✔
80
        if (units == "sum") {
180!
NEW
81
          fatal_error("Use of density_timeseries is incompatible with 'sum'"
×
82
                      "density unit");
83
        } else {
84
          density_timeseries_ =
180✔
85
            get_node_array<double>(density_node, "value_timeseries");
180✔
86
          if (density_timeseries_.size() >= settings::n_timesteps) {
180!
87
            warning(fmt::format(
216✔
88
              "Material {} has a density_timeseries (size={}) longer than "
89
              "n_timesteps ({}). Only the first {} entries of the density "
90
              "timeseries "
91
              "will be simulated.",
92
              this->id(), density_timeseries_.size(), settings::n_timesteps,
324✔
93
              settings::n_timesteps));
94
          } else {
NEW
95
            fatal_error(
×
NEW
96
              fmt::format("Material {} has a density_timeseries (size={}) "
×
97
                          "shorter than n_timesteps ({}). Not all "
98
                          "time steps can be simulated. Aborting.",
NEW
99
                this->id(), density_timeseries_.size(), settings::n_timesteps));
×
100
          }
101
        }
102
      }
103
    }
104
    if (units == "sum") {
16,354✔
105
      sum_density = true;
106
    } else if (units == "macro") {
13,956✔
107
      if (check_for_node(density_node, "value")) {
3,197!
108
        density_ = std::stod(get_node_value(density_node, "value"));
6,394✔
109
      } else {
110
        density_ = 1.0;
×
111
      }
112
    } else {
113
      double val = std::stod(get_node_value(density_node, "value"));
21,518✔
114
      if (val <= 0.0) {
10,759!
115
        fatal_error("Need to specify a positive density on material " +
×
116
                    std::to_string(id_) + ".");
×
117
      }
118

119
      double scale_factor;
10,759✔
120
      if (units == "g/cc" || units == "g/cm3") {
18,121✔
121
        density_ = -val;
10,410✔
122
        scale_factor = 1.0;
10,410✔
123
      } else if (units == "kg/m3") {
349✔
124
        density_ = -1.0e-3 * val;
15✔
125
        scale_factor = -1.0e-3;
15✔
126
      } else if (units == "atom/b-cm") {
334✔
127
        density_ = val;
319✔
128
        scale_factor = 1.0;
319✔
129
      } else if (units == "atom/cc" || units == "atom/cm3") {
30!
130
        density_ = 1.0e-24 * val;
15✔
131
        scale_factor = 1.0e-24;
15✔
132
      } else {
133
        fatal_error("Unknown units '" + units + "' specified on material " +
×
134
                    std::to_string(id_) + ".");
×
135
      }
136
      // Scale density_timeseries_ to the same units as density_
137
      if (density_timeseries_.size() > 0) {
10,759!
NEW
138
        for (double& p : density_timeseries_) {
×
NEW
139
          if (p <= 0) {
×
NEW
140
            fatal_error(
×
141
              "Zero or negative value detected in material timeseries. This "
142
              "will break any time-dependent simulations. Please fix the "
143
              "density timeseries in your input file. Aborting...");
144
          }
NEW
145
          p *= scale_factor;
×
146
        }
147

148
        // Check that all elements are the same sign.
NEW
149
        vector<double> zero_vector;
×
NEW
150
        for (int i = 0; i < density_timeseries_.size(); i++) {
×
NEW
151
          zero_vector.push_back(i);
×
152
        }
NEW
153
        if (!(density_timeseries_ >= zero_vector ||
×
NEW
154
              density_timeseries_ <= zero_vector)) {
×
NEW
155
          fatal_error(
×
156
            "Cannot mix atom and weight percents for density timeseries "
NEW
157
            "in material " +
×
NEW
158
            std::to_string(id_));
×
NEW
159
        } else if ((density_timeseries_ >= zero_vector && density_ <= 0) ||
×
NEW
160
                   (density_timeseries_ <= zero_vector && density_ >= 0)) {
×
NEW
161
          fatal_error("density_timeseries_ must be same type as density_");
×
162
        }
NEW
163
      }
×
164
    }
165
  } else {
166
    fatal_error("Must specify <density> element in material " +
×
167
                std::to_string(id_) + ".");
×
168
  }
169

170
  if (node.child("element")) {
16,354!
171
    fatal_error(
×
172
      "Unable to add an element to material " + std::to_string(id_) +
×
173
      " since the element option has been removed from the xml input. "
174
      "Elements can only be added via the Python API, which will expand "
175
      "elements into their natural nuclides.");
176
  }
177

178
  // =======================================================================
179
  // READ AND PARSE <nuclide> TAGS
180

181
  // Check to ensure material has at least one nuclide
182
  if (!check_for_node(node, "nuclide") &&
19,551!
183
      !check_for_node(node, "macroscopic")) {
3,197✔
184
    fatal_error("No macroscopic data or nuclides specified on material " +
×
185
                std::to_string(id_));
×
186
  }
187

188
  // Create list of macroscopic x/s based on those specified, just treat
189
  // them as nuclides. This is all really a facade so the user thinks they
190
  // are entering in macroscopic data but the code treats them the same
191
  // as nuclides internally.
192
  // Get pointer list of XML <macroscopic>
193
  auto node_macros = node.children("macroscopic");
16,354✔
194
  int num_macros = std::distance(node_macros.begin(), node_macros.end());
16,354✔
195

196
  vector<std::string> names;
16,354✔
197
  vector<double> densities;
16,354✔
198
  if (settings::run_CE && num_macros > 0) {
16,354!
199
    fatal_error("Macroscopic can not be used in continuous-energy mode.");
×
200
  } else if (num_macros > 1) {
16,354!
201
    fatal_error("Only one macroscopic object permitted per material, " +
×
202
                std::to_string(id_));
×
203
  } else if (num_macros == 1) {
16,354✔
204
    pugi::xml_node node_nuc = *node_macros.begin();
3,197✔
205

206
    // Check for empty name on nuclide
207
    if (!check_for_node(node_nuc, "name")) {
3,197!
208
      fatal_error("No name specified on macroscopic data in material " +
×
209
                  std::to_string(id_));
×
210
    }
211

212
    // store nuclide name
213
    std::string name = get_node_value(node_nuc, "name", false, true);
3,197✔
214
    names.push_back(name);
3,197✔
215

216
    // Set density for macroscopic data
217
    if (units == "macro") {
3,197!
218
      densities.push_back(density_);
3,197✔
219
    } else {
220
      fatal_error("Units can only be macro for macroscopic data " + name);
×
221
    }
222
  } else {
3,197✔
223
    // Create list of nuclides based on those specified
224
    for (auto node_nuc : node.children("nuclide")) {
63,946✔
225
      // Check for empty name on nuclide
226
      if (!check_for_node(node_nuc, "name")) {
50,789!
227
        fatal_error(
×
228
          "No name specified on nuclide in material " + std::to_string(id_));
×
229
      }
230

231
      // store nuclide name
232
      std::string name = get_node_value(node_nuc, "name", false, true);
50,789✔
233
      names.push_back(name);
50,789✔
234

235
      // Check if no atom/weight percents were specified or if both atom and
236
      // weight percents were specified
237
      if (units == "macro") {
50,789!
238
        densities.push_back(density_);
×
239
      } else {
240
        bool has_ao = check_for_node(node_nuc, "ao");
50,789✔
241
        bool has_wo = check_for_node(node_nuc, "wo");
50,789✔
242

243
        if (!has_ao && !has_wo) {
50,789!
244
          fatal_error(
×
245
            "No atom or weight percent specified for nuclide: " + name);
×
246
        } else if (has_ao && has_wo) {
50,789!
247
          fatal_error("Cannot specify both atom and weight percents for a "
×
248
                      "nuclide: " +
×
249
                      name);
250
        }
251

252
        // Copy atom/weight percents
253
        if (has_ao) {
50,789✔
254
          densities.push_back(std::stod(get_node_value(node_nuc, "ao")));
85,582✔
255
        } else {
256
          densities.push_back(-std::stod(get_node_value(node_nuc, "wo")));
15,996✔
257
        }
258
      }
259
    }
50,789✔
260
  }
261

262
  // =======================================================================
263
  // READ AND PARSE <isotropic> element
264

265
  vector<std::string> iso_lab;
16,354✔
266
  if (check_for_node(node, "isotropic")) {
16,354✔
267
    iso_lab = get_node_array<std::string>(node, "isotropic");
360✔
268
  }
269

270
  // ========================================================================
271
  // COPY NUCLIDES TO ARRAYS IN MATERIAL
272

273
  // allocate arrays in Material object
274
  auto n = names.size();
16,354✔
275
  nuclide_.reserve(n);
16,354✔
276
  atom_density_ = tensor::Tensor<double>({n});
16,354✔
277
  if (settings::photon_transport)
16,354✔
278
    element_.reserve(n);
323✔
279

280
  for (int i = 0; i < n; ++i) {
70,340✔
281
    const auto& name {names[i]};
53,986✔
282

283
    // Check that this nuclide is listed in the nuclear data library
284
    // (cross_sections.xml for CE and the MGXS HDF5 for MG)
285
    if (settings::run_mode != RunMode::PLOTTING) {
53,986✔
286
      LibraryKey key {Library::Type::neutron, name};
53,121✔
287
      if (data::library_map.find(key) == data::library_map.end()) {
53,121!
288
        fatal_error("Could not find nuclide " + name +
×
289
                    " in the "
290
                    "nuclear data library.");
291
      }
292
    }
53,121✔
293

294
    // If this nuclide hasn't been encountered yet, we need to add its name
295
    // and alias to the nuclide_dict
296
    if (data::nuclide_map.find(name) == data::nuclide_map.end()) {
53,986✔
297
      int index = data::nuclide_map.size();
33,141✔
298
      data::nuclide_map[name] = index;
33,141✔
299
      nuclide_.push_back(index);
33,141✔
300
    } else {
301
      nuclide_.push_back(data::nuclide_map[name]);
20,845✔
302
    }
303

304
    // If the corresponding element hasn't been encountered yet and photon
305
    // transport will be used, we need to add its symbol to the element_dict
306
    if (settings::photon_transport) {
53,986✔
307
      std::string element = to_element(name);
1,330✔
308

309
      // Make sure photon cross section data is available
310
      if (settings::run_mode != RunMode::PLOTTING) {
1,330!
311
        LibraryKey key {Library::Type::photon, element};
1,330✔
312
        if (data::library_map.find(key) == data::library_map.end()) {
1,330!
313
          fatal_error(
×
314
            "Could not find element " + element + " in cross_sections.xml.");
×
315
        }
316
      }
1,330✔
317

318
      if (data::element_map.find(element) == data::element_map.end()) {
1,330✔
319
        int index = data::element_map.size();
693✔
320
        data::element_map[element] = index;
693✔
321
        element_.push_back(index);
693✔
322
      } else {
323
        element_.push_back(data::element_map[element]);
637✔
324
      }
325
    }
1,330✔
326

327
    // Copy atom/weight percent
328
    atom_density_(i) = densities[i];
53,986✔
329
  }
330

331
  if (settings::run_CE) {
16,354✔
332
    // By default, isotropic-in-lab is not used
333
    if (iso_lab.size() > 0) {
12,962✔
334
      p0_.resize(n);
180✔
335

336
      // Apply isotropic-in-lab treatment to specified nuclides
337
      for (int j = 0; j < n; ++j) {
1,695✔
338
        for (const auto& nuc : iso_lab) {
7,875!
339
          if (names[j] == nuc) {
7,875✔
340
            p0_[j] = true;
1,515✔
341
            break;
1,515✔
342
          }
343
        }
344
      }
345
    }
346
  }
347

348
  // Check to make sure either all atom percents or all weight percents are
349
  // given
350
  if (!((atom_density_ >= 0.0).all() || (atom_density_ <= 0.0).all())) {
50,906!
351
    fatal_error(
×
352
      "Cannot mix atom and weight percents in material " + std::to_string(id_));
×
353
  }
354

355
  // Determine density if it is a sum value
356
  if (sum_density)
16,354✔
357
    density_ = atom_density_.sum();
2,398✔
358

359
  if (check_for_node(node, "temperature")) {
16,354✔
360
    temperature_ = std::stod(get_node_value(node, "temperature"));
3,714✔
361
  }
362

363
  if (check_for_node(node, "volume")) {
16,354✔
364
    volume_ = std::stod(get_node_value(node, "volume"));
3,874✔
365
  }
366

367
  // =======================================================================
368
  // READ AND PARSE <sab> TAG FOR THERMAL SCATTERING DATA
369
  if (settings::run_CE) {
16,354✔
370
    // Loop over <sab> elements
371

372
    vector<std::string> sab_names;
12,962✔
373
    for (auto node_sab : node.children("sab")) {
15,119✔
374
      // Determine name of thermal scattering table
375
      if (!check_for_node(node_sab, "name")) {
2,157!
376
        fatal_error("Need to specify <name> for thermal scattering table.");
×
377
      }
378
      std::string name = get_node_value(node_sab, "name");
2,157✔
379
      sab_names.push_back(name);
2,157✔
380

381
      // Read the fraction of nuclei affected by this thermal scattering table
382
      double fraction = 1.0;
2,157✔
383
      if (check_for_node(node_sab, "fraction")) {
2,157✔
384
        fraction = std::stod(get_node_value(node_sab, "fraction"));
30✔
385
      }
386

387
      // Check that the thermal scattering table is listed in the
388
      // cross_sections.xml file
389
      if (settings::run_mode != RunMode::PLOTTING) {
2,157✔
390
        LibraryKey key {Library::Type::thermal, name};
2,104✔
391
        if (data::library_map.find(key) == data::library_map.end()) {
2,104!
392
          fatal_error("Could not find thermal scattering data " + name +
×
393
                      " in cross_sections.xml file.");
394
        }
395
      }
2,104✔
396

397
      // Determine index of thermal scattering data in global
398
      // data::thermal_scatt array
399
      int index_table;
2,157✔
400
      if (data::thermal_scatt_map.find(name) == data::thermal_scatt_map.end()) {
2,157✔
401
        index_table = data::thermal_scatt_map.size();
1,446✔
402
        data::thermal_scatt_map[name] = index_table;
1,446✔
403
      } else {
404
        index_table = data::thermal_scatt_map[name];
711✔
405
      }
406

407
      // Add entry to thermal tables vector. For now, we put the nuclide index
408
      // as zero since we don't know which nuclides the table is being applied
409
      // to yet (this is assigned in init_thermal)
410
      thermal_tables_.push_back({index_table, 0, fraction});
2,157✔
411
    }
2,157✔
412
  }
12,962✔
413
}
16,354✔
414

415
Material::~Material()
16,398✔
416
{
417
  model::material_map.erase(id_);
16,398✔
418
}
49,194✔
419

420
Material& Material::clone()
×
421
{
422
  std::unique_ptr<Material> mat = std::make_unique<Material>();
×
423

424
  // set all other parameters to whatever the calling Material has
425
  mat->name_ = name_;
×
426
  mat->nuclide_ = nuclide_;
×
427
  mat->element_ = element_;
×
428
  mat->ncrystal_mat_ = ncrystal_mat_.clone();
×
429
  mat->atom_density_ = atom_density_;
×
430
  mat->density_ = density_;
×
NEW
431
  if (settings::kinetic_simulation)
×
NEW
432
    mat->density_timeseries_ = density_timeseries_;
×
433
  mat->density_gpcc_ = density_gpcc_;
×
434
  mat->volume_ = volume_;
×
435
  mat->fissionable() = fissionable_;
×
436
  mat->depletable() = depletable_;
×
437
  mat->p0_ = p0_;
×
438
  mat->mat_nuclide_index_ = mat_nuclide_index_;
×
439
  mat->thermal_tables_ = thermal_tables_;
×
440
  mat->temperature_ = temperature_;
×
441

442
  if (ttb_)
×
443
    mat->ttb_ = std::make_unique<Bremsstrahlung>(*ttb_);
×
444

445
  mat->index_ = model::materials.size();
×
446
  mat->set_id(C_NONE);
×
447
  model::materials.push_back(std::move(mat));
×
448
  return *model::materials.back();
×
449
}
×
450

451
void Material::finalize()
16,065✔
452
{
453
  // Set fissionable if any nuclide is fissionable
454
  if (settings::run_CE) {
16,065✔
455
    for (const auto& i_nuc : nuclide_) {
44,837✔
456
      if (data::nuclides[i_nuc]->fissionable_) {
37,833✔
457
        fissionable_ = true;
5,669✔
458
        break;
5,669✔
459
      }
460
    }
461

462
    // Generate material bremsstrahlung data for electrons and positrons
463
    if (settings::photon_transport &&
12,673✔
464
        settings::electron_treatment == ElectronTreatment::TTB) {
323✔
465
      this->init_bremsstrahlung();
264✔
466
    }
467

468
    // Assign thermal scattering tables
469
    this->init_thermal();
12,673✔
470
  }
471

472
  // Normalize density
473
  this->normalize_density();
16,065✔
474
}
16,065✔
475

476
void Material::normalize_density()
16,065✔
477
{
478
  bool percent_in_atom = (atom_density_(0) >= 0.0);
16,065✔
479
  bool density_in_atom = (density_ >= 0.0);
16,065✔
480

481
  for (int i = 0; i < nuclide_.size(); ++i) {
69,189✔
482
    // determine atomic weight ratio
483
    int i_nuc = nuclide_[i];
53,124✔
484
    double awr = settings::run_CE ? data::nuclides[i_nuc]->awr_
53,124✔
485
                                  : data::mg.nuclides_[i_nuc].awr;
3,752✔
486

487
    // if given weight percent, convert all values so that they are divided
488
    // by awr. thus, when a sum is done over the values, it's actually
489
    // sum(w/awr)
490
    if (!percent_in_atom)
53,124✔
491
      atom_density_(i) = -atom_density_(i) / awr;
7,998✔
492
  }
493

494
  // determine normalized atom percents. if given atom percents, this is
495
  // straightforward. if given weight percents, the value is w/awr and is
496
  // divided by sum(w/awr)
497
  atom_density_ /= atom_density_.sum();
16,065✔
498

499
  // Change density in g/cm^3 to atom/b-cm. Since all values are now in
500
  // atom percent, the sum needs to be re-evaluated as 1/sum(x*awr)
501
  if (!density_in_atom) {
16,065✔
502
    double sum_percent = 0.0;
503
    for (int i = 0; i < nuclide_.size(); ++i) {
44,628✔
504
      int i_nuc = nuclide_[i];
34,496✔
505
      double awr = settings::run_CE ? data::nuclides[i_nuc]->awr_
34,496✔
506
                                    : data::mg.nuclides_[i_nuc].awr;
75✔
507
      sum_percent += atom_density_(i) * awr;
34,496✔
508
    }
509
    sum_percent = 1.0 / sum_percent;
10,132✔
510
    density_ = -density_ * N_AVOGADRO / MASS_NEUTRON * sum_percent;
10,132✔
511

512
    for (double& p : density_timeseries_)
10,132!
NEW
513
      p = -p * N_AVOGADRO / MASS_NEUTRON * sum_percent;
×
514
  }
515

516
  // Calculate nuclide atom densities
517
  atom_density_ *= density_;
16,065✔
518

519
  // Calculate density in [g/cm^3] and charge density in [e/b-cm]
520
  density_gpcc_ = 0.0;
16,065✔
521
  charge_density_ = 0.0;
16,065✔
522
  for (int i = 0; i < nuclide_.size(); ++i) {
69,189✔
523
    int i_nuc = nuclide_[i];
53,124✔
524
    double awr = settings::run_CE ? data::nuclides[i_nuc]->awr_ : 1.0;
53,124✔
525
    int z = settings::run_CE ? data::nuclides[i_nuc]->Z_ : 0.0;
53,124✔
526
    density_gpcc_ += atom_density_(i) * awr * MASS_NEUTRON / N_AVOGADRO;
53,124✔
527
    charge_density_ += atom_density_(i) * z;
53,124✔
528
  }
529
}
16,065✔
530

531
void Material::init_thermal()
18,737✔
532
{
533
  vector<ThermalTable> tables;
18,737✔
534

535
  std::unordered_set<int> already_checked;
18,737✔
536
  for (const auto& table : thermal_tables_) {
20,853✔
537
    // Make sure each S(a,b) table only gets checked once
538
    if (already_checked.find(table.index_table) != already_checked.end()) {
2,116!
539
      continue;
×
540
    }
541
    already_checked.insert(table.index_table);
2,116✔
542

543
    // In order to know which nuclide the S(a,b) table applies to, we need
544
    // to search through the list of nuclides for one which has a matching
545
    // name
546
    bool found = false;
547
    for (int j = 0; j < nuclide_.size(); ++j) {
13,261✔
548
      const auto& name {data::nuclides[nuclide_[j]]->name_};
11,145✔
549
      if (contains(data::thermal_scatt[table.index_table]->nuclides_, name)) {
11,145✔
550
        tables.push_back({table.index_table, j, table.fraction});
2,176✔
551
        found = true;
2,176✔
552
      }
553
    }
554

555
    // Check to make sure thermal scattering table matched a nuclide
556
    if (!found) {
2,116!
557
      fatal_error("Thermal scattering table " +
×
558
                  data::thermal_scatt[table.index_table]->name_ +
×
559
                  " did not match any nuclide on material " +
×
560
                  std::to_string(id_));
×
561
    }
562
  }
563

564
  // Make sure each nuclide only appears in one table.
565
  for (int j = 0; j < tables.size(); ++j) {
20,913✔
566
    for (int k = j + 1; k < tables.size(); ++k) {
2,416✔
567
      if (tables[j].index_nuclide == tables[k].index_nuclide) {
240!
568
        int index = nuclide_[tables[j].index_nuclide];
×
569
        auto name = data::nuclides[index]->name_;
×
570
        fatal_error(
×
571
          name + " in material " + std::to_string(id_) +
×
572
          " was found "
573
          "in multiple thermal scattering tables. Each nuclide can appear in "
574
          "only one table per material.");
575
      }
×
576
    }
577
  }
578

579
  // If there are multiple S(a,b) tables, we need to make sure that the
580
  // entries in i_sab_nuclides are sorted or else they won't be applied
581
  // correctly in the cross_section module.
582
  std::sort(tables.begin(), tables.end(), [](ThermalTable a, ThermalTable b) {
18,737✔
583
    return a.index_nuclide < b.index_nuclide;
165!
584
  });
585

586
  // Update the list of thermal tables
587
  thermal_tables_ = tables;
18,737✔
588
}
18,737✔
589

590
void Material::collision_stopping_power(double* s_col, bool positron)
528✔
591
{
592
  // Average electron number and average atomic weight
593
  double electron_density = 0.0;
528✔
594
  double mass_density = 0.0;
528✔
595

596
  // Log of the mean excitation energy of the material
597
  double log_I = 0.0;
528✔
598

599
  // Effective number of conduction electrons in the material
600
  double n_conduction = 0.0;
528✔
601

602
  // Oscillator strength and square of the binding energy for each oscillator
603
  // in material
604
  vector<double> f;
528✔
605
  vector<double> e_b_sq;
528✔
606

607
  for (int i = 0; i < element_.size(); ++i) {
2,542✔
608
    const auto& elm = *data::elements[element_[i]];
2,014!
609
    double awr = data::nuclides[nuclide_[i]]->awr_;
2,014!
610

611
    // Get atomic density of nuclide given atom/weight percent
612
    double atom_density =
2,014✔
613
      (atom_density_[0] > 0.0) ? atom_density_[i] : -atom_density_[i] / awr;
2,014!
614

615
    electron_density += atom_density * elm.Z_;
2,014✔
616
    mass_density += atom_density * awr * MASS_NEUTRON;
2,014✔
617
    log_I += atom_density * elm.Z_ * std::log(elm.I_);
2,014✔
618

619
    for (int j = 0; j < elm.n_electrons_.size(); ++j) {
15,920✔
620
      if (elm.n_electrons_[j] < 0) {
13,906✔
621
        n_conduction -= elm.n_electrons_[j] * atom_density;
1,420✔
622
        continue;
1,420✔
623
      }
624
      e_b_sq.push_back(elm.ionization_energy_[j] * elm.ionization_energy_[j]);
12,486✔
625
      f.push_back(elm.n_electrons_[j] * atom_density);
12,486✔
626
    }
627
  }
628
  log_I /= electron_density;
528✔
629
  n_conduction /= electron_density;
528✔
630
  for (auto& f_i : f)
13,014✔
631
    f_i /= electron_density;
12,486✔
632

633
  // Get density in g/cm^3 if it is given in atom/b-cm
634
  double density = (density_ < 0.0) ? -density_ : mass_density / N_AVOGADRO;
528✔
635

636
  // Calculate the square of the plasma energy
637
  double e_p_sq =
528✔
638
    PLANCK_C * PLANCK_C * PLANCK_C * N_AVOGADRO * electron_density * density /
528✔
639
    (2.0 * PI * PI * FINE_STRUCTURE * MASS_ELECTRON_EV * mass_density);
528✔
640

641
  // Get the Sternheimer adjustment factor
642
  double rho =
528✔
643
    sternheimer_adjustment(f, e_b_sq, e_p_sq, n_conduction, log_I, 1.0e-6, 100);
528✔
644

645
  // Classical electron radius in cm
646
  constexpr double CM_PER_ANGSTROM {1.0e-8};
528✔
647
  constexpr double r_e =
528✔
648
    CM_PER_ANGSTROM * PLANCK_C / (2.0 * PI * FINE_STRUCTURE * MASS_ELECTRON_EV);
649

650
  // Constant in expression for collision stopping power
651
  constexpr double BARN_PER_CM_SQ {1.0e24};
528✔
652
  double c =
528✔
653
    BARN_PER_CM_SQ * 2.0 * PI * r_e * r_e * MASS_ELECTRON_EV * electron_density;
654

655
  // Loop over incident charged particle energies
656
  for (int i = 0; i < data::ttb_e_grid.size(); ++i) {
105,868✔
657
    double E = data::ttb_e_grid(i);
105,340✔
658

659
    // Get the density effect correction
660
    double delta =
105,340✔
661
      density_effect(f, e_b_sq, e_p_sq, n_conduction, rho, E, 1.0e-6, 100);
105,340✔
662

663
    // Square of the ratio of the speed of light to the velocity of the charged
664
    // particle
665
    double beta_sq = E * (E + 2.0 * MASS_ELECTRON_EV) /
105,340✔
666
                     ((E + MASS_ELECTRON_EV) * (E + MASS_ELECTRON_EV));
105,340✔
667

668
    double tau = E / MASS_ELECTRON_EV;
105,340✔
669

670
    double F;
105,340✔
671
    if (positron) {
105,340✔
672
      double t = tau + 2.0;
52,670✔
673
      F = std::log(4.0) - (beta_sq / 12.0) * (23.0 + 14.0 / t + 10.0 / (t * t) +
52,670✔
674
                                               4.0 / (t * t * t));
52,670✔
675
    } else {
676
      F = (1.0 - beta_sq) *
105,340✔
677
          (1.0 + tau * tau / 8.0 - (2.0 * tau + 1.0) * std::log(2.0));
52,670✔
678
    }
679

680
    // Calculate the collision stopping power for this energy
681
    s_col[i] =
210,680✔
682
      c / beta_sq *
210,680✔
683
      (2.0 * (std::log(E) - log_I) + std::log(1.0 + tau / 2.0) + F - delta);
105,340✔
684
  }
685
}
528✔
686

687
void Material::init_bremsstrahlung()
264✔
688
{
689
  // Create new object
690
  ttb_ = make_unique<Bremsstrahlung>();
264✔
691

692
  // Get the size of the energy grids
693
  auto n_k = data::ttb_k_grid.size();
264✔
694
  auto n_e = data::ttb_e_grid.size();
264✔
695

696
  // Determine number of elements
697
  int n = element_.size();
264✔
698

699
  for (int particle = 0; particle < 2; ++particle) {
792✔
700
    // Loop over logic twice, once for electron, once for positron
701
    BremsstrahlungData* ttb =
528✔
702
      (particle == 0) ? &ttb_->electron : &ttb_->positron;
528✔
703
    bool positron = (particle == 1);
528✔
704

705
    // Allocate arrays for TTB data
706
    ttb->pdf = tensor::zeros<double>({n_e, n_e});
528✔
707
    ttb->cdf = tensor::zeros<double>({n_e, n_e});
528✔
708
    ttb->yield = tensor::zeros<double>({n_e});
528✔
709

710
    // Allocate temporary arrays
711
    auto stopping_power_collision = tensor::zeros<double>({n_e});
528✔
712
    auto stopping_power_radiative = tensor::zeros<double>({n_e});
528✔
713
    auto dcs = tensor::zeros<double>({n_e, n_k});
528✔
714

715
    double Z_eq_sq = 0.0;
528✔
716
    double sum_density = 0.0;
528✔
717

718
    // Get the collision stopping power of the material
719
    this->collision_stopping_power(stopping_power_collision.data(), positron);
528✔
720

721
    // Calculate the molecular DCS and the molecular radiative stopping power
722
    // using Bragg's additivity rule.
723
    for (int i = 0; i < n; ++i) {
2,542✔
724
      // Get pointer to current element
725
      const auto& elm = *data::elements[element_[i]];
2,014!
726
      double awr = data::nuclides[nuclide_[i]]->awr_;
2,014!
727

728
      // Get atomic density and mass density of nuclide given atom/weight
729
      // percent
730
      double atom_density =
2,014✔
731
        (atom_density_[0] > 0.0) ? atom_density_[i] : -atom_density_[i] / awr;
2,014!
732

733
      // Calculate the "equivalent" atomic number Zeq of the material
734
      Z_eq_sq += atom_density * elm.Z_ * elm.Z_;
2,014✔
735
      sum_density += atom_density;
2,014✔
736

737
      // Accumulate material DCS
738
      dcs += (atom_density * elm.Z_ * elm.Z_) * elm.dcs_;
2,014✔
739

740
      // Accumulate material radiative stopping power
741
      stopping_power_radiative += atom_density * elm.stopping_power_radiative_;
4,028✔
742
    }
743
    Z_eq_sq /= sum_density;
528✔
744

745
    // Calculate the positron DCS and radiative stopping power. These are
746
    // obtained by multiplying the electron DCS and radiative stopping powers by
747
    // a factor r, which is a numerical approximation of the ratio of the
748
    // radiative stopping powers for positrons and electrons. Source: F. Salvat,
749
    // J. M. Fernández-Varea, and J. Sempau, "PENELOPE-2011: A Code System for
750
    // Monte Carlo Simulation of Electron and Photon Transport," OECD-NEA,
751
    // Issy-les-Moulineaux, France (2011).
752
    if (positron) {
528✔
753
      for (int i = 0; i < n_e; ++i) {
52,934✔
754
        double t = std::log(
52,670✔
755
          1.0 + 1.0e6 * data::ttb_e_grid(i) / (Z_eq_sq * MASS_ELECTRON_EV));
52,670✔
756
        double r =
52,670✔
757
          1.0 -
758
          std::exp(-1.2359e-1 * t + 6.1274e-2 * std::pow(t, 2) -
52,670✔
759
                   3.1516e-2 * std::pow(t, 3) + 7.7446e-3 * std::pow(t, 4) -
52,670✔
760
                   1.0595e-3 * std::pow(t, 5) + 7.0568e-5 * std::pow(t, 6) -
52,670✔
761
                   1.808e-6 * std::pow(t, 7));
52,670✔
762
        stopping_power_radiative(i) *= r;
52,670✔
763
        tensor::View<double> dcs_i = dcs.slice(i);
52,670✔
764
        dcs_i *= r;
52,670✔
765
      }
52,670✔
766
    }
767

768
    // Total material stopping power
769
    tensor::Tensor<double> stopping_power =
528✔
770
      stopping_power_collision + stopping_power_radiative;
528✔
771

772
    // Loop over photon energies
773
    auto f = tensor::zeros<double>({n_e});
528✔
774
    auto z = tensor::zeros<double>({n_e});
528✔
775
    for (int i = 0; i < n_e - 1; ++i) {
105,340✔
776
      double w = data::ttb_e_grid(i);
104,812✔
777

778
      // Loop over incident particle energies
779
      for (int j = i; j < n_e; ++j) {
10,667,004✔
780
        double e = data::ttb_e_grid(j);
10,562,192✔
781

782
        // Reduced photon energy
783
        double k = w / e;
10,562,192✔
784

785
        // Find the lower bounding index of the reduced photon energy
786
        int i_k = lower_bound_index(
10,562,192✔
787
          data::ttb_k_grid.cbegin(), data::ttb_k_grid.cend(), k);
10,562,192✔
788

789
        // Get the interpolation bounds
790
        double k_l = data::ttb_k_grid(i_k);
10,562,192✔
791
        double k_r = data::ttb_k_grid(i_k + 1);
10,562,192✔
792
        double x_l = dcs(j, i_k);
10,562,192✔
793
        double x_r = dcs(j, i_k + 1);
10,562,192✔
794

795
        // Find the value of the DCS using linear interpolation in reduced
796
        // photon energy k
797
        double x = x_l + (k - k_l) * (x_r - x_l) / (k_r - k_l);
10,562,192✔
798

799
        // Square of the ratio of the speed of light to the velocity of the
800
        // charged particle
801
        double beta_sq = e * (e + 2.0 * MASS_ELECTRON_EV) /
10,562,192✔
802
                         ((e + MASS_ELECTRON_EV) * (e + MASS_ELECTRON_EV));
10,562,192✔
803

804
        // Compute the integrand of the PDF
805
        f(j) = x / (beta_sq * stopping_power(j) * w);
10,562,192✔
806
      }
807

808
      // Number of points to integrate
809
      int n = n_e - i;
104,812✔
810

811
      // Integrate the PDF using cubic spline integration over the incident
812
      // particle energy
813
      if (n > 2) {
104,812✔
814
        spline(n, &data::ttb_e_grid(i), &f(i), &z(i));
104,284✔
815

816
        double c = 0.0;
817
        for (int j = i; j < n_e - 1; ++j) {
10,561,136✔
818
          c += spline_integrate(n, &data::ttb_e_grid(i), &f(i), &z(i),
20,913,704✔
819
            data::ttb_e_grid(j), data::ttb_e_grid(j + 1));
10,456,852✔
820

821
          ttb->pdf(j + 1, i) = c;
10,456,852✔
822
        }
823

824
        // Integrate the last two points using trapezoidal rule in log-log space
825
      } else {
826
        double e_l = std::log(data::ttb_e_grid(i));
528✔
827
        double e_r = std::log(data::ttb_e_grid(i + 1));
528✔
828
        double x_l = std::log(f(i));
528✔
829
        double x_r = std::log(f(i + 1));
528✔
830

831
        ttb->pdf(i + 1, i) =
1,056✔
832
          0.5 * (e_r - e_l) * (std::exp(e_l + x_l) + std::exp(e_r + x_r));
528✔
833
      }
834
    }
835

836
    // Loop over incident particle energies
837
    for (int j = 1; j < n_e; ++j) {
105,340✔
838
      // Set last element of PDF to small non-zero value to enable log-log
839
      // interpolation
840
      ttb->pdf(j, j) = std::exp(-500.0);
104,812✔
841

842
      // Loop over photon energies
843
      double c = 0.0;
104,812✔
844
      for (int i = 0; i < j; ++i) {
10,562,192✔
845
        // Integrate the CDF from the PDF using the fact that the PDF is linear
846
        // in log-log space
847
        double w_l = std::log(data::ttb_e_grid(i));
10,457,380✔
848
        double w_r = std::log(data::ttb_e_grid(i + 1));
10,457,380✔
849
        double x_l = std::log(ttb->pdf(j, i));
10,457,380✔
850
        double x_r = std::log(ttb->pdf(j, i + 1));
10,457,380✔
851
        double beta = (x_r - x_l) / (w_r - w_l);
10,457,380✔
852
        double a = beta + 1.0;
10,457,380✔
853
        c += std::exp(w_l + x_l) / a * std::expm1(a * (w_r - w_l));
10,457,380✔
854
        ttb->cdf(j, i + 1) = c;
10,457,380✔
855
      }
856

857
      // Set photon number yield
858
      ttb->yield(j) = c;
104,812✔
859
    }
860

861
    // Use logarithm of number yield since it is log-log interpolated
862
    ttb->yield =
528✔
863
      tensor::where(ttb->yield > 0.0, tensor::log(ttb->yield), -500.0);
2,112✔
864
  }
3,168✔
865
}
264✔
866

867
void Material::init_nuclide_index()
22,462✔
868
{
869
  int n = settings::run_CE ? data::nuclides.size() : data::mg.nuclides_.size();
22,462✔
870
  mat_nuclide_index_.resize(n);
22,462✔
871
  std::fill(mat_nuclide_index_.begin(), mat_nuclide_index_.end(), C_NONE);
22,462✔
872
  for (int i = 0; i < nuclide_.size(); ++i) {
120,734✔
873
    mat_nuclide_index_[nuclide_[i]] = i;
98,272✔
874
  }
875
}
22,462✔
876

877
void Material::calculate_xs(Particle& p) const
1,845,763,134✔
878
{
879
  // Set all material macroscopic cross sections to zero
880
  p.macro_xs().total = 0.0;
1,845,763,134✔
881
  p.macro_xs().absorption = 0.0;
1,845,763,134✔
882
  p.macro_xs().fission = 0.0;
1,845,763,134✔
883
  p.macro_xs().nu_fission = 0.0;
1,845,763,134✔
884

885
  if (p.type().is_neutron()) {
1,845,763,134✔
886
    this->calculate_neutron_xs(p);
1,773,265,346✔
887
  } else if (p.type().is_photon()) {
72,497,788✔
888
    this->calculate_photon_xs(p);
18,355,532✔
889
  }
890
}
1,845,763,134✔
891

892
void Material::calculate_neutron_xs(Particle& p) const
1,773,265,346✔
893
{
894
  // Find energy index on energy grid
895
  int neutron = ParticleType::neutron().transport_index();
1,773,265,346✔
896
  int i_grid =
1,773,265,346✔
897
    std::log(p.E() / data::energy_min[neutron]) / simulation::log_spacing;
1,773,265,346✔
898

899
  // Determine if this material has S(a,b) tables
900
  bool check_sab = (thermal_tables_.size() > 0);
1,773,265,346✔
901

902
  // Initialize position in i_sab_nuclides
903
  int j = 0;
1,773,265,346✔
904

905
  // Calculate NCrystal cross section
906
  double ncrystal_xs = -1.0;
1,773,265,346✔
907
  if (ncrystal_mat_ && p.E() < NCRYSTAL_MAX_ENERGY) {
1,773,265,346!
908
    ncrystal_xs = ncrystal_mat_.xs(p);
11,158,829✔
909
  }
910

911
  // Add contribution from each nuclide in material
912
  for (int i = 0; i < nuclide_.size(); ++i) {
2,147,483,647✔
913
    // ======================================================================
914
    // CHECK FOR S(A,B) TABLE
915

916
    int i_sab = C_NONE;
2,147,483,647✔
917
    double sab_frac = 0.0;
2,147,483,647✔
918

919
    // Check if this nuclide matches one of the S(a,b) tables specified.
920
    // This relies on thermal_tables_ being sorted by .index_nuclide
921
    if (check_sab) {
2,147,483,647✔
922
      const auto& sab {thermal_tables_[j]};
849,895,893✔
923
      if (i == sab.index_nuclide) {
849,895,893✔
924
        // Get index in sab_tables
925
        i_sab = sab.index_table;
439,123,333✔
926
        sab_frac = sab.fraction;
439,123,333✔
927

928
        // If particle energy is greater than the highest energy for the
929
        // S(a,b) table, then don't use the S(a,b) table
930
        if (p.E() > data::thermal_scatt[i_sab]->energy_max_)
439,123,333✔
931
          i_sab = C_NONE;
260,370,772✔
932

933
        // Increment position in thermal_tables_
934
        ++j;
439,123,333✔
935

936
        // Don't check for S(a,b) tables if there are no more left
937
        if (j == thermal_tables_.size())
439,123,333✔
938
          check_sab = false;
438,889,792✔
939
      }
940
    }
941

942
    // ======================================================================
943
    // CALCULATE MICROSCOPIC CROSS SECTION
944

945
    // Get nuclide index
946
    int i_nuclide = nuclide_[i];
2,147,483,647✔
947

948
    // Update microscopic cross section for this nuclide
949
    p.update_neutron_xs(i_nuclide, i_grid, i_sab, sab_frac, ncrystal_xs);
2,147,483,647✔
950
    auto& micro = p.neutron_xs(i_nuclide);
2,147,483,647✔
951

952
    // ======================================================================
953
    // ADD TO MACROSCOPIC CROSS SECTION
954

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

958
    // Add contributions to cross sections
959
    p.macro_xs().total += atom_density * micro.total;
2,147,483,647✔
960
    p.macro_xs().absorption += atom_density * micro.absorption;
2,147,483,647✔
961
    p.macro_xs().fission += atom_density * micro.fission;
2,147,483,647✔
962
    p.macro_xs().nu_fission += atom_density * micro.nu_fission;
2,147,483,647✔
963
  }
964
}
1,773,265,346✔
965

966
void Material::calculate_photon_xs(Particle& p) const
18,355,532✔
967
{
968
  p.macro_xs().coherent = 0.0;
18,355,532✔
969
  p.macro_xs().incoherent = 0.0;
18,355,532✔
970
  p.macro_xs().photoelectric = 0.0;
18,355,532✔
971
  p.macro_xs().pair_production = 0.0;
18,355,532✔
972

973
  // Add contribution from each nuclide in material
974
  for (int i = 0; i < nuclide_.size(); ++i) {
98,994,501✔
975
    // ========================================================================
976
    // CALCULATE MICROSCOPIC CROSS SECTION
977

978
    // Determine microscopic cross sections for this nuclide
979
    int i_element = element_[i];
80,638,969✔
980

981
    // Calculate microscopic cross section for this nuclide
982
    const auto& micro {p.photon_xs(i_element)};
80,638,969✔
983
    if (p.E() != micro.last_E) {
80,638,969✔
984
      data::elements[i_element]->calculate_xs(p);
43,197,213✔
985
    }
986

987
    // ========================================================================
988
    // ADD TO MACROSCOPIC CROSS SECTION
989

990
    // Copy atom density of nuclide in material
991
    double atom_density = this->atom_density(i, p.density_mult());
80,638,969✔
992

993
    // Add contributions to material macroscopic cross sections
994
    p.macro_xs().total += atom_density * micro.total;
80,638,969✔
995
    p.macro_xs().coherent += atom_density * micro.coherent;
80,638,969✔
996
    p.macro_xs().incoherent += atom_density * micro.incoherent;
80,638,969✔
997
    p.macro_xs().photoelectric += atom_density * micro.photoelectric;
80,638,969✔
998
    p.macro_xs().pair_production += atom_density * micro.pair_production;
80,638,969✔
999
  }
1000
}
18,355,532✔
1001

1002
void Material::set_id(int32_t id)
16,398✔
1003
{
1004
  assert(id >= 0 || id == C_NONE);
16,398!
1005

1006
  // Clear entry in material map if an ID was already assigned before
1007
  if (id_ != C_NONE) {
16,398!
1008
    model::material_map.erase(id_);
×
1009
    id_ = C_NONE;
×
1010
  }
1011

1012
  // Make sure no other material has same ID
1013
  if (model::material_map.find(id) != model::material_map.end()) {
16,398!
1014
    throw std::runtime_error {
×
1015
      "Two materials have the same ID: " + std::to_string(id)};
×
1016
  }
1017

1018
  // If no ID specified, auto-assign next ID in sequence
1019
  if (id == C_NONE) {
16,398!
1020
    id = 0;
×
1021
    for (const auto& m : model::materials) {
×
1022
      id = std::max(id, m->id_);
×
1023
    }
1024
    ++id;
×
1025
  }
1026

1027
  // Update ID and entry in material map
1028
  id_ = id;
16,398✔
1029
  model::material_map[id] = index_;
16,398✔
1030
}
16,398✔
1031

1032
void Material::set_density(double density, const std::string& units)
6,295✔
1033
{
1034
  assert(density >= 0.0);
6,295!
1035

1036
  if (nuclide_.empty()) {
6,295!
1037
    throw std::runtime_error {"No nuclides exist in material yet."};
×
1038
  }
1039

1040
  if (units == "atom/b-cm") {
6,295✔
1041
    // Set total density based on value provided
1042
    density_ = density;
6,251✔
1043

1044
    // Determine normalized atom percents
1045
    double sum_percent = atom_density_.sum();
6,251✔
1046
    atom_density_ /= sum_percent;
70,708✔
1047

1048
    // Recalculate nuclide atom densities based on given density
1049
    atom_density_ *= density;
6,251✔
1050

1051
    // Calculate density in g/cm^3 and charge density in [e/b-cm]
1052
    density_gpcc_ = 0.0;
6,251✔
1053
    charge_density_ = 0.0;
6,251✔
1054
    for (int i = 0; i < nuclide_.size(); ++i) {
64,457✔
1055
      int i_nuc = nuclide_[i];
58,206!
1056
      double awr = data::nuclides[i_nuc]->awr_;
58,206!
1057
      int z = settings::run_CE ? data::nuclides[i_nuc]->Z_ : 0.0;
58,206!
1058
      density_gpcc_ += atom_density_(i) * awr * MASS_NEUTRON / N_AVOGADRO;
58,206✔
1059
      charge_density_ += atom_density_(i) * z;
58,206✔
1060
    }
1061
  } else if (units == "g/cm3" || units == "g/cc") {
55!
1062
    // Determine factor by which to change densities
1063
    double previous_density_gpcc = density_gpcc_;
33✔
1064
    double f = density / previous_density_gpcc;
33✔
1065

1066
    // Update densities
1067
    density_gpcc_ = density;
33✔
1068
    density_ *= f;
33✔
1069
    atom_density_ *= f;
33✔
1070
    charge_density_ *= f;
33✔
1071
  } else {
1072
    throw std::invalid_argument {
22✔
1073
      "Invalid units '" + std::string(units.data()) + "' specified."};
22✔
1074
  }
1075
}
6,284✔
1076

1077
void Material::set_densities(
6,064✔
1078
  const vector<std::string>& name, const vector<double>& density)
1079
{
1080
  auto n = name.size();
6,064✔
1081
  assert(n > 0);
6,064!
1082
  assert(n == density.size());
6,064!
1083

1084
  if (n != nuclide_.size()) {
6,064✔
1085
    nuclide_.resize(n);
1,428✔
1086
    atom_density_ = tensor::zeros<double>({n});
1,428✔
1087
    if (settings::photon_transport)
1,428!
1088
      element_.resize(n);
×
1089
  }
1090

1091
  double sum_density = 0.0;
1092
  for (int64_t i = 0; i < n; ++i) {
63,445✔
1093
    const auto& nuc {name[i]};
57,381✔
1094
    if (data::nuclide_map.find(nuc) == data::nuclide_map.end()) {
57,381!
1095
      int err = openmc_load_nuclide(nuc.c_str(), nullptr, 0);
×
1096
      if (err < 0)
×
1097
        throw std::runtime_error {openmc_err_msg};
×
1098
    }
1099

1100
    nuclide_[i] = data::nuclide_map.at(nuc);
57,381!
1101
    assert(density[i] > 0.0);
57,381!
1102
    atom_density_(i) = density[i];
57,381!
1103
    sum_density += density[i];
57,381✔
1104

1105
    if (settings::photon_transport) {
57,381!
1106
      auto element_name = to_element(nuc);
×
1107
      element_[i] = data::element_map.at(element_name);
×
1108
    }
×
1109
  }
1110

1111
  // Set total density to the sum of the vector
1112
  this->set_density(sum_density, "atom/b-cm");
6,064✔
1113

1114
  // Generate material bremsstrahlung data for electrons and positrons
1115
  if (settings::photon_transport &&
6,064!
1116
      settings::electron_treatment == ElectronTreatment::TTB) {
×
1117
    this->init_bremsstrahlung();
×
1118
  }
1119

1120
  // Assign S(a,b) tables
1121
  this->init_thermal();
6,064✔
1122
}
6,064✔
1123

1124
double Material::volume() const
55✔
1125
{
1126
  if (volume_ < 0.0) {
55✔
1127
    throw std::runtime_error {
11✔
1128
      "Volume for material with ID=" + std::to_string(id_) + " not set."};
22✔
1129
  }
1130
  return volume_;
44✔
1131
}
1132

1133
double Material::temperature() const
20,578✔
1134
{
1135
  // If material doesn't have an assigned temperature, use global default
1136
  return temperature_ >= 0 ? temperature_ : settings::temperature_default;
20,578✔
1137
}
1138

1139
void Material::to_hdf5(hid_t group) const
13,531✔
1140
{
1141
  hid_t material_group = create_group(group, "material " + std::to_string(id_));
13,531✔
1142

1143
  write_attribute(material_group, "depletable", static_cast<int>(depletable()));
13,531✔
1144
  if (volume_ > 0.0) {
13,531✔
1145
    write_attribute(material_group, "volume", volume_);
1,926✔
1146
  }
1147
  if (temperature_ > 0.0) {
13,531✔
1148
    write_attribute(material_group, "temperature", temperature_);
1,809✔
1149
  }
1150
  write_dataset(material_group, "name", name_);
13,531✔
1151
  write_dataset(material_group, "atom_density", density_);
13,531✔
1152

1153
  // Copy nuclide/macro name for each nuclide to vector
1154
  vector<std::string> nuc_names;
13,531✔
1155
  vector<std::string> macro_names;
13,531✔
1156
  vector<double> nuc_densities;
13,531✔
1157
  if (settings::run_CE) {
13,531✔
1158
    for (int i = 0; i < nuclide_.size(); ++i) {
55,684✔
1159
      int i_nuc = nuclide_[i];
44,386✔
1160
      nuc_names.push_back(data::nuclides[i_nuc]->name_);
44,386✔
1161
      nuc_densities.push_back(atom_density_(i));
44,386✔
1162
    }
1163
  } else {
1164
    for (int i = 0; i < nuclide_.size(); ++i) {
4,499✔
1165
      int i_nuc = nuclide_[i];
2,266✔
1166
      if (data::mg.nuclides_[i_nuc].awr != MACROSCOPIC_AWR) {
2,266✔
1167
        nuc_names.push_back(data::mg.nuclides_[i_nuc].name);
88✔
1168
        nuc_densities.push_back(atom_density_(i));
88✔
1169
      } else {
1170
        macro_names.push_back(data::mg.nuclides_[i_nuc].name);
2,178✔
1171
      }
1172
    }
1173
  }
1174

1175
  // Write vector to 'nuclides'
1176
  if (!nuc_names.empty()) {
13,531✔
1177
    write_dataset(material_group, "nuclides", nuc_names);
11,353✔
1178
    write_dataset(material_group, "nuclide_densities", nuc_densities);
11,353✔
1179
  }
1180

1181
  // Write vector to 'macroscopics'
1182
  if (!macro_names.empty()) {
13,531✔
1183
    write_dataset(material_group, "macroscopics", macro_names);
2,178✔
1184
  }
1185

1186
  if (!thermal_tables_.empty()) {
13,531✔
1187
    vector<std::string> sab_names;
1,715✔
1188
    for (const auto& table : thermal_tables_) {
3,496✔
1189
      sab_names.push_back(data::thermal_scatt[table.index_table]->name_);
1,781✔
1190
    }
1191
    write_dataset(material_group, "sab_names", sab_names);
1,715✔
1192
  }
1,715✔
1193

1194
  close_group(material_group);
13,531✔
1195
}
13,531✔
1196

1197
void Material::export_properties_hdf5(hid_t group) const
165✔
1198
{
1199
  hid_t material_group = create_group(group, "material " + std::to_string(id_));
165✔
1200
  write_attribute(material_group, "atom_density", density_);
165✔
1201
  write_attribute(material_group, "mass_density", density_gpcc_);
165✔
1202
  close_group(material_group);
165✔
1203
}
165✔
1204

1205
void Material::import_properties_hdf5(hid_t group)
132✔
1206
{
1207
  hid_t material_group = open_group(group, "material " + std::to_string(id_));
132✔
1208
  double density;
132✔
1209
  read_attribute(material_group, "atom_density", density);
132✔
1210
  this->set_density(density, "atom/b-cm");
132✔
1211
  close_group(material_group);
132✔
1212
}
132✔
1213

1214
void Material::add_nuclide(const std::string& name, double density)
11✔
1215
{
1216
  // Check if nuclide is already in material
1217
  for (int i = 0; i < nuclide_.size(); ++i) {
55✔
1218
    int i_nuc = nuclide_[i];
44!
1219
    if (data::nuclides[i_nuc]->name_ == name) {
44!
1220
      double awr = data::nuclides[i_nuc]->awr_;
×
1221
      density_ += density - atom_density_(i);
×
1222
      density_gpcc_ +=
×
1223
        (density - atom_density_(i)) * awr * MASS_NEUTRON / N_AVOGADRO;
×
1224
      atom_density_(i) = density;
×
1225
      return;
×
1226
    }
1227
  }
1228

1229
  // If nuclide wasn't found, extend nuclide/density arrays
1230
  int err = openmc_load_nuclide(name.c_str(), nullptr, 0);
11✔
1231
  if (err < 0)
11!
1232
    throw std::runtime_error {openmc_err_msg};
×
1233

1234
  // Append new nuclide/density
1235
  int i_nuc = data::nuclide_map[name];
11✔
1236
  nuclide_.push_back(i_nuc);
11✔
1237

1238
  // Append new element if photon transport is on
1239
  if (settings::photon_transport) {
11!
1240
    int i_elem = data::element_map[to_element(name)];
×
1241
    element_.push_back(i_elem);
×
1242
  }
1243

1244
  auto n = nuclide_.size();
11✔
1245

1246
  // Create copy of atom_density_ array with one extra entry
1247
  tensor::Tensor<double> atom_density = tensor::zeros<double>({n});
11✔
1248
  atom_density.slice(tensor::range(0, n - 1)) = atom_density_;
11✔
1249
  atom_density(n - 1) = density;
11✔
1250
  atom_density_ = atom_density;
11✔
1251

1252
  density_ += density;
11✔
1253
  density_gpcc_ +=
11✔
1254
    density * data::nuclides[i_nuc]->awr_ * MASS_NEUTRON / N_AVOGADRO;
11✔
1255
}
11✔
1256

1257
//==============================================================================
1258
// Non-method functions
1259
//==============================================================================
1260

1261
double sternheimer_adjustment(const vector<double>& f,
528✔
1262
  const vector<double>& e_b_sq, double e_p_sq, double n_conduction,
1263
  double log_I, double tol, int max_iter)
1264
{
1265
  // Get the total number of oscillators
1266
  int n = f.size();
528✔
1267

1268
  // Calculate the Sternheimer adjustment factor using Newton's method
1269
  double rho = 2.0;
528✔
1270
  int iter;
528✔
1271
  for (iter = 0; iter < max_iter; ++iter) {
2,112!
1272
    double rho_0 = rho;
1273

1274
    // Function to find the root of and its derivative
1275
    double g = 0.0;
1276
    double gp = 0.0;
1277

1278
    for (int i = 0; i < n; ++i) {
52,598✔
1279
      // Square of resonance energy of a bound-shell oscillator
1280
      double e_r_sq = e_b_sq[i] * rho * rho + 2.0 / 3.0 * f[i] * e_p_sq;
50,486✔
1281
      g += f[i] * std::log(e_r_sq);
50,486✔
1282
      gp += e_b_sq[i] * f[i] * rho / e_r_sq;
50,486✔
1283
    }
1284
    // Include conduction electrons
1285
    if (n_conduction > 0.0) {
2,112✔
1286
      g += n_conduction * std::log(n_conduction * e_p_sq);
1,748✔
1287
    }
1288

1289
    // Set the next guess: rho_n+1 = rho_n - g(rho_n)/g'(rho_n)
1290
    rho -= (g - 2.0 * log_I) / (2.0 * gp);
2,112✔
1291

1292
    // If the initial guess is too large, rho can be negative
1293
    if (rho < 0.0)
2,112!
1294
      rho = rho_0 / 2.0;
×
1295

1296
    // Check for convergence
1297
    if (std::abs(rho - rho_0) / rho_0 < tol)
2,112✔
1298
      break;
1299
  }
1300
  // Did not converge
1301
  if (iter >= max_iter) {
528!
1302
    warning("Maximum Newton-Raphson iterations exceeded.");
×
1303
    rho = 1.0e-6;
×
1304
  }
1305
  return rho;
528✔
1306
}
1307

1308
double density_effect(const vector<double>& f, const vector<double>& e_b_sq,
105,340✔
1309
  double e_p_sq, double n_conduction, double rho, double E, double tol,
1310
  int max_iter)
1311
{
1312
  // Get the total number of oscillators
1313
  int n = f.size();
105,340✔
1314

1315
  // Square of the ratio of the speed of light to the velocity of the charged
1316
  // particle
1317
  double beta_sq = E * (E + 2.0 * MASS_ELECTRON_EV) /
105,340✔
1318
                   ((E + MASS_ELECTRON_EV) * (E + MASS_ELECTRON_EV));
105,340✔
1319

1320
  // For nonmetals, delta = 0 for beta < beta_0, where beta_0 is obtained by
1321
  // setting the frequency w = 0.
1322
  double beta_0_sq = 0.0;
105,340✔
1323
  if (n_conduction == 0.0) {
105,340✔
1324
    for (int i = 0; i < n; ++i) {
136,800✔
1325
      beta_0_sq += f[i] * e_p_sq / (e_b_sq[i] * rho * rho);
116,000✔
1326
    }
1327
    beta_0_sq = 1.0 / (1.0 + beta_0_sq);
20,800✔
1328
  }
1329
  double delta = 0.0;
105,340✔
1330
  if (beta_sq < beta_0_sq)
105,340✔
1331
    return delta;
1332

1333
  // Compute the square of the frequency w^2 using Newton's method, with the
1334
  // initial guess of w^2 equal to beta^2 * gamma^2
1335
  double w_sq = E / MASS_ELECTRON_EV * (E / MASS_ELECTRON_EV + 2);
94,608✔
1336
  int iter;
94,608✔
1337
  for (iter = 0; iter < max_iter; ++iter) {
634,422!
1338
    double w_sq_0 = w_sq;
1339

1340
    // Function to find the root of and its derivative
1341
    double g = 0.0;
1342
    double gp = 0.0;
1343

1344
    for (int i = 0; i < n; ++i) {
18,499,238✔
1345
      double c = e_b_sq[i] * rho * rho / e_p_sq + w_sq;
17,864,816✔
1346
      g += f[i] / c;
17,864,816✔
1347
      gp -= f[i] / (c * c);
17,864,816✔
1348
    }
1349
    // Include conduction electrons
1350
    g += n_conduction / w_sq;
634,422✔
1351
    gp -= n_conduction / (w_sq * w_sq);
634,422✔
1352

1353
    // Set the next guess: w_n+1 = w_n - g(w_n)/g'(w_n)
1354
    w_sq -= (g + 1.0 - 1.0 / beta_sq) / gp;
634,422✔
1355

1356
    // If the initial guess is too large, w can be negative
1357
    if (w_sq < 0.0)
634,422✔
1358
      w_sq = w_sq_0 / 2.0;
135,564✔
1359

1360
    // Check for convergence
1361
    if (std::abs(w_sq - w_sq_0) / w_sq_0 < tol)
634,422✔
1362
      break;
1363
  }
1364
  // Did not converge
1365
  if (iter >= max_iter) {
94,608!
1366
    warning("Maximum Newton-Raphson iterations exceeded: setting density "
×
1367
            "effect correction to zero.");
1368
    return delta;
×
1369
  }
1370

1371
  // Solve for the density effect correction
1372
  for (int i = 0; i < n; ++i) {
2,528,044✔
1373
    double l_sq = e_b_sq[i] * rho * rho / e_p_sq + 2.0 / 3.0 * f[i];
2,433,436✔
1374
    delta += f[i] * std::log((l_sq + w_sq) / l_sq);
2,433,436✔
1375
  }
1376
  // Include conduction electrons
1377
  if (n_conduction > 0.0) {
94,608✔
1378
    delta += n_conduction * std::log((n_conduction + w_sq) / n_conduction);
84,540✔
1379
  }
1380

1381
  return delta - w_sq * (1.0 - beta_sq);
94,608✔
1382
}
1383

1384
void read_materials_xml()
1,339✔
1385
{
1386
  write_message("Reading materials XML file...", 5);
1,339✔
1387

1388
  pugi::xml_document doc;
1,339✔
1389

1390
  // Check if materials.xml exists
1391
  std::string filename = settings::path_input + "materials.xml";
1,339✔
1392
  if (!file_exists(filename)) {
1,339!
1393
    fatal_error("Material XML file '" + filename + "' does not exist!");
×
1394
  }
1395

1396
  // Parse materials.xml file and get root element
1397
  doc.load_file(filename.c_str());
1,339✔
1398

1399
  // Loop over XML material elements and populate the array.
1400
  pugi::xml_node root = doc.document_element();
1,339✔
1401

1402
  read_materials_xml(root);
1,339✔
1403
}
1,339✔
1404

1405
void read_materials_xml(pugi::xml_node root)
8,525✔
1406
{
1407
  for (pugi::xml_node material_node : root.children("material")) {
24,871✔
1408
    model::materials.push_back(make_unique<Material>(material_node));
32,692✔
1409
  }
1410
  model::materials.shrink_to_fit();
8,525✔
1411
}
8,525✔
1412

1413
void free_memory_material()
8,654✔
1414
{
1415
  model::materials.clear();
8,654✔
1416
  model::material_map.clear();
8,654✔
1417
}
8,654✔
1418

1419
//==============================================================================
1420
// C API
1421
//==============================================================================
1422

1423
extern "C" int openmc_get_material_index(int32_t id, int32_t* index)
8,155✔
1424
{
1425
  auto it = model::material_map.find(id);
8,155✔
1426
  if (it == model::material_map.end()) {
8,155✔
1427
    set_errmsg("No material exists with ID=" + std::to_string(id) + ".");
11✔
1428
    return OPENMC_E_INVALID_ID;
11✔
1429
  } else {
1430
    *index = it->second;
8,144✔
1431
    return 0;
8,144✔
1432
  }
1433
}
1434

1435
extern "C" int openmc_material_add_nuclide(
11✔
1436
  int32_t index, const char* name, double density)
1437
{
1438
  int err = 0;
11✔
1439
  if (index >= 0 && index < model::materials.size()) {
11!
1440
    try {
11✔
1441
      model::materials[index]->add_nuclide(name, density);
11✔
1442
    } catch (const std::runtime_error& e) {
×
1443
      return OPENMC_E_DATA;
×
1444
    }
×
1445
  } else {
1446
    set_errmsg("Index in materials array is out of bounds.");
×
1447
    return OPENMC_E_OUT_OF_BOUNDS;
×
1448
  }
1449
  return err;
11✔
1450
}
1451

1452
extern "C" int openmc_material_get_densities(
231✔
1453
  int32_t index, const int** nuclides, const double** densities, int* n)
1454
{
1455
  if (index >= 0 && index < model::materials.size()) {
231!
1456
    auto& mat = model::materials[index];
231!
1457
    if (!mat->nuclides().empty()) {
231!
1458
      *nuclides = mat->nuclides().data();
231✔
1459
      *densities = mat->densities().data();
231✔
1460
      *n = mat->nuclides().size();
231✔
1461
      return 0;
231✔
1462
    } else {
1463
      set_errmsg("Material atom density array has not been allocated.");
×
1464
      return OPENMC_E_ALLOCATE;
×
1465
    }
1466
  } else {
1467
    set_errmsg("Index in materials array is out of bounds.");
×
1468
    return OPENMC_E_OUT_OF_BOUNDS;
×
1469
  }
1470
}
1471

1472
extern "C" int openmc_material_get_density(int32_t index, double* density)
33✔
1473
{
1474
  if (index >= 0 && index < model::materials.size()) {
33!
1475
    auto& mat = model::materials[index];
33!
1476
    *density = mat->density_gpcc();
33!
1477
    return 0;
33✔
1478
  } else {
1479
    set_errmsg("Index in materials array is out of bounds.");
×
1480
    return OPENMC_E_OUT_OF_BOUNDS;
×
1481
  }
1482
}
1483

1484
extern "C" int openmc_material_get_fissionable(int32_t index, bool* fissionable)
×
1485
{
1486
  if (index >= 0 && index < model::materials.size()) {
×
1487
    *fissionable = model::materials[index]->fissionable();
×
1488
    return 0;
×
1489
  } else {
1490
    set_errmsg("Index in materials array is out of bounds.");
×
1491
    return OPENMC_E_OUT_OF_BOUNDS;
×
1492
  }
1493
}
1494

1495
extern "C" int openmc_material_get_id(int32_t index, int32_t* id)
9,371✔
1496
{
1497
  if (index >= 0 && index < model::materials.size()) {
9,371!
1498
    *id = model::materials[index]->id();
9,371✔
1499
    return 0;
9,371✔
1500
  } else {
1501
    set_errmsg("Index in materials array is out of bounds.");
×
1502
    return OPENMC_E_OUT_OF_BOUNDS;
×
1503
  }
1504
}
1505

1506
extern "C" int openmc_material_get_temperature(
66✔
1507
  int32_t index, double* temperature)
1508
{
1509
  if (index < 0 || index >= model::materials.size()) {
66!
1510
    set_errmsg("Index in materials array is out of bounds.");
×
1511
    return OPENMC_E_OUT_OF_BOUNDS;
×
1512
  }
1513
  *temperature = model::materials[index]->temperature();
66✔
1514
  return 0;
66✔
1515
}
1516

1517
extern "C" int openmc_material_get_volume(int32_t index, double* volume)
55✔
1518
{
1519
  if (index >= 0 && index < model::materials.size()) {
55!
1520
    try {
55✔
1521
      *volume = model::materials[index]->volume();
55✔
1522
    } catch (const std::exception& e) {
11!
1523
      set_errmsg(e.what());
11✔
1524
      return OPENMC_E_UNASSIGNED;
11✔
1525
    }
11✔
1526
    return 0;
44✔
1527
  } else {
1528
    set_errmsg("Index in materials array is out of bounds.");
×
1529
    return OPENMC_E_OUT_OF_BOUNDS;
×
1530
  }
1531
}
1532

1533
extern "C" int openmc_material_set_density(
99✔
1534
  int32_t index, double density, const char* units)
1535
{
1536
  if (index >= 0 && index < model::materials.size()) {
99!
1537
    try {
99✔
1538
      model::materials[index]->set_density(density, units);
110✔
1539
    } catch (const std::exception& e) {
11!
1540
      set_errmsg(e.what());
11✔
1541
      return OPENMC_E_UNASSIGNED;
11✔
1542
    }
11✔
1543
  } else {
1544
    set_errmsg("Index in materials array is out of bounds.");
×
1545
    return OPENMC_E_OUT_OF_BOUNDS;
×
1546
  }
1547
  return 0;
88✔
1548
}
1549

1550
extern "C" int openmc_material_set_densities(
6,064✔
1551
  int32_t index, int n, const char** name, const double* density)
1552
{
1553
  if (index >= 0 && index < model::materials.size()) {
6,064!
1554
    try {
6,064✔
1555
      model::materials[index]->set_densities(
6,064✔
1556
        {name, name + n}, {density, density + n});
6,064✔
1557
    } catch (const std::exception& e) {
×
1558
      set_errmsg(e.what());
×
1559
      return OPENMC_E_UNASSIGNED;
×
1560
    }
×
1561
  } else {
1562
    set_errmsg("Index in materials array is out of bounds.");
×
1563
    return OPENMC_E_OUT_OF_BOUNDS;
×
1564
  }
1565
  return 0;
6,064✔
1566
}
1567

1568
extern "C" int openmc_material_set_id(int32_t index, int32_t id)
44✔
1569
{
1570
  if (index >= 0 && index < model::materials.size()) {
44!
1571
    try {
44✔
1572
      model::materials.at(index)->set_id(id);
44✔
1573
    } catch (const std::exception& e) {
×
1574
      set_errmsg(e.what());
×
1575
      return OPENMC_E_UNASSIGNED;
×
1576
    }
×
1577
  } else {
1578
    set_errmsg("Index in materials array is out of bounds.");
×
1579
    return OPENMC_E_OUT_OF_BOUNDS;
×
1580
  }
1581
  return 0;
1582
}
1583

1584
extern "C" int openmc_material_get_name(int32_t index, const char** name)
33✔
1585
{
1586
  if (index < 0 || index >= model::materials.size()) {
33!
1587
    set_errmsg("Index in materials array is out of bounds.");
×
1588
    return OPENMC_E_OUT_OF_BOUNDS;
×
1589
  }
1590

1591
  *name = model::materials[index]->name().data();
33✔
1592

1593
  return 0;
33✔
1594
}
1595

1596
extern "C" int openmc_material_set_name(int32_t index, const char* name)
11✔
1597
{
1598
  if (index < 0 || index >= model::materials.size()) {
11!
1599
    set_errmsg("Index in materials array is out of bounds.");
×
1600
    return OPENMC_E_OUT_OF_BOUNDS;
×
1601
  }
1602

1603
  model::materials[index]->set_name(name);
22✔
1604

1605
  return 0;
11✔
1606
}
1607

1608
extern "C" int openmc_material_set_volume(int32_t index, double volume)
47✔
1609
{
1610
  if (index >= 0 && index < model::materials.size()) {
47!
1611
    auto& m {model::materials[index]};
47!
1612
    if (volume >= 0.0) {
47!
1613
      m->volume_ = volume;
47✔
1614
      return 0;
47✔
1615
    } else {
1616
      set_errmsg("Volume must be non-negative");
×
1617
      return OPENMC_E_INVALID_ARGUMENT;
×
1618
    }
1619
  } else {
1620
    set_errmsg("Index in materials array is out of bounds.");
×
1621
    return OPENMC_E_OUT_OF_BOUNDS;
×
1622
  }
1623
}
1624

1625
extern "C" int openmc_material_get_depletable(int32_t index, bool* depletable)
22✔
1626
{
1627
  if (index < 0 || index >= model::materials.size()) {
22!
1628
    set_errmsg("Index in materials array is out of bounds.");
×
1629
    return OPENMC_E_OUT_OF_BOUNDS;
×
1630
  }
1631

1632
  *depletable = model::materials[index]->depletable();
22✔
1633

1634
  return 0;
22✔
1635
}
1636

1637
extern "C" int openmc_material_set_depletable(int32_t index, bool depletable)
11✔
1638
{
1639
  if (index < 0 || index >= model::materials.size()) {
11!
1640
    set_errmsg("Index in materials array is out of bounds.");
×
1641
    return OPENMC_E_OUT_OF_BOUNDS;
×
1642
  }
1643

1644
  model::materials[index]->depletable() = depletable;
11✔
1645

1646
  return 0;
11✔
1647
}
1648

1649
extern "C" int openmc_extend_materials(
44✔
1650
  int32_t n, int32_t* index_start, int32_t* index_end)
1651
{
1652
  if (index_start)
44!
1653
    *index_start = model::materials.size();
44✔
1654
  if (index_end)
44!
1655
    *index_end = model::materials.size() + n - 1;
×
1656
  for (int32_t i = 0; i < n; i++) {
88✔
1657
    model::materials.push_back(make_unique<Material>());
44✔
1658
  }
1659
  return 0;
44✔
1660
}
1661

1662
extern "C" size_t n_materials()
77✔
1663
{
1664
  return model::materials.size();
77✔
1665
}
1666

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

© 2026 Coveralls, Inc