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

openmc-dev / openmc / 13644212396

04 Mar 2025 01:29AM UTC coverage: 85.186% (+0.1%) from 85.042%
13644212396

Pull #3305

github

web-flow
Merge 0097aa73d into e2557bbe2
Pull Request #3305: New Feature Development: The Implementation of chord length sampling (CLS) method for stochastic media( #3286 )

6 of 60 new or added lines in 1 file covered. (10.0%)

31 existing lines in 1 file now uncovered.

51208 of 60113 relevant lines covered (85.19%)

32715476.0 hits per line

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

76.96
/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 "xtensor/xbuilder.hpp"
12
#include "xtensor/xoperation.hpp"
13
#include "xtensor/xview.hpp"
14

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

33
namespace openmc {
34

35
//==============================================================================
36
// Global variables
37
//==============================================================================
38

39
namespace model {
40

41
std::unordered_map<int32_t, int32_t> material_map;
42
vector<unique_ptr<Material>> materials;
43

44
std::unordered_map<int32_t, int32_t> stochastic_media_map;
45
vector<unique_ptr<Stochastic_Media>> stochastic_media;
46

47
} // namespace model
48

49
//==============================================================================
50
// Material implementation
51
//==============================================================================
52

53
Material::Material(pugi::xml_node node)
13,886✔
54
{
55
  index_ = model::materials.size(); // Avoids warning about narrowing
13,886✔
56

57
  if (check_for_node(node, "id")) {
13,886✔
58
    this->set_id(std::stoi(get_node_value(node, "id")));
13,886✔
59
  } else {
60
    fatal_error("Must specify id of material in materials XML file.");
×
61
  }
62

63
  if (check_for_node(node, "name")) {
13,886✔
64
    name_ = get_node_value(node, "name");
6,238✔
65
  }
66

67
  if (check_for_node(node, "cfg")) {
13,886✔
68
    auto cfg = get_node_value(node, "cfg");
1✔
69
    write_message(
1✔
70
      5, "NCrystal config string for material #{}: '{}'", this->id(), cfg);
1✔
71
    ncrystal_mat_ = NCrystalMat(cfg);
1✔
72
  }
1✔
73

74
  if (check_for_node(node, "depletable")) {
13,886✔
75
    depletable_ = get_node_value_bool(node, "depletable");
5,165✔
76
  }
77

78
  bool sum_density {false};
13,886✔
79
  pugi::xml_node density_node = node.child("density");
13,886✔
80
  std::string units;
13,886✔
81
  if (density_node) {
13,886✔
82
    units = get_node_value(density_node, "units");
13,886✔
83
    if (units == "sum") {
13,886✔
84
      sum_density = true;
2,591✔
85
    } else if (units == "macro") {
11,295✔
86
      if (check_for_node(density_node, "value")) {
1,789✔
87
        density_ = std::stod(get_node_value(density_node, "value"));
1,789✔
88
      } else {
89
        density_ = 1.0;
×
90
      }
91
    } else {
92
      double val = std::stod(get_node_value(density_node, "value"));
9,506✔
93
      if (val <= 0.0) {
9,506✔
94
        fatal_error("Need to specify a positive density on material " +
×
95
                    std::to_string(id_) + ".");
×
96
      }
97

98
      if (units == "g/cc" || units == "g/cm3") {
9,506✔
99
        density_ = -val;
9,044✔
100
      } else if (units == "kg/m3") {
462✔
101
        density_ = -1.0e-3 * val;
17✔
102
      } else if (units == "atom/b-cm") {
445✔
103
        density_ = val;
428✔
104
      } else if (units == "atom/cc" || units == "atom/cm3") {
17✔
105
        density_ = 1.0e-24 * val;
17✔
106
      } else {
107
        fatal_error("Unknown units '" + units + "' specified on material " +
×
108
                    std::to_string(id_) + ".");
×
109
      }
110
    }
111
  } else {
112
    fatal_error("Must specify <density> element in material " +
×
113
                std::to_string(id_) + ".");
×
114
  }
115

116
  if (node.child("element")) {
13,886✔
117
    fatal_error(
×
118
      "Unable to add an element to material " + std::to_string(id_) +
×
119
      " since the element option has been removed from the xml input. "
120
      "Elements can only be added via the Python API, which will expand "
121
      "elements into their natural nuclides.");
122
  }
123

124
  // =======================================================================
125
  // READ AND PARSE <nuclide> TAGS
126

127
  // Check to ensure material has at least one nuclide
128
  if (!check_for_node(node, "nuclide") &&
15,675✔
129
      !check_for_node(node, "macroscopic")) {
1,789✔
130
    fatal_error("No macroscopic data or nuclides specified on material " +
×
131
                std::to_string(id_));
×
132
  }
133

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

142
  vector<std::string> names;
13,886✔
143
  vector<double> densities;
13,886✔
144
  if (settings::run_CE && num_macros > 0) {
13,886✔
145
    fatal_error("Macroscopic can not be used in continuous-energy mode.");
×
146
  } else if (num_macros > 1) {
13,886✔
147
    fatal_error("Only one macroscopic object permitted per material, " +
×
148
                std::to_string(id_));
×
149
  } else if (num_macros == 1) {
13,886✔
150
    pugi::xml_node node_nuc = *node_macros.begin();
1,789✔
151

152
    // Check for empty name on nuclide
153
    if (!check_for_node(node_nuc, "name")) {
1,789✔
154
      fatal_error("No name specified on macroscopic data in material " +
×
155
                  std::to_string(id_));
×
156
    }
157

158
    // store nuclide name
159
    std::string name = get_node_value(node_nuc, "name", false, true);
1,789✔
160
    names.push_back(name);
1,789✔
161

162
    // Set density for macroscopic data
163
    if (units == "macro") {
1,789✔
164
      densities.push_back(density_);
1,789✔
165
    } else {
166
      fatal_error("Units can only be macro for macroscopic data " + name);
×
167
    }
168
  } else {
1,789✔
169
    // Create list of nuclides based on those specified
170
    for (auto node_nuc : node.children("nuclide")) {
63,513✔
171
      // Check for empty name on nuclide
172
      if (!check_for_node(node_nuc, "name")) {
51,416✔
173
        fatal_error(
×
174
          "No name specified on nuclide in material " + std::to_string(id_));
×
175
      }
176

177
      // store nuclide name
178
      std::string name = get_node_value(node_nuc, "name", false, true);
51,416✔
179
      names.push_back(name);
51,416✔
180

181
      // Check if no atom/weight percents were specified or if both atom and
182
      // weight percents were specified
183
      if (units == "macro") {
51,416✔
184
        densities.push_back(density_);
×
185
      } else {
186
        bool has_ao = check_for_node(node_nuc, "ao");
51,416✔
187
        bool has_wo = check_for_node(node_nuc, "wo");
51,416✔
188

189
        if (!has_ao && !has_wo) {
51,416✔
190
          fatal_error(
×
191
            "No atom or weight percent specified for nuclide: " + name);
×
192
        } else if (has_ao && has_wo) {
51,416✔
193
          fatal_error("Cannot specify both atom and weight percents for a "
×
194
                      "nuclide: " +
×
195
                      name);
196
        }
197

198
        // Copy atom/weight percents
199
        if (has_ao) {
51,416✔
200
          densities.push_back(std::stod(get_node_value(node_nuc, "ao")));
42,395✔
201
        } else {
202
          densities.push_back(-std::stod(get_node_value(node_nuc, "wo")));
9,021✔
203
        }
204
      }
205
    }
51,416✔
206
  }
207

208
  // =======================================================================
209
  // READ AND PARSE <isotropic> element
210

211
  vector<std::string> iso_lab;
13,886✔
212
  if (check_for_node(node, "isotropic")) {
13,886✔
213
    iso_lab = get_node_array<std::string>(node, "isotropic");
204✔
214
  }
215

216
  // ========================================================================
217
  // COPY NUCLIDES TO ARRAYS IN MATERIAL
218

219
  // allocate arrays in Material object
220
  auto n = names.size();
13,886✔
221
  nuclide_.reserve(n);
13,886✔
222
  atom_density_ = xt::empty<double>({n});
13,886✔
223
  if (settings::photon_transport)
13,886✔
224
    element_.reserve(n);
278✔
225

226
  for (int i = 0; i < n; ++i) {
67,091✔
227
    const auto& name {names[i]};
53,205✔
228

229
    // Check that this nuclide is listed in the nuclear data library
230
    // (cross_sections.xml for CE and the MGXS HDF5 for MG)
231
    if (settings::run_mode != RunMode::PLOTTING) {
53,205✔
232
      LibraryKey key {Library::Type::neutron, name};
51,743✔
233
      if (data::library_map.find(key) == data::library_map.end()) {
51,743✔
234
        fatal_error("Could not find nuclide " + name +
×
235
                    " in the "
236
                    "nuclear data library.");
237
      }
238
    }
51,743✔
239

240
    // If this nuclide hasn't been encountered yet, we need to add its name
241
    // and alias to the nuclide_dict
242
    if (data::nuclide_map.find(name) == data::nuclide_map.end()) {
53,205✔
243
      int index = data::nuclide_map.size();
28,524✔
244
      data::nuclide_map[name] = index;
28,524✔
245
      nuclide_.push_back(index);
28,524✔
246
    } else {
247
      nuclide_.push_back(data::nuclide_map[name]);
24,681✔
248
    }
249

250
    // If the corresponding element hasn't been encountered yet and photon
251
    // transport will be used, we need to add its symbol to the element_dict
252
    if (settings::photon_transport) {
53,205✔
253
      std::string element = to_element(name);
1,389✔
254

255
      // Make sure photon cross section data is available
256
      if (settings::run_mode != RunMode::PLOTTING) {
1,389✔
257
        LibraryKey key {Library::Type::photon, element};
1,389✔
258
        if (data::library_map.find(key) == data::library_map.end()) {
1,389✔
259
          fatal_error(
×
260
            "Could not find element " + element + " in cross_sections.xml.");
×
261
        }
262
      }
1,389✔
263

264
      if (data::element_map.find(element) == data::element_map.end()) {
1,389✔
265
        int index = data::element_map.size();
659✔
266
        data::element_map[element] = index;
659✔
267
        element_.push_back(index);
659✔
268
      } else {
269
        element_.push_back(data::element_map[element]);
730✔
270
      }
271
    }
1,389✔
272

273
    // Copy atom/weight percent
274
    atom_density_(i) = densities[i];
53,205✔
275
  }
276

277
  if (settings::run_CE) {
13,886✔
278
    // By default, isotropic-in-lab is not used
279
    if (iso_lab.size() > 0) {
11,876✔
280
      p0_.resize(n);
204✔
281

282
      // Apply isotropic-in-lab treatment to specified nuclides
283
      for (int j = 0; j < n; ++j) {
1,921✔
284
        for (const auto& nuc : iso_lab) {
8,925✔
285
          if (names[j] == nuc) {
8,925✔
286
            p0_[j] = true;
1,717✔
287
            break;
1,717✔
288
          }
289
        }
290
      }
291
    }
292
  }
293

294
  // Check to make sure either all atom percents or all weight percents are
295
  // given
296
  if (!(xt::all(atom_density_ >= 0.0) || xt::all(atom_density_ <= 0.0))) {
13,886✔
297
    fatal_error(
×
298
      "Cannot mix atom and weight percents in material " + std::to_string(id_));
×
299
  }
300

301
  // Determine density if it is a sum value
302
  if (sum_density)
13,886✔
303
    density_ = xt::sum(atom_density_)();
2,591✔
304

305
  if (check_for_node(node, "temperature")) {
13,886✔
306
    temperature_ = std::stod(get_node_value(node, "temperature"));
1,980✔
307
  }
308

309
  if (check_for_node(node, "volume")) {
13,886✔
310
    volume_ = std::stod(get_node_value(node, "volume"));
2,312✔
311
  }
312

313
  // =======================================================================
314
  // READ AND PARSE <sab> TAG FOR THERMAL SCATTERING DATA
315
  if (settings::run_CE) {
13,886✔
316
    // Loop over <sab> elements
317

318
    vector<std::string> sab_names;
11,876✔
319
    for (auto node_sab : node.children("sab")) {
13,801✔
320
      // Determine name of thermal scattering table
321
      if (!check_for_node(node_sab, "name")) {
1,925✔
322
        fatal_error("Need to specify <name> for thermal scattering table.");
×
323
      }
324
      std::string name = get_node_value(node_sab, "name");
1,925✔
325
      sab_names.push_back(name);
1,925✔
326

327
      // Read the fraction of nuclei affected by this thermal scattering table
328
      double fraction = 1.0;
1,925✔
329
      if (check_for_node(node_sab, "fraction")) {
1,925✔
330
        fraction = std::stod(get_node_value(node_sab, "fraction"));
17✔
331
      }
332

333
      // Check that the thermal scattering table is listed in the
334
      // cross_sections.xml file
335
      if (settings::run_mode != RunMode::PLOTTING) {
1,925✔
336
        LibraryKey key {Library::Type::thermal, name};
1,831✔
337
        if (data::library_map.find(key) == data::library_map.end()) {
1,831✔
338
          fatal_error("Could not find thermal scattering data " + name +
×
339
                      " in cross_sections.xml file.");
340
        }
341
      }
1,831✔
342

343
      // Determine index of thermal scattering data in global
344
      // data::thermal_scatt array
345
      int index_table;
346
      if (data::thermal_scatt_map.find(name) == data::thermal_scatt_map.end()) {
1,925✔
347
        index_table = data::thermal_scatt_map.size();
1,167✔
348
        data::thermal_scatt_map[name] = index_table;
1,167✔
349
      } else {
350
        index_table = data::thermal_scatt_map[name];
758✔
351
      }
352

353
      // Add entry to thermal tables vector. For now, we put the nuclide index
354
      // as zero since we don't know which nuclides the table is being applied
355
      // to yet (this is assigned in init_thermal)
356
      thermal_tables_.push_back({index_table, 0, fraction});
1,925✔
357
    }
1,925✔
358
  }
11,876✔
359
}
13,886✔
360

361
Material::~Material()
13,934✔
362
{
363
  model::material_map.erase(id_);
13,934✔
364
}
13,934✔
365

366
Material& Material::clone()
×
367
{
368
  std::unique_ptr<Material> mat = std::make_unique<Material>();
×
369

370
  // set all other parameters to whatever the calling Material has
371
  mat->name_ = name_;
×
372
  mat->nuclide_ = nuclide_;
×
373
  mat->element_ = element_;
×
374
  mat->ncrystal_mat_ = ncrystal_mat_;
×
375
  mat->atom_density_ = atom_density_;
×
376
  mat->density_ = density_;
×
377
  mat->density_gpcc_ = density_gpcc_;
×
378
  mat->volume_ = volume_;
×
379
  mat->fissionable() = fissionable_;
×
380
  mat->depletable() = depletable_;
×
381
  mat->p0_ = p0_;
×
382
  mat->mat_nuclide_index_ = mat_nuclide_index_;
×
383
  mat->thermal_tables_ = thermal_tables_;
×
384
  mat->temperature_ = temperature_;
×
385

386
  if (ttb_)
×
387
    mat->ttb_ = std::make_unique<Bremsstrahlung>(*ttb_);
×
388

389
  mat->index_ = model::materials.size();
×
390
  mat->set_id(C_NONE);
×
391
  model::materials.push_back(std::move(mat));
×
392
  return *model::materials.back();
×
393
}
394

395
void Material::finalize()
13,414✔
396
{
397
  // Set fissionable if any nuclide is fissionable
398
  if (settings::run_CE) {
13,414✔
399
    for (const auto& i_nuc : nuclide_) {
43,428✔
400
      if (data::nuclides[i_nuc]->fissionable_) {
37,478✔
401
        fissionable_ = true;
5,454✔
402
        break;
5,454✔
403
      }
404
    }
405

406
    // Generate material bremsstrahlung data for electrons and positrons
407
    if (settings::photon_transport &&
11,404✔
408
        settings::electron_treatment == ElectronTreatment::TTB) {
278✔
409
      this->init_bremsstrahlung();
266✔
410
    }
411

412
    // Assign thermal scattering tables
413
    this->init_thermal();
11,404✔
414
  }
415

416
  // Normalize density
417
  this->normalize_density();
13,414✔
418
}
13,414✔
419

420
void Material::normalize_density()
13,414✔
421
{
422
  bool percent_in_atom = (atom_density_(0) >= 0.0);
13,414✔
423
  bool density_in_atom = (density_ >= 0.0);
13,414✔
424

425
  for (int i = 0; i < nuclide_.size(); ++i) {
65,160✔
426
    // determine atomic weight ratio
427
    int i_nuc = nuclide_[i];
51,746✔
428
    double awr = settings::run_CE ? data::nuclides[i_nuc]->awr_
54,164✔
429
                                  : data::mg.nuclides_[i_nuc].awr;
2,418✔
430

431
    // if given weight percent, convert all values so that they are divided
432
    // by awr. thus, when a sum is done over the values, it's actually
433
    // sum(w/awr)
434
    if (!percent_in_atom)
51,746✔
435
      atom_density_(i) = -atom_density_(i) / awr;
9,021✔
436
  }
437

438
  // determine normalized atom percents. if given atom percents, this is
439
  // straightforward. if given weight percents, the value is w/awr and is
440
  // divided by sum(w/awr)
441
  atom_density_ /= xt::sum(atom_density_)();
13,414✔
442

443
  // Change density in g/cm^3 to atom/b-cm. Since all values are now in
444
  // atom percent, the sum needs to be re-evaluated as 1/sum(x*awr)
445
  if (!density_in_atom) {
13,414✔
446
    double sum_percent = 0.0;
8,585✔
447
    for (int i = 0; i < nuclide_.size(); ++i) {
38,585✔
448
      int i_nuc = nuclide_[i];
30,000✔
449
      double awr = settings::run_CE ? data::nuclides[i_nuc]->awr_
30,085✔
450
                                    : data::mg.nuclides_[i_nuc].awr;
85✔
451
      sum_percent += atom_density_(i) * awr;
30,000✔
452
    }
453
    sum_percent = 1.0 / sum_percent;
8,585✔
454
    density_ = -density_ * N_AVOGADRO / MASS_NEUTRON * sum_percent;
8,585✔
455
  }
456

457
  // Calculate nuclide atom densities
458
  atom_density_ *= density_;
13,414✔
459

460
  // Calculate density in g/cm^3.
461
  density_gpcc_ = 0.0;
13,414✔
462
  for (int i = 0; i < nuclide_.size(); ++i) {
65,160✔
463
    int i_nuc = nuclide_[i];
51,746✔
464
    double awr = settings::run_CE ? data::nuclides[i_nuc]->awr_ : 1.0;
51,746✔
465
    density_gpcc_ += atom_density_(i) * awr * MASS_NEUTRON / N_AVOGADRO;
51,746✔
466
  }
467
}
13,414✔
468

469
void Material::init_thermal()
18,964✔
470
{
471
  vector<ThermalTable> tables;
18,964✔
472

473
  std::unordered_set<int> already_checked;
18,964✔
474
  for (const auto& table : thermal_tables_) {
20,808✔
475
    // Make sure each S(a,b) table only gets checked once
476
    if (already_checked.find(table.index_table) != already_checked.end()) {
1,844✔
477
      continue;
×
478
    }
479
    already_checked.insert(table.index_table);
1,844✔
480

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

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

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

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

524
  // Update the list of thermal tables
525
  thermal_tables_ = tables;
18,964✔
526
}
18,964✔
527

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

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

537
  // Effective number of conduction electrons in the material
538
  double n_conduction = 0.0;
532✔
539

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

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

549
    // Get atomic density of nuclide given atom/weight percent
550
    double atom_density =
551
      (atom_density_[0] > 0.0) ? atom_density_[i] : -atom_density_[i] / awr;
2,322✔
552

553
    electron_density += atom_density * elm.Z_;
2,322✔
554
    mass_density += atom_density * awr * MASS_NEUTRON;
2,322✔
555
    log_I += atom_density * elm.Z_ * std::log(elm.I_);
2,322✔
556

557
    for (int j = 0; j < elm.n_electrons_.size(); ++j) {
18,992✔
558
      if (elm.n_electrons_[j] < 0) {
16,670✔
559
        n_conduction -= elm.n_electrons_[j] * atom_density;
1,694✔
560
        continue;
1,694✔
561
      }
562
      e_b_sq.push_back(elm.ionization_energy_[j] * elm.ionization_energy_[j]);
14,976✔
563
      f.push_back(elm.n_electrons_[j] * atom_density);
14,976✔
564
    }
565
  }
566
  log_I /= electron_density;
532✔
567
  n_conduction /= electron_density;
532✔
568
  for (auto& f_i : f)
15,508✔
569
    f_i /= electron_density;
14,976✔
570

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

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

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

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

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

593
  // Loop over incident charged particle energies
594
  for (int i = 0; i < data::ttb_e_grid.size(); ++i) {
106,620✔
595
    double E = data::ttb_e_grid(i);
106,088✔
596

597
    // Get the density effect correction
598
    double delta =
599
      density_effect(f, e_b_sq, e_p_sq, n_conduction, rho, E, 1.0e-6, 100);
106,088✔
600

601
    // Square of the ratio of the speed of light to the velocity of the charged
602
    // particle
603
    double beta_sq = E * (E + 2.0 * MASS_ELECTRON_EV) /
106,088✔
604
                     ((E + MASS_ELECTRON_EV) * (E + MASS_ELECTRON_EV));
106,088✔
605

606
    double tau = E / MASS_ELECTRON_EV;
106,088✔
607

608
    double F;
609
    if (positron) {
106,088✔
610
      double t = tau + 2.0;
53,044✔
611
      F = std::log(4.0) - (beta_sq / 12.0) * (23.0 + 14.0 / t + 10.0 / (t * t) +
53,044✔
612
                                               4.0 / (t * t * t));
53,044✔
613
    } else {
614
      F = (1.0 - beta_sq) *
53,044✔
615
          (1.0 + tau * tau / 8.0 - (2.0 * tau + 1.0) * std::log(2.0));
53,044✔
616
    }
617

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

625
void Material::init_bremsstrahlung()
266✔
626
{
627
  // Create new object
628
  ttb_ = make_unique<Bremsstrahlung>();
266✔
629

630
  // Get the size of the energy grids
631
  auto n_k = data::ttb_k_grid.size();
266✔
632
  auto n_e = data::ttb_e_grid.size();
266✔
633

634
  // Determine number of elements
635
  int n = element_.size();
266✔
636

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

643
    // Allocate arrays for TTB data
644
    ttb->pdf = xt::zeros<double>({n_e, n_e});
532✔
645
    ttb->cdf = xt::zeros<double>({n_e, n_e});
532✔
646
    ttb->yield = xt::empty<double>({n_e});
532✔
647

648
    // Allocate temporary arrays
649
    xt::xtensor<double, 1> stopping_power_collision({n_e}, 0.0);
532✔
650
    xt::xtensor<double, 1> stopping_power_radiative({n_e}, 0.0);
532✔
651
    xt::xtensor<double, 2> dcs({n_e, n_k}, 0.0);
532✔
652

653
    double Z_eq_sq = 0.0;
532✔
654
    double sum_density = 0.0;
532✔
655

656
    // Get the collision stopping power of the material
657
    this->collision_stopping_power(stopping_power_collision.data(), positron);
532✔
658

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

666
      // Get atomic density and mass density of nuclide given atom/weight
667
      // percent
668
      double atom_density =
669
        (atom_density_[0] > 0.0) ? atom_density_[i] : -atom_density_[i] / awr;
2,322✔
670

671
      // Calculate the "equivalent" atomic number Zeq of the material
672
      Z_eq_sq += atom_density * elm.Z_ * elm.Z_;
2,322✔
673
      sum_density += atom_density;
2,322✔
674

675
      // Accumulate material DCS
676
      dcs += (atom_density * elm.Z_ * elm.Z_) * elm.dcs_;
2,322✔
677

678
      // Accumulate material radiative stopping power
679
      stopping_power_radiative += atom_density * elm.stopping_power_radiative_;
2,322✔
680
    }
681
    Z_eq_sq /= sum_density;
532✔
682

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

706
    // Total material stopping power
707
    xt::xtensor<double, 1> stopping_power =
708
      stopping_power_collision + stopping_power_radiative;
532✔
709

710
    // Loop over photon energies
711
    xt::xtensor<double, 1> f({n_e}, 0.0);
532✔
712
    xt::xtensor<double, 1> z({n_e}, 0.0);
532✔
713
    for (int i = 0; i < n_e - 1; ++i) {
106,088✔
714
      double w = data::ttb_e_grid(i);
105,556✔
715

716
      // Loop over incident particle energies
717
      for (int j = i; j < n_e; ++j) {
10,737,696✔
718
        double e = data::ttb_e_grid(j);
10,632,140✔
719

720
        // Reduced photon energy
721
        double k = w / e;
10,632,140✔
722

723
        // Find the lower bounding index of the reduced photon energy
724
        int i_k = lower_bound_index(
10,632,140✔
725
          data::ttb_k_grid.cbegin(), data::ttb_k_grid.cend(), k);
10,632,140✔
726

727
        // Get the interpolation bounds
728
        double k_l = data::ttb_k_grid(i_k);
10,632,140✔
729
        double k_r = data::ttb_k_grid(i_k + 1);
10,632,140✔
730
        double x_l = dcs(j, i_k);
10,632,140✔
731
        double x_r = dcs(j, i_k + 1);
10,632,140✔
732

733
        // Find the value of the DCS using linear interpolation in reduced
734
        // photon energy k
735
        double x = x_l + (k - k_l) * (x_r - x_l) / (k_r - k_l);
10,632,140✔
736

737
        // Square of the ratio of the speed of light to the velocity of the
738
        // charged particle
739
        double beta_sq = e * (e + 2.0 * MASS_ELECTRON_EV) /
10,632,140✔
740
                         ((e + MASS_ELECTRON_EV) * (e + MASS_ELECTRON_EV));
10,632,140✔
741

742
        // Compute the integrand of the PDF
743
        f(j) = x / (beta_sq * stopping_power(j) * w);
10,632,140✔
744
      }
745

746
      // Number of points to integrate
747
      int n = n_e - i;
105,556✔
748

749
      // Integrate the PDF using cubic spline integration over the incident
750
      // particle energy
751
      if (n > 2) {
105,556✔
752
        spline(n, &data::ttb_e_grid(i), &f(i), &z(i));
105,024✔
753

754
        double c = 0.0;
105,024✔
755
        for (int j = i; j < n_e - 1; ++j) {
10,631,076✔
756
          c += spline_integrate(n, &data::ttb_e_grid(i), &f(i), &z(i),
10,526,052✔
757
            data::ttb_e_grid(j), data::ttb_e_grid(j + 1));
10,526,052✔
758

759
          ttb->pdf(j + 1, i) = c;
10,526,052✔
760
        }
761

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

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

774
    // Loop over incident particle energies
775
    for (int j = 1; j < n_e; ++j) {
106,088✔
776
      // Set last element of PDF to small non-zero value to enable log-log
777
      // interpolation
778
      ttb->pdf(j, j) = std::exp(-500.0);
105,556✔
779

780
      // Loop over photon energies
781
      double c = 0.0;
105,556✔
782
      for (int i = 0; i < j; ++i) {
10,632,140✔
783
        // Integrate the CDF from the PDF using the trapezoidal rule in log-log
784
        // space
785
        double w_l = std::log(data::ttb_e_grid(i));
10,526,584✔
786
        double w_r = std::log(data::ttb_e_grid(i + 1));
10,526,584✔
787
        double x_l = std::log(ttb->pdf(j, i));
10,526,584✔
788
        double x_r = std::log(ttb->pdf(j, i + 1));
10,526,584✔
789

790
        c += 0.5 * (w_r - w_l) * (std::exp(w_l + x_l) + std::exp(w_r + x_r));
10,526,584✔
791
        ttb->cdf(j, i + 1) = c;
10,526,584✔
792
      }
793

794
      // Set photon number yield
795
      ttb->yield(j) = c;
105,556✔
796
    }
797

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

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

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

821
  if (p.type() == ParticleType::neutron) {
1,351,591,083✔
822
    this->calculate_neutron_xs(p);
1,283,175,580✔
823
  } else if (p.type() == ParticleType::photon) {
68,415,503✔
824
    this->calculate_photon_xs(p);
15,152,362✔
825
  }
826
}
1,351,591,083✔
827

828
void Material::calculate_neutron_xs(Particle& p) const
1,283,175,580✔
829
{
830
  // Find energy index on energy grid
831
  int neutron = static_cast<int>(ParticleType::neutron);
1,283,175,580✔
832
  int i_grid =
833
    std::log(p.E() / data::energy_min[neutron]) / simulation::log_spacing;
1,283,175,580✔
834

835
  // Determine if this material has S(a,b) tables
836
  bool check_sab = (thermal_tables_.size() > 0);
1,283,175,580✔
837

838
  // Initialize position in i_sab_nuclides
839
  int j = 0;
1,283,175,580✔
840

841
  // Calculate NCrystal cross section
842
  double ncrystal_xs = -1.0;
1,283,175,580✔
843
  if (ncrystal_mat_ && p.E() < NCRYSTAL_MAX_ENERGY) {
1,283,175,580✔
844
    ncrystal_xs = ncrystal_mat_.xs(p);
1,014,439✔
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]};
779,940,470✔
859
      if (i == sab.index_nuclide) {
779,940,470✔
860
        // Get index in sab_tables
861
        i_sab = sab.index_table;
312,742,706✔
862
        sab_frac = sab.fraction;
312,742,706✔
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_)
312,742,706✔
867
          i_sab = C_NONE;
205,419,491✔
868

869
        // Increment position in thermal_tables_
870
        ++j;
312,742,706✔
871

872
        // Don't check for S(a,b) tables if there are no more left
873
        if (j == thermal_tables_.size())
312,742,706✔
874
          check_sab = false;
312,486,758✔
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 = atom_density_(i);
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
}
1,283,175,580✔
901

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

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

914
    // Determine microscopic cross sections for this nuclide
915
    int i_element = element_[i];
82,600,784✔
916

917
    // Calculate microscopic cross section for this nuclide
918
    const auto& micro {p.photon_xs(i_element)};
82,600,784✔
919
    if (p.E() != micro.last_E) {
82,600,784✔
920
      data::elements[i_element]->calculate_xs(p);
38,161,198✔
921
    }
922

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

926
    // Copy atom density of nuclide in material
927
    double atom_density = atom_density_(i);
82,600,784✔
928

929
    // Add contributions to material macroscopic cross sections
930
    p.macro_xs().total += atom_density * micro.total;
82,600,784✔
931
    p.macro_xs().coherent += atom_density * micro.coherent;
82,600,784✔
932
    p.macro_xs().incoherent += atom_density * micro.incoherent;
82,600,784✔
933
    p.macro_xs().photoelectric += atom_density * micro.photoelectric;
82,600,784✔
934
    p.macro_xs().pair_production += atom_density * micro.pair_production;
82,600,784✔
935
  }
936
}
15,152,362✔
937

