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

openmc-dev / openmc / 19886361392

03 Dec 2025 07:48AM UTC coverage: 81.942% (-0.1%) from 82.057%
19886361392

push

github

web-flow
Improved automatic MGXS generation for random ray (#3658)

Co-authored-by: Paul Romano <paul.k.romano@gmail.com>

16953 of 23523 branches covered (72.07%)

Branch coverage included in aggregate %.

26 of 31 new or added lines in 1 file covered. (83.87%)

120 existing lines in 2 files now uncovered.

54900 of 64165 relevant lines covered (85.56%)

43352334.81 hits per line

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

62.19
/src/dagmc.cpp
1
#include "openmc/dagmc.h"
2

3
#include <cassert>
4

5
#include "openmc/constants.h"
6
#include "openmc/container_util.h"
7
#include "openmc/error.h"
8
#include "openmc/file_utils.h"
9
#include "openmc/geometry.h"
10
#include "openmc/geometry_aux.h"
11
#include "openmc/hdf5_interface.h"
12
#include "openmc/material.h"
13
#include "openmc/settings.h"
14
#include "openmc/string_utils.h"
15

16
#ifdef OPENMC_UWUW_ENABLED
17
#include "uwuw.hpp"
18
#endif
19
#include <fmt/core.h>
20

21
#include <algorithm>
22
#include <filesystem>
23
#include <fstream>
24
#include <sstream>
25
#include <string>
26

27
namespace openmc {
28

29
#ifdef OPENMC_DAGMC_ENABLED
30
const bool DAGMC_ENABLED = true;
31
#else
32
const bool DAGMC_ENABLED = false;
33
#endif
34

35
#ifdef OPENMC_UWUW_ENABLED
36
const bool UWUW_ENABLED = true;
37
#else
38
const bool UWUW_ENABLED = false;
39
#endif
40

41
} // namespace openmc
42

43
#ifdef OPENMC_DAGMC_ENABLED
44

45
namespace openmc {
46

47
//==============================================================================
48
// DAGMC Universe implementation
49
//==============================================================================
50

51
DAGUniverse::DAGUniverse(pugi::xml_node node)
44✔
52
{
53
  if (check_for_node(node, "id")) {
44!
54
    id_ = std::stoi(get_node_value(node, "id"));
44✔
55
  } else {
UNCOV
56
    fatal_error("Must specify the id of the DAGMC universe");
×
57
  }
58

59
  if (check_for_node(node, "filename")) {
44!
60
    filename_ = get_node_value(node, "filename");
44✔
61
    if (!starts_with(filename_, "/")) {
44✔
62
      std::filesystem::path d(dir_name(settings::path_input));
23✔
63
      filename_ = (d / filename_).string();
23✔
64
    }
23✔
65
  } else {
UNCOV
66
    fatal_error("Must specify a file for the DAGMC universe");
×
67
  }
68

69
  adjust_geometry_ids_ = false;
44✔
70
  if (check_for_node(node, "auto_geom_ids")) {
44✔
71
    adjust_geometry_ids_ = get_node_value_bool(node, "auto_geom_ids");
15✔
72
  }
73

74
  adjust_material_ids_ = false;
44✔
75
  if (check_for_node(node, "auto_mat_ids")) {
44!
UNCOV
76
    adjust_material_ids_ = get_node_value_bool(node, "auto_mat_ids");
×
77
  }
78

79
  // get material assignment overloading
80
  if (check_for_node(node, "material_overrides")) {
44✔
81
    auto mat_node = node.child("material_overrides");
2✔
82
    // loop over all subelements (each subelement corresponds to a material)
83
    for (pugi::xml_node cell_node : mat_node.children("cell_override")) {
6✔
84
      // Store assignment reference name
85
      int32_t ref_assignment = std::stoi(get_node_value(cell_node, "id"));
4✔
86

87
      // Get mat name for each assignement instances
88
      vector<int32_t> instance_mats =
89
        get_node_array<int32_t>(cell_node, "material_ids");
4✔
90

91
      // Store mat name for each instances
92
      material_overrides_.emplace(ref_assignment, instance_mats);
4✔
93
    }
4✔
94
  }
95

96
  initialize();
44✔
97
}
42✔
98

UNCOV
99
DAGUniverse::DAGUniverse(
×
UNCOV
100
  const std::string& filename, bool auto_geom_ids, bool auto_mat_ids)
×
UNCOV
101
  : filename_(filename), adjust_geometry_ids_(auto_geom_ids),
×
UNCOV
102
    adjust_material_ids_(auto_mat_ids)
×
103
{
UNCOV
104
  set_id();
×
UNCOV
105
  initialize();
×
UNCOV
106
}
×
107

108
DAGUniverse::DAGUniverse(std::shared_ptr<moab::DagMC> dagmc_ptr,
2✔
109
  const std::string& filename, bool auto_geom_ids, bool auto_mat_ids)
2✔
110
  : dagmc_instance_(dagmc_ptr), filename_(filename),
2✔
111
    adjust_geometry_ids_(auto_geom_ids), adjust_material_ids_(auto_mat_ids)
4✔
112
{
113
  set_id();
2✔
114
  init_metadata();
2✔
115
  init_geometry();
2✔
116
}
2✔
117

118
void DAGUniverse::set_id()
2✔
119
{
120
  // determine the next universe id
121
  int32_t next_univ_id = 0;
2✔
122
  for (const auto& u : model::universes) {
2!
UNCOV
123
    if (u->id_ > next_univ_id)
×
UNCOV
124
      next_univ_id = u->id_;
×
125
  }
126
  next_univ_id++;
2✔
127

128
  // set the universe id
129
  id_ = next_univ_id;
2✔
130
}
2✔
131

132
void DAGUniverse::initialize()
44✔
133
{
134
#ifdef OPENMC_UWUW_ENABLED
135
  // read uwuw materials from the .h5m file if present
136
  read_uwuw_materials();
44✔
137
#endif
138

139
  init_dagmc();
44✔
140

141
  init_metadata();
44✔
142

143
  init_geometry();
44✔
144
}
42✔
145

146
void DAGUniverse::init_dagmc()
44✔
147
{
148

149
  // create a new DAGMC instance
150
  dagmc_instance_ = std::make_shared<moab::DagMC>();
44✔
151

152
  // load the DAGMC geometry
153
  if (!file_exists(filename_)) {
44!
UNCOV
154
    fatal_error("Geometry DAGMC file '" + filename_ + "' does not exist!");
×
155
  }
156
  moab::ErrorCode rval = dagmc_instance_->load_file(filename_.c_str());
44✔
157
  MB_CHK_ERR_CONT(rval);
44!
158

159
  // initialize acceleration data structures
160
  rval = dagmc_instance_->init_OBBTree();
44✔
161
  MB_CHK_ERR_CONT(rval);
44!
162
}
44✔
163

164
void DAGUniverse::init_metadata()
46✔
165
{
166
  // parse model metadata
167
  dmd_ptr =
168
    std::make_unique<dagmcMetaData>(dagmc_instance_.get(), false, false);
46✔
169
  dmd_ptr->load_property_data();
46✔
170

171
  std::vector<std::string> keywords {"temp"};
184✔
172
  std::map<std::string, std::string> dum;
46✔
173
  std::string delimiters = ":/";
46✔
174
  moab::ErrorCode rval;
175
  rval = dagmc_instance_->parse_properties(keywords, dum, delimiters.c_str());
46✔
176
  MB_CHK_ERR_CONT(rval);
46!
177
}
46✔
178

179
void DAGUniverse::init_geometry()
46✔
180
{
181
  moab::ErrorCode rval;
182

183
  // determine the next cell id
184
  int32_t next_cell_id = 0;
46✔
185
  for (const auto& c : model::cells) {
70✔
186
    if (c->id_ > next_cell_id)
24!
187
      next_cell_id = c->id_;
24✔
188
  }
189
  cell_idx_offset_ = model::cells.size();
46✔
190
  next_cell_id++;
46✔
191

192
  // initialize cell objects
193
  int n_cells = dagmc_instance_->num_entities(3);
46✔
194
  moab::EntityHandle graveyard = 0;
46✔
195
  for (int i = 0; i < n_cells; i++) {
260✔
196
    moab::EntityHandle vol_handle = dagmc_instance_->entity_by_index(3, i + 1);
216✔
197

198
    // set cell ids using global IDs
199
    auto c = std::make_unique<DAGCell>(dagmc_instance_, i + 1);
216✔
200
    c->id_ = adjust_geometry_ids_
216✔
201
               ? next_cell_id++
216✔
202
               : dagmc_instance_->id_by_index(3, c->dag_index());
139✔
203
    c->universe_ = this->id_;
216✔
204
    c->fill_ = C_NONE; // no fill, single universe
216✔
205

206
    auto in_map = model::cell_map.find(c->id_);
216✔
207
    if (in_map == model::cell_map.end()) {
216!
208
      model::cell_map[c->id_] = model::cells.size();
216✔
209
    } else {
UNCOV
210
      warning(fmt::format("DAGMC Cell IDs: {}", dagmc_ids_for_dim(3)));
×
UNCOV
211
      fatal_error(fmt::format(
×
212
        "DAGMC Universe {} contains a cell with ID {}, which "
213
        "already exists elsewhere in the geometry. Setting auto_geom_ids "
214
        "to True when initiating the DAGMC Universe may "
215
        "resolve this issue",
UNCOV
216
        this->id_, c->id_));
×
217
    }
218

219
    // --- Materials ---
220

221
    // determine volume material assignment
222
    std::string mat_str = dmd_ptr->get_volume_property("material", vol_handle);
432✔
223

224
    if (mat_str.empty()) {
216!
UNCOV
225
      fatal_error(fmt::format("Volume {} has no material assignment.", c->id_));
×
226
    }
227

228
    to_lower(mat_str);
216✔
229

230
    if (mat_str == "graveyard") {
216✔
231
      graveyard = vol_handle;
40✔
232
    }
233
    // material void checks
234
    if (mat_str == "void" || mat_str == "vacuum" || mat_str == "graveyard") {
216✔
235
      c->material_.push_back(MATERIAL_VOID);
85✔
236
    } else {
237
      if (material_overrides_.count(c->id_)) {
131✔
238
        override_assign_material(c);
4✔
239
      } else if (uses_uwuw()) {
127✔
240
        uwuw_assign_material(vol_handle, c);
12✔
241
      } else {
242
        legacy_assign_material(mat_str, c);
115✔
243
      }
244
    }
245

246
    // check for temperature assignment
247
    std::string temp_value;
214✔
248

249
    // no temperature if void
250
    if (c->material_[0] == MATERIAL_VOID) {
214✔
251
      model::cells.emplace_back(std::move(c));
85✔
252
      continue;
85✔
253
    }
254

255
    // assign cell temperature
256
    const auto& mat = model::materials[model::material_map.at(c->material_[0])];
129✔
257
    if (dagmc_instance_->has_prop(vol_handle, "temp")) {
129✔
258
      rval = dagmc_instance_->prop_value(vol_handle, "temp", temp_value);
49✔
259
      MB_CHK_ERR_CONT(rval);
49!
260
      double temp = std::stod(temp_value);
49✔
261
      c->sqrtkT_.push_back(std::sqrt(K_BOLTZMANN * temp));
49✔
262
    } else if (mat->temperature() > 0.0) {
80!
263
      c->sqrtkT_.push_back(std::sqrt(K_BOLTZMANN * mat->temperature()));
80✔
264
    } else {
UNCOV
265
      c->sqrtkT_.push_back(
×
UNCOV
266
        std::sqrt(K_BOLTZMANN * settings::temperature_default));
×
267
    }
268

269
    model::cells.emplace_back(std::move(c));
129✔
270
  }
384✔
271

272
  // allocate the cell overlap count if necessary
273
  if (settings::check_overlaps) {
44✔
274
    model::overlap_check_count.resize(model::cells.size(), 0);
3✔
275
  }
276

277
  has_graveyard_ = graveyard;
44✔
278

279
  // determine the next surface id
280
  int32_t next_surf_id = 0;
44✔
281
  for (const auto& s : model::surfaces) {
178✔
282
    if (s->id_ > next_surf_id)
134!
283
      next_surf_id = s->id_;
134✔
284
  }
285
  surf_idx_offset_ = model::surfaces.size();
44✔
286
  next_surf_id++;
44✔
287

288
  // initialize surface objects
289
  int n_surfaces = dagmc_instance_->num_entities(2);
44✔
290
  for (int i = 0; i < n_surfaces; i++) {
926✔
291
    moab::EntityHandle surf_handle = dagmc_instance_->entity_by_index(2, i + 1);
882✔
292

293
    // set cell ids using global IDs
294
    auto s = std::make_unique<DAGSurface>(dagmc_instance_, i + 1);
882✔
295
    s->id_ = adjust_geometry_ids_ ? next_surf_id++
1,437✔
296
                                  : dagmc_instance_->id_by_index(2, i + 1);
555✔
297

298
    // set surface source attribute if needed
299
    if (contains(settings::source_write_surf_id, s->id_) ||
1,760✔
300
        settings::source_write_surf_id.empty()) {
878✔
301
      s->surf_source_ = true;
760✔
302
    }
303

304
    // set BCs
305
    std::string bc_value =
306
      dmd_ptr->get_surface_property("boundary", surf_handle);
1,764✔
307
    to_lower(bc_value);
882✔
308
    if (bc_value.empty() || bc_value == "transmit" ||
900!
309
        bc_value == "transmission") {
18✔
310
      // set to transmission by default (nullptr)
311
    } else if (bc_value == "vacuum") {
18✔
312
      s->bc_ = make_unique<VacuumBC>();
6✔
313
    } else if (bc_value == "reflective" || bc_value == "reflect" ||
24!
314
               bc_value == "reflecting") {
12✔
315
      s->bc_ = make_unique<ReflectiveBC>();
12✔
UNCOV
316
    } else if (bc_value == "periodic") {
×
UNCOV
317
      fatal_error("Periodic boundary condition not supported in DAGMC.");
×
318
    } else {
UNCOV
319
      fatal_error(fmt::format("Unknown boundary condition \"{}\" specified "
×
320
                              "on surface {}",
UNCOV
321
        bc_value, s->id_));
×
322
    }
323

324
    // graveyard check
325
    moab::Range parent_vols;
882✔
326
    rval = dagmc_instance_->moab_instance()->get_parent_meshsets(
882✔
327
      surf_handle, parent_vols);
328
    MB_CHK_ERR_CONT(rval);
882!
329

330
    // if this surface belongs to the graveyard
331
    if (graveyard && parent_vols.find(graveyard) != parent_vols.end()) {
882✔
332
      // set graveyard surface BC's to vacuum
333
      s->bc_ = make_unique<VacuumBC>();
480✔
334
    }
335

336
    // add to global array and map
337

338
    auto in_map = model::surface_map.find(s->id_);
882✔
339
    if (in_map == model::surface_map.end()) {
882!
340
      model::surface_map[s->id_] = model::surfaces.size();
882✔
341
    } else {
UNCOV
342
      warning(fmt::format("DAGMC Surface IDs: {}", dagmc_ids_for_dim(2)));
×
UNCOV
343
      fatal_error(fmt::format("Surface ID {} exists in both Universe {} "
×
344
                              "and the CSG geometry.",
UNCOV
345
        s->id_, this->id_));
×
346
    }
347

348
    model::surfaces.emplace_back(std::move(s));
882✔
349
  } // end surface loop
882✔
350
}
44✔
351

352
int32_t DAGUniverse::cell_index(moab::EntityHandle vol) const
44,310✔
353
{
354
  // return the index of the volume in the DAGMC instance and then
355
  // adjust by the offset into the model cells for this DAGMC universe
356
  return dagmc_ptr()->index_by_handle(vol) + cell_idx_offset_;
44,310✔
357
}
358

UNCOV
359
int32_t DAGUniverse::surface_index(moab::EntityHandle surf) const
×
360
{
361
  // return the index of the surface in the DAGMC instance and then
362
  // adjust by the offset into the model cells for this DAGMC universe
UNCOV
363
  return dagmc_ptr()->index_by_handle(surf) + surf_idx_offset_;
×
364
}
365

UNCOV
366
std::string DAGUniverse::dagmc_ids_for_dim(int dim) const
×
367
{
368
  // generate a vector of ids
UNCOV
369
  std::vector<int> id_vec;
×
UNCOV
370
  int n_ents = dagmc_instance_->num_entities(dim);
×
UNCOV
371
  for (int i = 1; i <= n_ents; i++) {
×
UNCOV
372
    id_vec.push_back(dagmc_instance_->id_by_index(dim, i));
×
373
  }
374

375
  // sort the vector of ids
UNCOV
376
  std::sort(id_vec.begin(), id_vec.end());
×
377

378
  // generate a string representation of the ID range(s)
UNCOV
379
  std::stringstream out;
×
380

UNCOV
381
  int i = 0;
×
UNCOV
382
  int start_id = id_vec[0]; // initialize with first ID
×
383
  int stop_id;
384
  // loop over all cells in the universe
UNCOV
385
  while (i < n_ents) {
×
386

UNCOV
387
    stop_id = id_vec[i];
×
388

389
    // if the next ID is not in this contiguous set of IDS,
390
    // figure out how to write the string representing this set
UNCOV
391
    if (id_vec[i + 1] > stop_id + 1) {
×
392

UNCOV
393
      if (start_id != stop_id) {
×
394
        // there are several IDs in a row, print condensed version (i.e. 1-10,
395
        // 12-20)
UNCOV
396
        out << start_id << "-" << stop_id;
×
397
      } else {
398
        // only one ID in this contiguous block (i.e. 3, 5, 7, 9)
UNCOV
399
        out << start_id;
×
400
      }
401
      // insert a comma as long as we aren't in the last ID set
UNCOV
402
      if (i < n_ents - 1) {
×
UNCOV
403
        out << ", ";
×
404
      }
405

406
      // if we are at the end of a set, set the start ID to the first value
407
      // in the next set.
UNCOV
408
      start_id = id_vec[++i];
×
409
    }
410

UNCOV
411
    i++;
×
412
  }
413

UNCOV
414
  return out.str();
×
UNCOV
415
}
×
416

417
int32_t DAGUniverse::implicit_complement_idx() const
25,632✔
418
{
419
  moab::EntityHandle ic;
420
  moab::ErrorCode rval =
421
    dagmc_instance_->geom_tool()->get_implicit_complement(ic);
25,632✔
422
  MB_CHK_SET_ERR_CONT(rval, "Failed to get implicit complement");
25,632!
423
  // off-by-one: DAGMC indices start at one
424
  return cell_idx_offset_ + dagmc_instance_->index_by_handle(ic) - 1;
25,632✔
425
}
426

427
bool DAGUniverse::find_cell(GeometryState& p) const
5,139,311✔
428
{
429
  // if the particle isn't in any of the other DagMC
430
  // cells, place it in the implicit complement
431
  bool found = Universe::find_cell(p);
5,139,311✔
432
  if (!found && model::universe_map[this->id_] != model::root_universe) {
5,139,311!
433
    p.lowest_coord().cell() = implicit_complement_idx();
25,410✔
434
    found = true;
25,410✔
435
  }
436
  return found;
5,139,311✔
437
}
438

439
void DAGUniverse::to_hdf5(hid_t universes_group) const
30✔
440
{
441
  // Create a group for this universe.
442
  auto group = create_group(universes_group, fmt::format("universe {}", id_));
60✔
443

444
  // Write the geometry representation type.
445
  write_string(group, "geom_type", "dagmc", false);
30✔
446

447
  // Write other properties of the DAGMC Universe
448
  write_string(group, "filename", filename_, false);
30✔
449
  write_attribute(
30✔
450
    group, "auto_geom_ids", static_cast<int>(adjust_geometry_ids_));
30✔
451
  write_attribute(
30✔
452
    group, "auto_mat_ids", static_cast<int>(adjust_material_ids_));
30✔
453

454
  close_group(group);
30✔
455
}
30✔
456

457
bool DAGUniverse::uses_uwuw() const
175✔
458
{
459
#ifdef OPENMC_UWUW_ENABLED
460
  return uwuw_ && !uwuw_->material_library.empty();
175✔
461
#else
462
  return false;
463
#endif // OPENMC_UWUW_ENABLED
464
}
465

466
std::string DAGUniverse::get_uwuw_materials_xml() const
4✔
467
{
468
#ifdef OPENMC_UWUW_ENABLED
469
  if (!uses_uwuw()) {
4!
UNCOV
470
    throw std::runtime_error("This DAGMC Universe does not use UWUW materials");
×
471
  }
472

473
  std::stringstream ss;
4✔
474
  // write header
475
  ss << "<?xml version=\"1.0\"?>\n";
4✔
476
  ss << "<materials>\n";
4✔
477
  const auto& mat_lib = uwuw_->material_library;
4✔
478
  // write materials
479
  for (auto mat : mat_lib) {
12✔
480
    ss << mat.second->openmc("atom");
8✔
481
  }
8✔
482
  // write footer
483
  ss << "</materials>";
4✔
484

485
  return ss.str();
8✔
486
#else
487
  fatal_error("DAGMC was not configured with UWUW.");
488
#endif // OPENMC_UWUW_ENABLED
489
}
4✔
490

UNCOV
491
void DAGUniverse::write_uwuw_materials_xml(const std::string& outfile) const
×
492
{
493
#ifdef OPENMC_UWUW_ENABLED
UNCOV
494
  if (!uses_uwuw()) {
×
UNCOV
495
    throw std::runtime_error(
×
UNCOV
496
      "This DAGMC universe does not use UWUW materials.");
×
497
  }
498

UNCOV
499
  std::string xml_str = get_uwuw_materials_xml();
×
500
  // if there is a material library in the file
UNCOV
501
  std::ofstream mats_xml(outfile);
×
UNCOV
502
  mats_xml << xml_str;
×
UNCOV
503
  mats_xml.close();
×
504
#else
505
  fatal_error("DAGMC was not configured with UWUW.");
506
#endif // OPENMC_UWUW_ENABLED
UNCOV
507
}
×
508

509
void DAGUniverse::legacy_assign_material(
115✔
510
  std::string mat_string, std::unique_ptr<DAGCell>& c) const
511
{
512
  bool mat_found_by_name = false;
115✔
513
  // attempt to find a material with a matching name
514
  to_lower(mat_string);
115✔
515
  for (const auto& m : model::materials) {
354✔
516
    std::string m_name = m->name();
239✔
517
    to_lower(m_name);
239✔
518
    if (mat_string == m_name) {
239✔
519
      // assign the material with that name
520
      if (!mat_found_by_name) {
94!
521
        mat_found_by_name = true;
94✔
522
        c->material_.push_back(m->id_);
94✔
523
        // report error if more than one material is found
524
      } else {
UNCOV
525
        fatal_error(fmt::format(
×
526
          "More than one material found with name '{}'. Please ensure "
527
          "materials "
528
          "have unique names if using this property to assign materials.",
529
          mat_string));
530
      }
531
    }
532
  }
239✔
533

534
  // if no material was set using a name, assign by id
535
  if (!mat_found_by_name) {
115✔
536
    bool found_by_id = true;
21✔
537
    try {
538
      auto id = std::stoi(mat_string);
21✔
539
      if (model::material_map.find(id) == model::material_map.end())
20✔
540
        found_by_id = false;
1✔
541
      c->material_.emplace_back(id);
20✔
542
    } catch (const std::invalid_argument&) {
1!
543
      found_by_id = false;
1✔
544
    }
1✔
545

546
    // report failure for failed int conversion or missing material
547
    if (!found_by_id)
21✔
548
      fatal_error(
2✔
549
        fmt::format("Material with name/ID '{}' not found for volume (cell) {}",
2✔
550
          mat_string, c->id_));
2✔
551
  }
552

553
  if (settings::verbosity >= 10) {
113!
UNCOV
554
    const auto& m = model::materials[model::material_map.at(c->material_[0])];
×
UNCOV
555
    std::stringstream msg;
×
UNCOV
556
    msg << "DAGMC material " << mat_string << " was assigned";
×
UNCOV
557
    if (mat_found_by_name) {
×
UNCOV
558
      msg << " using material name: " << m->name_;
×
559
    } else {
UNCOV
560
      msg << " using material id: " << m->id_;
×
561
    }
UNCOV
562
    write_message(msg.str(), 10);
×
UNCOV
563
  }
×
564
}
113✔
565

566
void DAGUniverse::read_uwuw_materials()
44✔
567
{
568
#ifdef OPENMC_UWUW_ENABLED
569
  // If no filename was provided, don't read UWUW materials
570
  if (filename_ == "")
44!
571
    return;
40✔
572

573
  uwuw_ = std::make_shared<UWUW>(filename_.c_str());
44✔
574

575
  if (!uses_uwuw())
44✔
576
    return;
40✔
577

578
  // Notify user if UWUW materials are going to be used
579
  write_message("Found UWUW Materials in the DAGMC geometry file.", 6);
4✔
580

581
  // if we're using automatic IDs, update the UWUW material metadata
582
  if (adjust_material_ids_) {
4!
UNCOV
583
    int32_t next_material_id = 0;
×
UNCOV
584
    for (const auto& m : model::materials) {
×
UNCOV
585
      next_material_id = std::max(m->id_, next_material_id);
×
586
    }
UNCOV
587
    next_material_id++;
×
588

UNCOV
589
    for (auto& mat : uwuw_->material_library) {
×
UNCOV
590
      mat.second->metadata["mat_number"] = next_material_id++;
×
591
    }
592
  }
593

594
  std::string mat_xml_string = get_uwuw_materials_xml();
4✔
595

596
  // create a pugi XML document from this string
597
  pugi::xml_document doc;
4✔
598
  auto result = doc.load_string(mat_xml_string.c_str());
4✔
599
  if (!result) {
4!
UNCOV
600
    fatal_error("Error processing XML created using DAGMC UWUW materials.");
×
601
  }
602
  pugi::xml_node root = doc.document_element();
4✔
603
  for (pugi::xml_node material_node : root.children("material")) {
12✔
604
    model::materials.push_back(std::make_unique<Material>(material_node));
8✔
605
  }
606
#else
607
  fatal_error("DAGMC was not configured with UWUW.");
608
#endif // OPENMC_UWUW_ENABLED
609
}
4✔
610

611
void DAGUniverse::uwuw_assign_material(
12✔
612
  moab::EntityHandle vol_handle, std::unique_ptr<DAGCell>& c) const
613
{
614
#ifdef OPENMC_UWUW_ENABLED
615
  // lookup material in uwuw if present
616
  std::string uwuw_mat = dmd_ptr->volume_material_property_data_eh[vol_handle];
12✔
617
  if (uwuw_->material_library.count(uwuw_mat) != 0) {
12!
618
    // Note: material numbers are set by UWUW
619
    int mat_number = uwuw_->material_library.get_material(uwuw_mat)
12✔
620
                       .metadata["mat_number"]
12✔
621
                       .asInt();
12✔
622
    c->material_.push_back(mat_number);
12✔
623
  } else {
UNCOV
624
    fatal_error(fmt::format("Material with value '{}' not found in the "
×
625
                            "UWUW material library",
626
      uwuw_mat));
627
  }
628
#else
629
  fatal_error("DAGMC was not configured with UWUW.");
630
#endif // OPENMC_UWUW_ENABLED
631
}
12✔
632

633
void DAGUniverse::override_assign_material(std::unique_ptr<DAGCell>& c) const
4✔
634
{
635
  // if Cell ID matches an override key, use it to override the material
636
  // assignment else if UWUW is used, get the material assignment from the DAGMC
637
  // metadata
638
  // Notify User that an override is being applied on a DAGMCCell
639
  write_message(fmt::format("Applying override for DAGMCCell {}", c->id_), 8);
8✔
640

641
  if (settings::verbosity >= 10) {
4!
642
    auto msg = fmt::format("Assigning DAGMC cell {} material(s) based on "
643
                           "override information (see input XML).",
UNCOV
644
      c->id_);
×
UNCOV
645
    write_message(msg, 10);
×
UNCOV
646
  }
×
647

648
  // Override the material assignment for each cell instance using the legacy
649
  // assignement
650
  for (auto mat_id : material_overrides_.at(c->id_)) {
17✔
651
    if (model::material_map.find(mat_id) == model::material_map.end()) {
13!
UNCOV
652
      fatal_error(fmt::format(
×
UNCOV
653
        "Material with ID '{}' not found for DAGMC cell {}", mat_id, c->id_));
×
654
    }
655
    c->material_.push_back(mat_id);
13✔
656
  }
657
}
4✔
658

659
//==============================================================================
660
// DAGMC Cell implementation
661
//==============================================================================
662

663
DAGCell::DAGCell(std::shared_ptr<moab::DagMC> dag_ptr, int32_t dag_idx)
216✔
664
  : Cell {}, dagmc_ptr_(dag_ptr), dag_index_(dag_idx) {};
216✔
665

666
std::pair<double, int32_t> DAGCell::distance(
341,844✔
667
  Position r, Direction u, int32_t on_surface, GeometryState* p) const
668
{
669
  // if we've changed direction or we're not on a surface,
670
  // reset the history and update last direction
671
  if (u != p->last_dir()) {
341,844✔
672
    p->last_dir() = u;
295,639✔
673
    p->history().reset();
295,639✔
674
  }
675
  if (on_surface == SURFACE_NONE) {
341,844✔
676
    p->history().reset();
275,719✔
677
  }
678

679
  const auto& univ = model::universes[p->lowest_coord().universe()];
341,844✔
680

681
  DAGUniverse* dag_univ = static_cast<DAGUniverse*>(univ.get());
341,844✔
682
  if (!dag_univ)
341,844!
UNCOV
683
    fatal_error("DAGMC call made for particle in a non-DAGMC universe");
×
684

685
  // initialize to lost particle conditions
686
  int surf_idx = -1;
341,844✔
687
  double dist = INFINITY;
341,844✔
688

689
  moab::EntityHandle vol = dagmc_ptr_->entity_by_index(3, dag_index_);
341,844✔
690
  moab::EntityHandle hit_surf;
691

692
  // create the ray
693
  double pnt[3] = {r.x, r.y, r.z};
341,844✔
694
  double dir[3] = {u.x, u.y, u.z};
341,844✔
695
  MB_CHK_ERR_CONT(
341,844!
696
    dagmc_ptr_->ray_fire(vol, pnt, dir, hit_surf, dist, &p->history()));
697
  if (hit_surf != 0) {
341,844✔
698
    surf_idx =
324,962✔
699
      dag_univ->surf_idx_offset_ + dagmc_ptr_->index_by_handle(hit_surf);
324,962✔
700
  } else if (!dagmc_ptr_->is_implicit_complement(vol) ||
33,764!
701
             is_root_universe(dag_univ->id_)) {
16,882!
702
    // surface boundary conditions are ignored for projection plotting, meaning
703
    // that the particle may move through the graveyard (bounding) volume and
704
    // into the implicit complement on the other side where no intersection will
705
    // be found. Treating this as a lost particle is problematic when plotting.
706
    // Instead, the infinite distance and invalid surface index are returned.
UNCOV
707
    if (settings::run_mode == RunMode::PLOTTING)
×
UNCOV
708
      return {INFTY, -1};
×
709

710
    // the particle should be marked as lost immediately if an intersection
711
    // isn't found in a volume that is not the implicit complement. In the case
712
    // that the DAGMC model is the root universe of the geometry, even a missing
713
    // intersection in the implicit complement should trigger this condition.
714
    std::string material_id =
UNCOV
715
      p->material() == MATERIAL_VOID
×
716
        ? "-1 (VOID)"
UNCOV
717
        : std::to_string(model::materials[p->material()]->id());
×
UNCOV
718
    p->mark_as_lost(fmt::format(
×
UNCOV
719
      "No intersection found with DAGMC cell {}, filled with material {}", id_,
×
720
      material_id));
UNCOV
721
  }
×
722

723
  return {dist, surf_idx};
341,844✔
724
}
725

726
bool DAGCell::contains(Position r, Direction u, int32_t on_surface) const
5,527,425✔
727
{
728
  moab::ErrorCode rval;
729
  moab::EntityHandle vol = dagmc_ptr_->entity_by_index(3, dag_index_);
5,527,425✔
730

731
  int result = 0;
5,527,425✔
732
  double pnt[3] = {r.x, r.y, r.z};
5,527,425✔
733
  double dir[3] = {u.x, u.y, u.z};
5,527,425✔
734
  rval = dagmc_ptr_->point_in_volume(vol, pnt, result, dir);
5,527,425✔
735
  MB_CHK_ERR_CONT(rval);
5,527,425!
736
  return result;
5,527,425✔
737
}
738

739
moab::EntityHandle DAGCell::mesh_handle() const
44,310✔
740
{
741
  return dagmc_ptr()->entity_by_index(3, dag_index());
44,310✔
742
}
743

744
void DAGCell::to_hdf5_inner(hid_t group_id) const
145✔
745
{
746
  write_string(group_id, "geom_type", "dagmc", false);
145✔
747
}
145✔
748

UNCOV
749
BoundingBox DAGCell::bounding_box() const
×
750
{
751
  moab::ErrorCode rval;
UNCOV
752
  moab::EntityHandle vol = dagmc_ptr_->entity_by_index(3, dag_index_);
×
753
  double min[3], max[3];
UNCOV
754
  rval = dagmc_ptr_->getobb(vol, min, max);
×
UNCOV
755
  MB_CHK_ERR_CONT(rval);
×
UNCOV
756
  return {min[0], max[0], min[1], max[1], min[2], max[2]};
×
757
}
758

759
//==============================================================================
760
// DAGSurface implementation
761
//==============================================================================
762

763
DAGSurface::DAGSurface(std::shared_ptr<moab::DagMC> dag_ptr, int32_t dag_idx)
882✔
764
  : Surface {}, dagmc_ptr_(dag_ptr), dag_index_(dag_idx)
882✔
765
{} // empty constructor
882✔
766

767
moab::EntityHandle DAGSurface::mesh_handle() const
47,068✔
768
{
769
  return dagmc_ptr()->entity_by_index(2, dag_index());
47,068✔
770
}
771

UNCOV
772
double DAGSurface::evaluate(Position r) const
×
773
{
UNCOV
774
  return 0.0;
×
775
}
776

UNCOV
777
double DAGSurface::distance(Position r, Direction u, bool coincident) const
×
778
{
779
  moab::ErrorCode rval;
UNCOV
780
  moab::EntityHandle surf = dagmc_ptr_->entity_by_index(2, dag_index_);
×
781
  moab::EntityHandle hit_surf;
782
  double dist;
UNCOV
783
  double pnt[3] = {r.x, r.y, r.z};
×
UNCOV
784
  double dir[3] = {u.x, u.y, u.z};
×
UNCOV
785
  rval = dagmc_ptr_->ray_fire(surf, pnt, dir, hit_surf, dist, NULL, 0, 0);
×
UNCOV
786
  MB_CHK_ERR_CONT(rval);
×
UNCOV
787
  if (dist < 0.0)
×
UNCOV
788
    dist = INFTY;
×
UNCOV
789
  return dist;
×
790
}
791

UNCOV
792
Direction DAGSurface::normal(Position r) const
×
793
{
794
  moab::ErrorCode rval;
UNCOV
795
  moab::EntityHandle surf = dagmc_ptr_->entity_by_index(2, dag_index_);
×
UNCOV
796
  double pnt[3] = {r.x, r.y, r.z};
×
797
  double dir[3];
UNCOV
798
  rval = dagmc_ptr_->get_angle(surf, pnt, dir);
×
UNCOV
799
  MB_CHK_ERR_CONT(rval);
×
UNCOV
800
  return dir;
×
801
}
802

803
Direction DAGSurface::reflect(Position r, Direction u, GeometryState* p) const
2,758✔
804
{
805
  assert(p);
2,758!
806
  double pnt[3] = {r.x, r.y, r.z};
2,758✔
807
  double dir[3];
808
  moab::ErrorCode rval =
809
    dagmc_ptr_->get_angle(mesh_handle(), pnt, dir, &p->history());
2,758✔
810
  MB_CHK_ERR_CONT(rval);
2,758!
811
  return u.reflect(dir);
2,758✔
812
}
813

814
//==============================================================================
815
// Non-member functions
816
//==============================================================================
817

818
void read_dagmc_universes(pugi::xml_node node)
860✔
819
{
820
  for (pugi::xml_node dag_node : node.children("dagmc_universe")) {
902✔
821
    model::universes.push_back(std::make_unique<DAGUniverse>(dag_node));
44✔
822
    model::universe_map[model::universes.back()->id_] =
42✔
823
      model::universes.size() - 1;
42✔
824
  }
825
}
858✔
826

827
void check_dagmc_root_univ()
860✔
828
{
829
  const auto& ru = model::universes[model::root_universe];
860✔
830
  if (ru->geom_type() == GeometryType::DAG) {
860✔
831
    // if the root universe contains DAGMC geometry, warn the user
832
    // if it does not contain a graveyard volume
833
    auto dag_univ = dynamic_cast<DAGUniverse*>(ru.get());
23!
834
    if (dag_univ && !dag_univ->has_graveyard()) {
23!
835
      warning(
1✔
836
        "No graveyard volume found in the DagMC model. "
837
        "This may result in lost particles and rapid simulation failure.");
838
    }
839
  }
840
}
860✔
841

842
int32_t next_cell(int32_t surf, int32_t curr_cell, int32_t univ)
44,310✔
843
{
844
  auto surfp = dynamic_cast<DAGSurface*>(model::surfaces[surf].get());
44,310!
845
  auto cellp = dynamic_cast<DAGCell*>(model::cells[curr_cell].get());
44,310!
846
  auto univp = static_cast<DAGUniverse*>(model::universes[univ].get());
44,310✔
847

848
  moab::EntityHandle surf_handle = surfp->mesh_handle();
44,310✔
849
  moab::EntityHandle curr_vol = cellp->mesh_handle();
44,310✔
850

851
  moab::EntityHandle new_vol;
852
  moab::ErrorCode rval =
853
    cellp->dagmc_ptr()->next_vol(surf_handle, curr_vol, new_vol);
44,310✔
854
  if (rval != moab::MB_SUCCESS)
44,310!
UNCOV
855
    return -1;
×
856

857
  return univp->cell_index(new_vol);
44,310✔
858
}
859

860
extern "C" int openmc_dagmc_universe_get_cell_ids(
10✔
861
  int32_t univ_id, int32_t* ids, size_t* n)
862
{
863
  // make sure the universe id is a DAGMC Universe
864
  const auto& univ = model::universes[model::universe_map[univ_id]];
10✔
865
  if (univ->geom_type() != GeometryType::DAG) {
10!
UNCOV
866
    set_errmsg(fmt::format("Universe {} is not a DAGMC Universe", univ_id));
×
UNCOV
867
    return OPENMC_E_INVALID_TYPE;
×
868
  }
869

870
  std::vector<int32_t> dag_cell_ids;
10✔
871
  for (const auto& cell_index : univ->cells_) {
57✔
872
    const auto& cell = model::cells[cell_index];
47✔
873
    if (cell->geom_type() == GeometryType::CSG) {
47!
UNCOV
874
      set_errmsg(fmt::format("Cell {} is not a DAGMC Cell", cell->id_));
×
UNCOV
875
      return OPENMC_E_INVALID_TYPE;
×
876
    }
877
    dag_cell_ids.push_back(cell->id_);
47✔
878
  }
879
  std::copy(dag_cell_ids.begin(), dag_cell_ids.end(), ids);
10✔
880
  *n = dag_cell_ids.size();
10✔
881
  return 0;
10✔
882
}
10✔
883

884
extern "C" int openmc_dagmc_universe_get_num_cells(int32_t univ_id, size_t* n)
10✔
885
{
886
  // make sure the universe id is a DAGMC Universe
887
  const auto& univ = model::universes[model::universe_map[univ_id]];
10✔
888
  if (univ->geom_type() != GeometryType::DAG) {
10!
UNCOV
889
    set_errmsg(fmt::format("Universe {} is not a DAGMC universe", univ_id));
×
UNCOV
890
    return OPENMC_E_INVALID_TYPE;
×
891
  }
892
  *n = univ->cells_.size();
10✔
893
  return 0;
10✔
894
}
895

896
} // namespace openmc
897

898
#else
899

900
namespace openmc {
901

902
extern "C" int openmc_dagmc_universe_get_cell_ids(
903
  int32_t univ_id, int32_t* ids, size_t* n)
904
{
905
  set_errmsg("OpenMC was not configured with DAGMC");
906
  return OPENMC_E_UNASSIGNED;
907
};
908

909
extern "C" int openmc_dagmc_universe_get_num_cells(int32_t univ_id, size_t* n)
910
{
911
  set_errmsg("OpenMC was not configured with DAGMC");
912
  return OPENMC_E_UNASSIGNED;
913
};
914

915
void read_dagmc_universes(pugi::xml_node node)
916
{
917
  if (check_for_node(node, "dagmc_universe")) {
6,664!
918
    fatal_error("DAGMC Universes are present but OpenMC was not configured "
919
                "with DAGMC");
920
  }
921
};
922

923
void check_dagmc_root_univ() {};
924

925
int32_t next_cell(int32_t surf, int32_t curr_cell, int32_t univ);
926

927
} // namespace openmc
928

929
#endif // OPENMC_DAGMC_ENABLED
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