938
void Material::set_id(int32_t id)
13,934✔
939
{
940
  assert(id >= 0 || id == C_NONE);
11,534✔
941

942
  // Clear entry in material map if an ID was already assigned before
943
  if (id_ != C_NONE) {
13,934✔
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()) {
13,934✔
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) {
13,934✔
956
    id = 0;
×
957
    for (const auto& m : model::materials) {
×
958
      id = std::max(id, m->id_);
×
959
    }
960
    ++id;
×
961
  }
962

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

968
void Material::set_density(double density, const std::string& units)
7,740✔
969
{
970
  assert(density >= 0.0);
6,450✔
971

972
  if (nuclide_.empty()) {
7,740✔
973
    throw std::runtime_error {"No nuclides exist in material yet."};
×
974
  }
975

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

980
    // Determine normalized atom percents
981
    double sum_percent = xt::sum(atom_density_)();
7,692✔
982
    atom_density_ /= sum_percent;
7,692✔
983

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

987
    // Calculate density in g/cm^3.
988
    density_gpcc_ = 0.0;
7,692✔
989
    for (int i = 0; i < nuclide_.size(); ++i) {
81,840✔
990
      int i_nuc = nuclide_[i];
74,148✔
991
      double awr = data::nuclides[i_nuc]->awr_;
74,148✔
992
      density_gpcc_ += atom_density_(i) * awr * MASS_NEUTRON / N_AVOGADRO;
74,148✔
993
    }
994
  } else if (units == "g/cm3" || units == "g/cc") {
48✔
995
    // Determine factor by which to change densities
996
    double previous_density_gpcc = density_gpcc_;
36✔
997
    double f = density / previous_density_gpcc;
36✔
998

999
    // Update densities
1000
    density_gpcc_ = density;
36✔
1001
    density_ *= f;
36✔
1002
    atom_density_ *= f;
36✔
1003
  } else {
1004
    throw std::invalid_argument {
12✔
1005
      "Invalid units '" + std::string(units.data()) + "' specified."};
24✔
1006
  }
1007
}
7,728✔
1008

1009
void Material::set_densities(
7,560✔
1010
  const vector<std::string>& name, const vector<double>& density)
1011
{
1012
  auto n = name.size();
7,560✔
1013
  assert(n > 0);
6,300✔
1014
  assert(n == density.size());
6,300✔
1015

1016
  if (n != nuclide_.size()) {
7,560✔
1017
    nuclide_.resize(n);
1,812✔
1018
    atom_density_ = xt::zeros<double>({n});
1,812✔
1019
    if (settings::photon_transport)
1,812✔
1020
      element_.resize(n);
×
1021
  }
1022

1023
  double sum_density = 0.0;
7,560✔
1024
  for (int64_t i = 0; i < n; ++i) {
81,120✔
1025
    const auto& nuc {name[i]};
73,560✔
1026
    if (data::nuclide_map.find(nuc) == data::nuclide_map.end()) {
73,560✔
1027
      int err = openmc_load_nuclide(nuc.c_str(), nullptr, 0);
×
1028
      if (err < 0)
×
1029
        throw std::runtime_error {openmc_err_msg};
×
1030
    }
1031

1032
    nuclide_[i] = data::nuclide_map.at(nuc);
73,560✔
1033
    assert(density[i] > 0.0);
61,300✔
1034
    atom_density_(i) = density[i];
73,560✔
1035
    sum_density += density[i];
73,560✔
1036

1037
    if (settings::photon_transport) {
73,560✔
1038
      auto element_name = to_element(nuc);
×
1039
      element_[i] = data::element_map.at(element_name);
×
1040
    }
1041
  }
1042

1043
  // Set total density to the sum of the vector
1044
  this->set_density(sum_density, "atom/b-cm");
7,560✔
1045

1046
  // Generate material bremsstrahlung data for electrons and positrons
1047
  if (settings::photon_transport &&
7,560✔
1048
      settings::electron_treatment == ElectronTreatment::TTB) {
×
1049
    this->init_bremsstrahlung();
×
1050
  }
1051

1052
  // Assign S(a,b) tables
1053
  this->init_thermal();
7,560✔
1054
}
7,560✔
1055

1056
double Material::volume() const
60✔
1057
{
1058
  if (volume_ < 0.0) {
60✔
1059
    throw std::runtime_error {
12✔
1060
      "Volume for material with ID=" + std::to_string(id_) + " not set."};
24✔
1061
  }
1062
  return volume_;
48✔
1063
}
1064

1065
double Material::temperature() const
17,817✔
1066
{
1067
  // If material doesn't have an assigned temperature, use global default
1068
  return temperature_ >= 0 ? temperature_ : settings::temperature_default;
17,817✔
1069
}
1070

1071
void Material::to_hdf5(hid_t group) const
11,009✔
1072
{
1073
  hid_t material_group = create_group(group, "material " + std::to_string(id_));
11,009✔
1074

1075
  write_attribute(material_group, "depletable", static_cast<int>(depletable()));
11,009✔
1076
  if (volume_ > 0.0) {
11,009✔
1077
    write_attribute(material_group, "volume", volume_);
2,300✔
1078
  }
1079
  if (temperature_ > 0.0) {
11,009✔
1080
    write_attribute(material_group, "temperature", temperature_);
1,970✔
1081
  }
1082
  write_dataset(material_group, "name", name_);
11,009✔
1083
  write_dataset(material_group, "atom_density", density_);
11,009✔
1084

1085
  // Copy nuclide/macro name for each nuclide to vector
1086
  vector<std::string> nuc_names;
11,009✔
1087
  vector<std::string> macro_names;
11,009✔
1088
  vector<double> nuc_densities;
11,009✔
1089
  if (settings::run_CE) {
11,009✔
1090
    for (int i = 0; i < nuclide_.size(); ++i) {
52,975✔
1091
      int i_nuc = nuclide_[i];
43,094✔
1092
      nuc_names.push_back(data::nuclides[i_nuc]->name_);
43,094✔
1093
      nuc_densities.push_back(atom_density_(i));
43,094✔
1094
    }
1095
  } else {
1096
    for (int i = 0; i < nuclide_.size(); ++i) {
2,292✔
1097
      int i_nuc = nuclide_[i];
1,164✔
1098
      if (data::mg.nuclides_[i_nuc].awr != MACROSCOPIC_AWR) {
1,164✔
1099
        nuc_names.push_back(data::mg.nuclides_[i_nuc].name);
96✔
1100
        nuc_densities.push_back(atom_density_(i));
96✔
1101
      } else {
1102
        macro_names.push_back(data::mg.nuclides_[i_nuc].name);
1,068✔
1103
      }
1104
    }
1105
  }
1106

1107
  // Write vector to 'nuclides'
1108
  if (!nuc_names.empty()) {
11,009✔
1109
    write_dataset(material_group, "nuclides", nuc_names);
9,941✔
1110
    write_dataset(material_group, "nuclide_densities", nuc_densities);
9,941✔
1111
  }
1112

1113
  // Write vector to 'macroscopics'
1114
  if (!macro_names.empty()) {
11,009✔
1115
    write_dataset(material_group, "macroscopics", macro_names);
1,068✔
1116
  }
1117

1118
  if (!thermal_tables_.empty()) {
11,009✔
1119
    vector<std::string> sab_names;
1,397✔
1120
    for (const auto& table : thermal_tables_) {
2,866✔
1121
      sab_names.push_back(data::thermal_scatt[table.index_table]->name_);
1,469✔
1122
    }
1123
    write_dataset(material_group, "sab_names", sab_names);
1,397✔
1124
  }
1,397✔
1125

1126
  close_group(material_group);
11,009✔
1127
}
11,009✔
1128

1129
void Material::export_properties_hdf5(hid_t group) const
108✔
1130
{
1131
  hid_t material_group = create_group(group, "material " + std::to_string(id_));
108✔
1132
  write_attribute(material_group, "atom_density", density_);
108✔
1133
  write_attribute(material_group, "mass_density", density_gpcc_);
108✔
1134
  close_group(material_group);
108✔
1135
}
108✔
1136

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

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

1161
  // If nuclide wasn't found, extend nuclide/density arrays
1162
  int err = openmc_load_nuclide(name.c_str(), nullptr, 0);
12✔
1163
  if (err < 0)
12✔
1164
    throw std::runtime_error {openmc_err_msg};
×
1165

1166
  // Append new nuclide/density
1167
  int i_nuc = data::nuclide_map[name];
12✔
1168
  nuclide_.push_back(i_nuc);
12✔
1169

1170
  // Append new element if photon transport is on
1171
  if (settings::photon_transport) {
12✔
1172
    int i_elem = data::element_map[to_element(name)];
×
1173
    element_.push_back(i_elem);
×
1174
  }
1175

1176
  auto n = nuclide_.size();
12✔
1177

1178
  // Create copy of atom_density_ array with one extra entry
1179
  xt::xtensor<double, 1> atom_density = xt::zeros<double>({n});
12✔
1180
  xt::view(atom_density, xt::range(0, n - 1)) = atom_density_;
12✔
1181
  atom_density(n - 1) = density;
12✔
1182
  atom_density_ = atom_density;
12✔
1183

1184
  density_ += density;
12✔
1185
  density_gpcc_ +=
12✔
1186
    density * data::nuclides[i_nuc]->awr_ * MASS_NEUTRON / N_AVOGADRO;
12✔
1187
}
12✔
1188
//==============================================================================
1189
// Stochastic_Media implementation
1190
//==============================================================================
1191

NEW
1192
Stochastic_Media::~Stochastic_Media()
×
1193
{
NEW
1194
  model::stochastic_media_map.erase(id_);
×
1195
}
NEW
1196

×
1197
Stochastic_Media::Stochastic_Media(pugi::xml_node node)
1198
{
1199
  if (check_for_node(node, "id")) {
NEW
1200
    this->set_id(std::stoi(get_node_value(node, "id")));
×
1201
  } else {
NEW
1202
    fatal_error("Must specify id of material in materials XML file.");
×
1203
  }
1204

NEW
1205
  if (check_for_node(node, "name")) {
×
1206
    name_ = get_node_value(node, "name");
NEW
1207
  }
×
NEW
1208

×
1209
  bool particle_mat_present = check_for_node(node, "particle_material");
NEW
1210
  bool matrix_mat_present = check_for_node(node, "matrix_material");
×
1211

1212
  // Read the material element.  There can be zero materials (filled with a
NEW
1213
  // universe), more than one material (distribmats), and some materials may
×
NEW
1214
  // be "void".
×
1215
  if (particle_mat_present) {
1216
    vector<std::string> mats {
NEW
1217
      get_node_array<std::string>(node, "particle_material", true)};
×
NEW
1218
    if (mats.size() > 0) {
×
1219
      particle_mat_.reserve(mats.size());
1220
      for (std::string mat : mats) {
1221
        if (mat.compare("void") == 0) {
1222
          particle_mat_.push_back(MATERIAL_VOID);
NEW
1223
        } else {
×
1224
          particle_mat_.push_back(std::stoi(mat));
NEW
1225
        }
×
NEW
1226
      }
×
NEW
1227
    }
×
NEW
1228
  } else {
×
NEW
1229
    fatal_error(fmt::format(
×
NEW
1230
      "An empty material element was specified for stochastic media {}", id_));
×
1231
  }
NEW
1232

×
1233
  if (matrix_mat_present) {
1234
    vector<std::string> mats {
1235
      get_node_array<std::string>(node, "matrix_material", true)};
NEW
1236
    if (mats.size() > 0) {
×
NEW
1237
      matrix_mat_.reserve(mats.size());
×
NEW
1238
      for (std::string mat : mats) {
×
1239
        if (mat.compare("void") == 0) {
1240
          matrix_mat_.push_back(MATERIAL_VOID);
NEW
1241
        } else {
×
1242
          matrix_mat_.push_back(std::stoi(mat));
NEW
1243
        }
×
NEW
1244
      }
×
NEW
1245
    }
×
NEW
1246
  } else {
×
NEW
1247
    fatal_error(fmt::format(
×
NEW
1248
      "An empty material element was specified for stochastic media {}", id_));
×
1249
  }
NEW
1250

×
1251
  if (check_for_node(node, "radius")) {
1252
    radius_ = std::stoi(get_node_value(node, "radius"));
1253
  } else {
NEW
1254
    fatal_error(fmt::format(
×
NEW
1255
      "An empty particle radius was specified for stochastic media {}", id_));
×
NEW
1256
  }
×
1257

1258
  if (check_for_node(node, "pack_fraction")) {
NEW
1259
    pf_ = std::stoi(get_node_value(node, "pack_fraction"));
×
NEW
1260
  } else {
×
1261
    fatal_error(fmt::format(
NEW
1262
      "An empty pack fraction was specified for stochastic media {}", id_));
×
NEW
1263
  }
×
1264
}
1265

NEW
1266
void Stochastic_Media::set_id(int32_t id)
×
NEW
1267
{
×
1268
  assert(id >= 0 || id == C_NONE);
NEW
1269

×
NEW
1270
  // Clear entry in material map if an ID was already assigned before
×
1271
  if (id_ != C_NONE) {
1272
    model::material_map.erase(id_);
1273
    id_ = C_NONE;
NEW
1274
  }
×
1275

1276
  // Make sure no other material has same ID
1277
  if (model::material_map.find(id) != model::material_map.end()) {
1278
    throw std::runtime_error {
NEW
1279
      "Two materials have the same ID: " + std::to_string(id)};
×
NEW
1280
  }
×
NEW
1281

×
1282
  // If no ID specified, auto-assign next ID in sequence
1283
  if (id == C_NONE) {
1284
    id = 0;
NEW
1285
    for (const auto& m : model::materials) {
×
NEW
1286
      id = std::max(id, m->id_);
×
NEW
1287
    }
×
1288
    ++id;
1289
  }
1290

NEW
1291
  // Update ID and entry in material map
×
NEW
1292
  id_ = id;
×
NEW
1293
  model::material_map[id] = index_;
×
NEW
1294
}
×
1295

UNCOV
1296
//==============================================================================
×
1297
// Non-method functions
1298
//==============================================================================
1299

UNCOV
1300
double sternheimer_adjustment(const vector<double>& f,
×
UNCOV
1301
  const vector<double>& e_b_sq, double e_p_sq, double n_conduction,
×
1302
  double log_I, double tol, int max_iter)
1303
{
1304
  // Get the total number of oscillators
1305
  int n = f.size();
1306

1307
  // Calculate the Sternheimer adjustment factor using Newton's method
1308
  double rho = 2.0;
532✔
1309
  int iter;
1310
  for (iter = 0; iter < max_iter; ++iter) {
1311
    double rho_0 = rho;
1312

1313
    // Function to find the root of and its derivative
532✔
1314
    double g = 0.0;
1315
    double gp = 0.0;
1316

532✔
1317
    for (int i = 0; i < n; ++i) {
1318
      // Square of resonance energy of a bound-shell oscillator
2,138✔
1319
      double e_r_sq = e_b_sq[i] * rho * rho + 2.0 / 3.0 * f[i] * e_p_sq;
2,138✔
1320
      g += f[i] * std::log(e_r_sq);
1321
      gp += e_b_sq[i] * f[i] * rho / e_r_sq;
1322
    }
2,138✔
1323
    // Include conduction electrons
2,138✔
1324
    if (n_conduction > 0.0) {
1325
      g += n_conduction * std::log(n_conduction * e_p_sq);
62,748✔
1326
    }
1327

60,610✔
1328
    // Set the next guess: rho_n+1 = rho_n - g(rho_n)/g'(rho_n)
60,610✔
1329
    rho -= (g - 2.0 * log_I) / (2.0 * gp);
60,610✔
1330

1331
    // If the initial guess is too large, rho can be negative
1332
    if (rho < 0.0)
2,138✔
1333
      rho = rho_0 / 2.0;
1,834✔
1334

1335
    // Check for convergence
1336
    if (std::abs(rho - rho_0) / rho_0 < tol)
1337
      break;
2,138✔
1338
  }
1339
  // Did not converge
1340
  if (iter >= max_iter) {
2,138✔
1341
    warning("Maximum Newton-Raphson iterations exceeded.");
×
1342
    rho = 1.0e-6;
1343
  }
1344
  return rho;
2,138✔
1345
}
532✔
1346

1347
double density_effect(const vector<double>& f, const vector<double>& e_b_sq,
1348
  double e_p_sq, double n_conduction, double rho, double E, double tol,
532✔
UNCOV
1349
  int max_iter)
×
UNCOV
1350
{
×
1351
  // Get the total number of oscillators
1352
  int n = f.size();
532✔
1353

1354
  // Square of the ratio of the speed of light to the velocity of the charged
1355
  // particle
106,088✔
1356
  double beta_sq = E * (E + 2.0 * MASS_ELECTRON_EV) /
1357
                   ((E + MASS_ELECTRON_EV) * (E + MASS_ELECTRON_EV));
1358

1359
  // For nonmetals, delta = 0 for beta < beta_0, where beta_0 is obtained by
1360
  // setting the frequency w = 0.
106,088✔
1361
  double beta_0_sq = 0.0;
1362
  if (n_conduction == 0.0) {
1363
    for (int i = 0; i < n; ++i) {
1364
      beta_0_sq += f[i] * e_p_sq / (e_b_sq[i] * rho * rho);
106,088✔
1365
    }
106,088✔
1366
    beta_0_sq = 1.0 / (1.0 + beta_0_sq);
1367
  }
1368
  double delta = 0.0;
1369
  if (beta_sq < beta_0_sq)
106,088✔
1370
    return delta;
106,088✔
1371

118,800✔
1372
  // Compute the square of the frequency w^2 using Newton's method, with the
102,400✔
1373
  // initial guess of w^2 equal to beta^2 * gamma^2
1374
  double w_sq = E / MASS_ELECTRON_EV * (E / MASS_ELECTRON_EV + 2);
16,400✔
1375
  int iter;
1376
  for (iter = 0; iter < max_iter; ++iter) {
106,088✔
1377
    double w_sq_0 = w_sq;
106,088✔
1378

8,946✔
1379
    // Function to find the root of and its derivative
1380
    double g = 0.0;
1381
    double gp = 0.0;
1382

97,142✔
1383
    for (int i = 0; i < n; ++i) {
1384
      double c = e_b_sq[i] * rho * rho / e_p_sq + w_sq;
669,490✔
1385
      g += f[i] / c;
669,490✔
1386
      gp -= f[i] / (c * c);
1387
    }
1388
    // Include conduction electrons
669,490✔
1389
    g += n_conduction / w_sq;
669,490✔
1390
    gp -= n_conduction / (w_sq * w_sq);
1391

22,357,296✔
1392
    // Set the next guess: w_n+1 = w_n - g(w_n)/g'(w_n)
21,687,806✔
1393
    w_sq -= (g + 1.0 - 1.0 / beta_sq) / gp;
21,687,806✔
1394

21,687,806✔
1395
    // If the initial guess is too large, w can be negative
1396
    if (w_sq < 0.0)
1397
      w_sq = w_sq_0 / 2.0;
669,490✔
1398

669,490✔
1399
    // Check for convergence
1400
    if (std::abs(w_sq - w_sq_0) / w_sq_0 < tol)
1401
      break;
669,490✔
1402
  }
1403
  // Did not converge
1404
  if (iter >= max_iter) {
669,490✔
1405
    warning("Maximum Newton-Raphson iterations exceeded: setting density "
148,844✔
1406
            "effect correction to zero.");
1407
    return delta;
1408
  }
669,490✔
1409

97,142✔
1410
  // Solve for the density effect correction
1411
  for (int i = 0; i < n; ++i) {
1412
    double l_sq = e_b_sq[i] * rho * rho / e_p_sq + 2.0 / 3.0 * f[i];
97,142✔
UNCOV
1413
    delta += f[i] * std::log((l_sq + w_sq) / l_sq);
×
1414
  }
UNCOV
1415
  // Include conduction electrons
×
1416
  if (n_conduction > 0.0) {
1417
    delta += n_conduction * std::log((n_conduction + w_sq) / n_conduction);
1418
  }
1419

3,033,014✔
1420
  return delta - w_sq * (1.0 - beta_sq);
2,935,872✔
1421
}
2,935,872✔
1422

1423
void read_materials_xml()
1424
{
97,142✔
1425
  write_message("Reading materials XML file...", 5);
89,688✔
1426

1427
  pugi::xml_document doc;
1428

97,142✔
1429
  // Check if materials.xml exists
1430
  std::string filename = settings::path_input + "materials.xml";
1431
  if (!file_exists(filename)) {
1,411✔
1432
    fatal_error("Material XML file '" + filename + "' does not exist!");
1433
  }
1,411✔
1434

1435
  // Parse materials.xml file and get root element
1,411✔
1436
  doc.load_file(filename.c_str());
1437

1438
  // Loop over XML material elements and populate the array.
1,411✔
1439
  pugi::xml_node root = doc.document_element();
1,411✔
UNCOV
1440

×
1441
  read_materials_xml(root);
1442
}
1443

1444
void read_materials_xml(pugi::xml_node root)
1,411✔
1445
{
1446
  for (pugi::xml_node material_node : root.children("material")) {
1447
    model::materials.push_back(make_unique<Material>(material_node));
1,411✔
1448
  }
1449
  model::materials.shrink_to_fit();
1,411✔
1450
}
1,411✔
1451

1452
void free_memory_material()
6,708✔
1453
{
1454
  model::materials.clear();
20,586✔
1455
  model::material_map.clear();
13,878✔
1456
  model::stochastic_media.clear();
1457
  model::stochastic_media_map.clear();
6,708✔
1458
}
6,708✔
1459

1460
//==============================================================================
6,841✔
1461
// C API
1462
//==============================================================================
6,841✔
1463

6,841✔
1464
extern "C" int openmc_get_material_index(int32_t id, int32_t* index)
6,841✔
1465
{
6,841✔
1466
  auto it = model::material_map.find(id);
6,841✔
1467
  if (it == model::material_map.end()) {
1468
    set_errmsg("No material exists with ID=" + std::to_string(id) + ".");
1469
    return OPENMC_E_INVALID_ID;
1470
  } else {
1471
    *index = it->second;
1472
    return 0;
9,938✔
1473
  }
1474
}
9,938✔
1475

9,938✔
1476
extern "C" int openmc_material_add_nuclide(
12✔
1477
  int32_t index, const char* name, double density)
12✔
1478
{
1479
  int err = 0;
9,926✔
1480
  if (index >= 0 && index < model::materials.size()) {
9,926✔
1481
    try {
1482
      model::materials[index]->add_nuclide(name, density);
1483
    } catch (const std::runtime_error& e) {
1484
      return OPENMC_E_DATA;
12✔
1485
    }
1486
  } else {
1487
    set_errmsg("Index in materials array is out of bounds.");
12✔
1488
    return OPENMC_E_OUT_OF_BOUNDS;
12✔
1489
  }
1490
  return err;
12✔
1491
}
×
1492

×
UNCOV
1493
extern "C" int openmc_material_get_densities(
×
1494
  int32_t index, const int** nuclides, const double** densities, int* n)
1495
{
×
UNCOV
1496
  if (index >= 0 && index < model::materials.size()) {
×
1497
    auto& mat = model::materials[index];
1498
    if (!mat->nuclides().empty()) {
12✔
1499
      *nuclides = mat->nuclides().data();
1500
      *densities = mat->densities().data();
1501
      *n = mat->nuclides().size();
180✔
1502
      return 0;
1503
    } else {
1504
      set_errmsg("Material atom density array has not been allocated.");
180✔
1505
      return OPENMC_E_ALLOCATE;
180✔
1506
    }
180✔
1507
  } else {
180✔
1508
    set_errmsg("Index in materials array is out of bounds.");
180✔
1509
    return OPENMC_E_OUT_OF_BOUNDS;
180✔
1510
  }
180✔
1511
}
1512

×
UNCOV
1513
extern "C" int openmc_material_get_density(int32_t index, double* density)
×
1514
{
1515
  if (index >= 0 && index < model::materials.size()) {
1516
    auto& mat = model::materials[index];
×
UNCOV
1517
    *density = mat->density_gpcc();
×
1518
    return 0;
1519
  } else {
1520
    set_errmsg("Index in materials array is out of bounds.");
1521
    return OPENMC_E_OUT_OF_BOUNDS;
36✔
1522
  }
1523
}
36✔
1524

36✔
1525
extern "C" int openmc_material_get_fissionable(int32_t index, bool* fissionable)
36✔
1526
{
36✔
1527
  if (index >= 0 && index < model::materials.size()) {
1528
    *fissionable = model::materials[index]->fissionable();
×
UNCOV
1529
    return 0;
×
1530
  } else {
1531
    set_errmsg("Index in materials array is out of bounds.");
1532
    return OPENMC_E_OUT_OF_BOUNDS;
UNCOV
1533
  }
×
1534
}
1535

×
1536
extern "C" int openmc_material_get_id(int32_t index, int32_t* id)
×
UNCOV
1537
{
×
1538
  if (index >= 0 && index < model::materials.size()) {
1539
    *id = model::materials[index]->id();
×
UNCOV
1540
    return 0;
×
1541
  } else {
1542
    set_errmsg("Index in materials array is out of bounds.");
1543
    return OPENMC_E_OUT_OF_BOUNDS;
1544
  }
11,102✔
1545
}
1546

11,102✔
1547
extern "C" int openmc_material_get_temperature(
11,102✔
1548
  int32_t index, double* temperature)
11,102✔
1549
{
1550
  if (index < 0 || index >= model::materials.size()) {
×
UNCOV
1551
    set_errmsg("Index in materials array is out of bounds.");
×
1552
    return OPENMC_E_OUT_OF_BOUNDS;
1553
  }
1554
  *temperature = model::materials[index]->temperature();
1555
  return 0;
72✔
1556
}
1557

1558
extern "C" int openmc_material_get_volume(int32_t index, double* volume)
72✔
1559
{
×
UNCOV
1560
  if (index >= 0 && index < model::materials.size()) {
×
1561
    try {
1562
      *volume = model::materials[index]->volume();
72✔
1563
    } catch (const std::exception& e) {
72✔
1564
      set_errmsg(e.what());
1565
      return OPENMC_E_UNASSIGNED;
1566
    }
60✔
1567
    return 0;
1568
  } else {
60✔
1569
    set_errmsg("Index in materials array is out of bounds.");
1570
    return OPENMC_E_OUT_OF_BOUNDS;
60✔
1571
  }
12✔
1572
}
12✔
1573

12✔
1574
extern "C" int openmc_material_set_density(
12✔
1575
  int32_t index, double density, const char* units)
48✔
1576
{
1577
  if (index >= 0 && index < model::materials.size()) {
×
UNCOV
1578
    try {
×
1579
      model::materials[index]->set_density(density, units);
1580
    } catch (const std::exception& e) {
1581
      set_errmsg(e.what());
1582
      return OPENMC_E_UNASSIGNED;
108✔
1583
    }
1584
  } else {
1585
    set_errmsg("Index in materials array is out of bounds.");
108✔
1586
    return OPENMC_E_OUT_OF_BOUNDS;
1587
  }
132✔
1588
  return 0;
12✔
1589
}
12✔
1590

12✔
1591
extern "C" int openmc_material_set_densities(
12✔
1592
  int32_t index, int n, const char** name, const double* density)
1593
{
×
UNCOV
1594
  if (index >= 0 && index < model::materials.size()) {
×
1595
    try {
1596
      model::materials[index]->set_densities(
96✔
1597
        {name, name + n}, {density, density + n});
1598
    } catch (const std::exception& e) {
1599
      set_errmsg(e.what());
7,560✔
1600
      return OPENMC_E_UNASSIGNED;
1601
    }
1602
  } else {
7,560✔
1603
    set_errmsg("Index in materials array is out of bounds.");
1604
    return OPENMC_E_OUT_OF_BOUNDS;
22,680✔
1605
  }
15,120✔
1606
  return 0;
×
1607
}
×
1608

×
UNCOV
1609
extern "C" int openmc_material_set_id(int32_t index, int32_t id)
×
1610
{
1611
  if (index >= 0 && index < model::materials.size()) {
×
UNCOV
1612
    try {
×
1613
      model::materials.at(index)->set_id(id);
1614
    } catch (const std::exception& e) {
7,560✔
1615
      set_errmsg(e.what());
1616
      return OPENMC_E_UNASSIGNED;
1617
    }
48✔
1618
  } else {
1619
    set_errmsg("Index in materials array is out of bounds.");
48✔
1620
    return OPENMC_E_OUT_OF_BOUNDS;
1621
  }
48✔
1622
  return 0;
×
1623
}
×
1624

×
UNCOV
1625
extern "C" int openmc_material_get_name(int32_t index, const char** name)
×
1626
{
1627
  if (index < 0 || index >= model::materials.size()) {
×
UNCOV
1628
    set_errmsg("Index in materials array is out of bounds.");
×
1629
    return OPENMC_E_OUT_OF_BOUNDS;
1630
  }
48✔
1631

1632
  *name = model::materials[index]->name().data();
1633

36✔
1634
  return 0;
1635
}
36✔
1636

×
UNCOV
1637
extern "C" int openmc_material_set_name(int32_t index, const char* name)
×
1638
{
1639
  if (index < 0 || index >= model::materials.size()) {
1640
    set_errmsg("Index in materials array is out of bounds.");
36✔
1641
    return OPENMC_E_OUT_OF_BOUNDS;
1642
  }
36✔
1643

1644
  model::materials[index]->set_name(name);
1645

12✔
1646
  return 0;
1647
}
12✔
1648

×
UNCOV
1649
extern "C" int openmc_material_set_volume(int32_t index, double volume)
×
1650
{
1651
  if (index >= 0 && index < model::materials.size()) {
1652
    auto& m {model::materials[index]};
12✔
1653
    if (volume >= 0.0) {
1654
      m->volume_ = volume;
12✔
1655
      return 0;
1656
    } else {
1657
      set_errmsg("Volume must be non-negative");
50✔
1658
      return OPENMC_E_INVALID_ARGUMENT;
1659
    }
50✔
1660
  } else {
50✔
1661
    set_errmsg("Index in materials array is out of bounds.");
50✔
1662
    return OPENMC_E_OUT_OF_BOUNDS;
50✔
1663
  }
50✔
1664
}
1665

×
UNCOV
1666
extern "C" int openmc_material_get_depletable(int32_t index, bool* depletable)
×
1667
{
1668
  if (index < 0 || index >= model::materials.size()) {
1669
    set_errmsg("Index in materials array is out of bounds.");
×
UNCOV
1670
    return OPENMC_E_OUT_OF_BOUNDS;
×
1671
  }
1672

1673
  *depletable = model::materials[index]->depletable();
1674

24✔
1675
  return 0;
1676
}
24✔
1677

×
UNCOV
1678
extern "C" int openmc_material_set_depletable(int32_t index, bool depletable)
×
1679
{
1680
  if (index < 0 || index >= model::materials.size()) {
1681
    set_errmsg("Index in materials array is out of bounds.");
24✔
1682
    return OPENMC_E_OUT_OF_BOUNDS;
1683
  }
24✔
1684

1685
  model::materials[index]->depletable() = depletable;
1686

12✔
1687
  return 0;
1688
}
12✔
1689

×
UNCOV
1690
extern "C" int openmc_extend_materials(
×
1691
  int32_t n, int32_t* index_start, int32_t* index_end)
1692
{
1693
  if (index_start)
12✔
1694
    *index_start = model::materials.size();
1695
  if (index_end)
12✔
1696
    *index_end = model::materials.size() + n - 1;
1697
  for (int32_t i = 0; i < n; i++) {
1698
    model::materials.push_back(make_unique<Material>());
48✔
1699
  }
1700
  return 0;
1701
}
48✔
1702

48✔
1703
extern "C" size_t n_materials()
48✔
UNCOV
1704
{
×
1705
  return model::materials.size();
96✔
1706
}
48✔
1707

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

© 2025 Coveralls, Inc