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

openmc-dev / openmc / 27730902877

18 Jun 2026 01:33AM UTC coverage: 81.319% (+0.04%) from 81.281%
27730902877

push

github

web-flow
Replaced 'C0' by elemental carbon in `openmc/examples.py'. (#3974)

17591 of 25212 branches covered (69.77%)

Branch coverage included in aggregate %.

1 of 1 new or added line in 1 file covered. (100.0%)

205 existing lines in 21 files now uncovered.

58454 of 68303 relevant lines covered (85.58%)

46640950.49 hits per line

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

38.46
/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)
52
{
53
  MaterialOverrides material_overrides;
54
  TemperatureOverrides temperature_overrides;
55
  DensityOverrides density_overrides;
56

57
  if (check_for_node(node, "id")) {
58
    id_ = std::stoi(get_node_value(node, "id"));
59
  } else {
60
    fatal_error("Must specify the id of the DAGMC universe");
61
  }
62

63
  if (check_for_node(node, "filename")) {
64
    filename_ = get_node_value(node, "filename");
65
    if (!starts_with(filename_, "/")) {
66
      std::filesystem::path d(dir_name(settings::path_input));
67
      filename_ = (d / filename_).string();
68
    }
69
  } else {
70
    fatal_error("Must specify a file for the DAGMC universe");
71
  }
72

73
  adjust_geometry_ids_ = false;
74
  if (check_for_node(node, "auto_geom_ids")) {
75
    adjust_geometry_ids_ = get_node_value_bool(node, "auto_geom_ids");
76
  }
77

78
  adjust_material_ids_ = false;
79
  if (check_for_node(node, "auto_mat_ids")) {
80
    adjust_material_ids_ = get_node_value_bool(node, "auto_mat_ids");
81
  }
82

83
  // Get material assignment overrides from nested DAGMC cell elements.
84
  if (node.child("cell")) {
85
    for (pugi::xml_node cell_node : node.children("cell")) {
86
      if (!check_for_node(cell_node, "id")) {
87
        fatal_error(
88
          "Must specify id for each DAGMC cell override in <dagmc_universe>.");
89
      }
90

91
      int32_t cell_id = std::stoi(get_node_value(cell_node, "id"));
92

93
      if (check_for_node(cell_node, "region")) {
94
        fatal_error(fmt::format(
95
          "DAGMC cell {} override cannot specify a region.", cell_id));
96
      }
97
      if (check_for_node(cell_node, "fill")) {
98
        fatal_error(fmt::format(
99
          "DAGMC cell {} override currently only supports material fills.",
100
          cell_id));
101
      }
102
      if (check_for_node(cell_node, "universe")) {
103
        fatal_error(fmt::format(
104
          "DAGMC cell {} override cannot specify a universe.", cell_id));
105
      }
106
      if (check_for_node(cell_node, "translation") ||
107
          check_for_node(cell_node, "rotation")) {
108
        fatal_error(fmt::format(
109
          "DAGMC cell {} override does not support translation or rotation.",
110
          cell_id));
111
      }
112
      if (!check_for_node(cell_node, "material")) {
113
        fatal_error(fmt::format(
114
          "DAGMC cell {} override must specify material.", cell_id));
115
      }
116

117
      auto inserted = material_overrides.emplace(
118
        cell_id, parse_cell_material_xml(cell_node, cell_id));
119
      if (!inserted.second) {
120
        fatal_error(fmt::format(
121
          "Duplicate DAGMC cell override specified for cell {}", cell_id));
122
      }
123

124
      if (check_for_node(cell_node, "temperature")) {
125
        temperature_overrides.emplace(
126
          cell_id, parse_cell_temperature_xml(cell_node, cell_id));
127
      }
128

129
      if (check_for_node(cell_node, "density")) {
130
        density_overrides.emplace(
131
          cell_id, parse_cell_density_xml(cell_node, cell_id));
132
      }
133
    }
134
  } else if (check_for_node(node, "material_overrides")) {
135
    if (node.child("cell")) {
136
      fatal_error("DAGMCUniverse cannot specify both <material_overrides> and "
137
                  "<cell> sub-elements. Use <cell> elements only.");
138
    }
139
    warning("DAGMCUniverse <material_overrides> is deprecated. Use nested "
140
            "<cell> elements under <dagmc_universe> instead.");
141
    for (pugi::xml_node co :
142
      node.child("material_overrides").children("cell_override")) {
143
      int32_t cell_id = std::stoi(get_node_value(co, "id"));
144
      std::istringstream iss(co.child("material_ids").text().get());
145
      vector<int32_t> mats;
146
      for (std::string s; iss >> s;) {
147
        mats.push_back(s == "void" ? MATERIAL_VOID : std::stoi(s));
148
      }
149
      material_overrides.emplace(cell_id, mats);
150
    }
151
  }
152

153
  initialize(material_overrides, temperature_overrides, density_overrides);
154
}
155

156
DAGUniverse::DAGUniverse(
157
  const std::string& filename, bool auto_geom_ids, bool auto_mat_ids)
158
  : filename_(filename), adjust_geometry_ids_(auto_geom_ids),
159
    adjust_material_ids_(auto_mat_ids)
160
{
161
  set_id();
162
  initialize();
163
}
164

165
DAGUniverse::DAGUniverse(std::shared_ptr<moab::DagMC> dagmc_ptr,
166
  const std::string& filename, bool auto_geom_ids, bool auto_mat_ids)
167
  : dagmc_instance_(dagmc_ptr), filename_(filename),
168
    adjust_geometry_ids_(auto_geom_ids), adjust_material_ids_(auto_mat_ids)
169
{
170
  MaterialOverrides material_overrides;
171
  TemperatureOverrides temperature_overrides;
172
  DensityOverrides density_overrides;
173
  set_id();
174
  init_metadata();
175
  init_geometry(material_overrides, temperature_overrides, density_overrides);
176
}
177

178
void DAGUniverse::set_id()
179
{
180
  // determine the next universe id
181
  int32_t next_univ_id = 0;
182
  for (const auto& u : model::universes) {
183
    if (u->id_ > next_univ_id)
184
      next_univ_id = u->id_;
185
  }
186
  next_univ_id++;
187

188
  // set the universe id
189
  id_ = next_univ_id;
190
}
191

192
void DAGUniverse::initialize()
193
{
194
  MaterialOverrides material_overrides;
195
  TemperatureOverrides temperature_overrides;
196
  initialize(material_overrides, temperature_overrides);
197
}
198

199
void DAGUniverse::initialize(const MaterialOverrides& material_overrides,
200
  const TemperatureOverrides& temperature_overrides,
201
  const DensityOverrides& density_overrides)
202
{
203
#ifdef OPENMC_UWUW_ENABLED
204
  // read uwuw materials from the .h5m file if present
205
  read_uwuw_materials();
206
#endif
207

208
  init_dagmc();
209

210
  init_metadata();
211

212
  init_geometry(material_overrides, temperature_overrides, density_overrides);
213
}
214

215
void DAGUniverse::init_dagmc()
216
{
217

218
  // create a new DAGMC instance
219
  dagmc_instance_ = std::make_shared<moab::DagMC>();
220

221
  // load the DAGMC geometry
222
  if (!file_exists(filename_)) {
223
    fatal_error("Geometry DAGMC file '" + filename_ + "' does not exist!");
224
  }
225
  moab::ErrorCode rval = dagmc_instance_->load_file(filename_.c_str());
226
  MB_CHK_ERR_CONT(rval);
227

228
  // initialize acceleration data structures
229
  rval = dagmc_instance_->init_OBBTree();
230
  MB_CHK_ERR_CONT(rval);
231
}
232

233
void DAGUniverse::init_metadata()
234
{
235
  // parse model metadata
236
  dmd_ptr =
237
    std::make_unique<dagmcMetaData>(dagmc_instance_.get(), false, false);
238
  dmd_ptr->load_property_data();
239

240
  std::vector<std::string> keywords {"temp"};
241
  std::map<std::string, std::string> dum;
242
  std::string delimiters = ":/";
243
  moab::ErrorCode rval;
244
  rval = dagmc_instance_->parse_properties(keywords, dum, delimiters.c_str());
245
  MB_CHK_ERR_CONT(rval);
246
}
247

248
void DAGUniverse::init_geometry(const MaterialOverrides& material_overrides,
249
  const TemperatureOverrides& temperature_overrides,
250
  const DensityOverrides& density_overrides)
251
{
252
  moab::ErrorCode rval;
253

254
  // determine the next cell id
255
  int32_t next_cell_id = 0;
256
  for (const auto& c : model::cells) {
257
    if (c->id_ > next_cell_id)
258
      next_cell_id = c->id_;
259
  }
260
  cell_idx_offset_ = model::cells.size();
261
  next_cell_id++;
262

263
  // initialize cell objects
264
  int n_cells = dagmc_instance_->num_entities(3);
265
  moab::EntityHandle graveyard = 0;
266
  for (int i = 0; i < n_cells; i++) {
267
    moab::EntityHandle vol_handle = dagmc_instance_->entity_by_index(3, i + 1);
268

269
    // set cell ids using global IDs
270
    auto c = std::make_unique<DAGCell>(dagmc_instance_, i + 1);
271
    c->id_ = adjust_geometry_ids_
272
               ? next_cell_id++
273
               : dagmc_instance_->id_by_index(3, c->dag_index());
274
    c->universe_ = this->id_;
275
    c->fill_ = C_NONE; // no fill, single universe
276
    if (dagmc_instance_->is_implicit_complement(vol_handle)) {
277
      c->name_ = "implicit complement";
278
    }
279

280
    auto in_map = model::cell_map.find(c->id_);
281
    if (in_map == model::cell_map.end()) {
282
      model::cell_map[c->id_] = model::cells.size();
283
    } else {
284
      warning(fmt::format("DAGMC Cell IDs: {}", dagmc_ids_for_dim(3)));
285
      fatal_error(fmt::format(
286
        "DAGMC Universe {} contains a cell with ID {}, which "
287
        "already exists elsewhere in the geometry. Setting auto_geom_ids "
288
        "to True when initiating the DAGMC Universe may "
289
        "resolve this issue",
290
        this->id_, c->id_));
291
    }
292

293
    // --- Materials ---
294

295
    // determine volume material assignment
296
    std::string mat_str = dmd_ptr->get_volume_property("material", vol_handle);
297

298
    if (mat_str.empty()) {
299
      fatal_error(fmt::format("Volume {} has no material assignment.", c->id_));
300
    }
301

302
    to_lower(mat_str);
303

304
    if (mat_str == "graveyard") {
305
      graveyard = vol_handle;
306
    }
307
    if (material_overrides.count(c->id_)) {
308
      override_assign_material(c, material_overrides);
309
    } else if (mat_str == "void" || mat_str == "vacuum" ||
310
               mat_str == "graveyard") {
311
      c->material_.push_back(MATERIAL_VOID);
312
    } else if (uses_uwuw()) {
313
      uwuw_assign_material(vol_handle, c);
314
    } else {
315
      legacy_assign_material(mat_str, c);
316
    }
317

318
    if (temperature_overrides.count(c->id_)) {
319
      if (c->material_.empty() || c->material_[0] == MATERIAL_VOID) {
320
        fatal_error(fmt::format("DAGMC cell {} was specified with a "
321
                                "temperature but no non-void material.",
322
          c->id_));
323
      }
324

325
      c->sqrtkT_.clear();
326
      const auto& temp_overrides = temperature_overrides.at(c->id_);
327
      c->sqrtkT_.reserve(temp_overrides.size());
328
      for (auto T : temp_overrides) {
329
        c->sqrtkT_.push_back(std::sqrt(K_BOLTZMANN * T));
330
      }
331

332
      if (settings::verbosity >= 10) {
333
        std::stringstream override_values;
334
        for (size_t i = 0; i < temp_overrides.size(); ++i) {
335
          if (i > 0) {
336
            override_values << " ";
337
          }
338
          override_values << temp_overrides[i];
339
        }
340
        auto msg = fmt::format("Overriding DAGMC cell {} property "
341
                               "'temperature [K]' with value(s): {}",
342
          c->id_, override_values.str());
343
        write_message(msg, 10);
344
      }
345
    }
346

347
    if (density_overrides.count(c->id_)) {
348
      if (c->material_.empty() || c->material_[0] == MATERIAL_VOID) {
349
        fatal_error(fmt::format("DAGMC cell {} was specified with a density "
350
                                "but no non-void material.",
351
          c->id_));
352
      }
353
      // density_mult_ holds the true density until materials are finalized,
354
      // at which point it is converted to a proper multiplier (same as CSG).
355
      c->density_mult_ = density_overrides.at(c->id_);
356

357
      if (settings::verbosity >= 10) {
358
        const auto& dens = density_overrides.at(c->id_);
359
        std::stringstream override_values;
360
        for (size_t i = 0; i < dens.size(); ++i) {
361
          if (i > 0)
362
            override_values << " ";
363
          override_values << dens[i];
364
        }
365
        write_message(fmt::format("Overriding DAGMC cell {} property "
366
                                  "'density [g/cm³]' with value(s): {}",
367
                        c->id_, override_values.str()),
368
          10);
369
      }
370
    }
371

372
    // check for temperature assignment
373
    std::string temp_value;
374

375
    // no temperature if void
376
    if (c->material_[0] == MATERIAL_VOID) {
377
      model::cells.emplace_back(std::move(c));
378
      continue;
379
    }
380

381
    // assign cell temperature if not explicitly overridden
382
    if (c->sqrtkT_.empty()) {
383
      const auto& mat =
384
        model::materials[model::material_map.at(c->material_[0])];
385
      if (dagmc_instance_->has_prop(vol_handle, "temp")) {
386
        rval = dagmc_instance_->prop_value(vol_handle, "temp", temp_value);
387
        MB_CHK_ERR_CONT(rval);
388
        double temp = std::stod(temp_value);
389
        c->sqrtkT_.push_back(std::sqrt(K_BOLTZMANN * temp));
390
      } else if (mat->temperature() > 0.0) {
391
        c->sqrtkT_.push_back(std::sqrt(K_BOLTZMANN * mat->temperature()));
392
      } else {
393
        c->sqrtkT_.push_back(
394
          std::sqrt(K_BOLTZMANN * settings::temperature_default));
395
      }
396
    }
397

398
    model::cells.emplace_back(std::move(c));
399
  }
400

401
  // allocate the cell overlap count if necessary
402
  if (settings::check_overlaps) {
403
    model::overlap_check_count.resize(model::cells.size(), 0);
404
  }
405

406
  has_graveyard_ = graveyard;
407

408
  // determine the next surface id
409
  int32_t next_surf_id = 0;
410
  for (const auto& s : model::surfaces) {
411
    if (s->id_ > next_surf_id)
412
      next_surf_id = s->id_;
413
  }
414
  surf_idx_offset_ = model::surfaces.size();
415
  next_surf_id++;
416

417
  // initialize surface objects
418
  int n_surfaces = dagmc_instance_->num_entities(2);
419
  for (int i = 0; i < n_surfaces; i++) {
420
    moab::EntityHandle surf_handle = dagmc_instance_->entity_by_index(2, i + 1);
421

422
    // set cell ids using global IDs
423
    auto s = std::make_unique<DAGSurface>(dagmc_instance_, i + 1);
424
    s->id_ = adjust_geometry_ids_ ? next_surf_id++
425
                                  : dagmc_instance_->id_by_index(2, i + 1);
426

427
    // set surface source attribute if needed
428
    if (contains(settings::source_write_surf_id, s->id_) ||
429
        settings::source_write_surf_id.empty()) {
430
      s->surf_source_ = true;
431
    }
432

433
    // set BCs
434
    std::string bc_value =
435
      dmd_ptr->get_surface_property("boundary", surf_handle);
436
    to_lower(bc_value);
437
    if (bc_value.empty() || bc_value == "transmit" ||
438
        bc_value == "transmission") {
439
      // set to transmission by default (nullptr)
440
    } else if (bc_value == "vacuum") {
441
      s->bc_ = make_unique<VacuumBC>();
442
    } else if (bc_value == "reflective" || bc_value == "reflect" ||
443
               bc_value == "reflecting") {
444
      s->bc_ = make_unique<ReflectiveBC>();
445
    } else if (bc_value == "periodic") {
446
      fatal_error("Periodic boundary condition not supported in DAGMC.");
447
    } else {
448
      fatal_error(fmt::format("Unknown boundary condition \"{}\" specified "
449
                              "on surface {}",
450
        bc_value, s->id_));
451
    }
452

453
    // graveyard check
454
    moab::Range parent_vols;
455
    rval = dagmc_instance_->moab_instance()->get_parent_meshsets(
456
      surf_handle, parent_vols);
457
    MB_CHK_ERR_CONT(rval);
458

459
    // if this surface belongs to the graveyard
460
    if (graveyard && parent_vols.find(graveyard) != parent_vols.end()) {
461
      // set graveyard surface BC's to vacuum
462
      s->bc_ = make_unique<VacuumBC>();
463
    }
464

465
    // add to global array and map
466

467
    auto in_map = model::surface_map.find(s->id_);
468
    if (in_map == model::surface_map.end()) {
469
      model::surface_map[s->id_] = model::surfaces.size();
470
    } else {
471
      warning(fmt::format("DAGMC Surface IDs: {}", dagmc_ids_for_dim(2)));
472
      fatal_error(fmt::format("Surface ID {} exists in both Universe {} "
473
                              "and the CSG geometry.",
474
        s->id_, this->id_));
475
    }
476

477
    model::surfaces.emplace_back(std::move(s));
478
  } // end surface loop
479
}
480

481
int32_t DAGUniverse::cell_index(moab::EntityHandle vol) const
482
{
483
  // return the index of the volume in the DAGMC instance and then
484
  // adjust by the offset into the model cells for this DAGMC universe
485
  return dagmc_ptr()->index_by_handle(vol) + cell_idx_offset_;
486
}
487

488
int32_t DAGUniverse::surface_index(moab::EntityHandle surf) const
489
{
490
  // return the index of the surface in the DAGMC instance and then
491
  // adjust by the offset into the model cells for this DAGMC universe
492
  return dagmc_ptr()->index_by_handle(surf) + surf_idx_offset_;
493
}
494

495
std::string DAGUniverse::dagmc_ids_for_dim(int dim) const
496
{
497
  // generate a vector of ids
498
  std::vector<int> id_vec;
499
  int n_ents = dagmc_instance_->num_entities(dim);
500
  for (int i = 1; i <= n_ents; i++) {
501
    id_vec.push_back(dagmc_instance_->id_by_index(dim, i));
502
  }
503

504
  // sort the vector of ids
505
  std::sort(id_vec.begin(), id_vec.end());
506

507
  // generate a string representation of the ID range(s)
508
  std::stringstream out;
509

510
  int i = 0;
511
  int start_id = id_vec[0]; // initialize with first ID
512
  int stop_id;
513
  // loop over all cells in the universe
514
  while (i < n_ents) {
515

516
    stop_id = id_vec[i];
517

518
    // if the next ID is not in this contiguous set of IDS,
519
    // figure out how to write the string representing this set
520
    if (id_vec[i + 1] > stop_id + 1) {
521

522
      if (start_id != stop_id) {
523
        // there are several IDs in a row, print condensed version (i.e. 1-10,
524
        // 12-20)
525
        out << start_id << "-" << stop_id;
526
      } else {
527
        // only one ID in this contiguous block (i.e. 3, 5, 7, 9)
528
        out << start_id;
529
      }
530
      // insert a comma as long as we aren't in the last ID set
531
      if (i < n_ents - 1) {
532
        out << ", ";
533
      }
534

535
      // if we are at the end of a set, set the start ID to the first value
536
      // in the next set.
537
      start_id = id_vec[++i];
538
    }
539

540
    i++;
541
  }
542

543
  return out.str();
544
}
545

546
int32_t DAGUniverse::implicit_complement_idx() const
547
{
548
  moab::EntityHandle ic;
549
  moab::ErrorCode rval =
550
    dagmc_instance_->geom_tool()->get_implicit_complement(ic);
551
  MB_CHK_SET_ERR_CONT(rval, "Failed to get implicit complement");
552
  // off-by-one: DAGMC indices start at one
553
  return cell_idx_offset_ + dagmc_instance_->index_by_handle(ic) - 1;
554
}
555

556
bool DAGUniverse::find_cell(GeometryState& p) const
557
{
558
  // if the particle isn't in any of the other DagMC
559
  // cells, place it in the implicit complement
560
  bool found = Universe::find_cell(p);
561
  if (!found && model::universe_map[this->id_] != model::root_universe) {
562
    p.lowest_coord().cell() = implicit_complement_idx();
563
    found = true;
564
  }
565
  return found;
566
}
567

568
void DAGUniverse::to_hdf5(hid_t universes_group) const
569
{
570
  // Create a group for this universe.
571
  auto group = create_group(universes_group, fmt::format("universe {}", id_));
572

573
  // Write the geometry representation type.
574
  write_string(group, "geom_type", "dagmc", false);
575

576
  // Write other properties of the DAGMC Universe
577
  write_string(group, "filename", filename_, false);
578
  write_attribute(
579
    group, "auto_geom_ids", static_cast<int>(adjust_geometry_ids_));
580
  write_attribute(
581
    group, "auto_mat_ids", static_cast<int>(adjust_material_ids_));
582

583
  close_group(group);
584
}
585

586
bool DAGUniverse::uses_uwuw() const
587
{
588
#ifdef OPENMC_UWUW_ENABLED
589
  return uwuw_ && !uwuw_->material_library.empty();
590
#else
591
  return false;
592
#endif // OPENMC_UWUW_ENABLED
593
}
594

595
std::string DAGUniverse::get_uwuw_materials_xml() const
596
{
597
#ifdef OPENMC_UWUW_ENABLED
598
  if (!uses_uwuw()) {
599
    throw std::runtime_error("This DAGMC Universe does not use UWUW materials");
600
  }
601

602
  std::stringstream ss;
603
  // write header
604
  ss << "<?xml version=\"1.0\"?>\n";
605
  ss << "<materials>\n";
606
  const auto& mat_lib = uwuw_->material_library;
607
  // write materials
608
  for (auto mat : mat_lib) {
609
    ss << mat.second->openmc("atom");
610
  }
611
  // write footer
612
  ss << "</materials>";
613

614
  return ss.str();
615
#else
616
  fatal_error("DAGMC was not configured with UWUW.");
617
#endif // OPENMC_UWUW_ENABLED
618
}
619

620
void DAGUniverse::write_uwuw_materials_xml(const std::string& outfile) const
621
{
622
#ifdef OPENMC_UWUW_ENABLED
623
  if (!uses_uwuw()) {
624
    throw std::runtime_error(
625
      "This DAGMC universe does not use UWUW materials.");
626
  }
627

628
  std::string xml_str = get_uwuw_materials_xml();
629
  // if there is a material library in the file
630
  std::ofstream mats_xml(outfile);
631
  mats_xml << xml_str;
632
  mats_xml.close();
633
#else
634
  fatal_error("DAGMC was not configured with UWUW.");
635
#endif // OPENMC_UWUW_ENABLED
636
}
637

638
void DAGUniverse::legacy_assign_material(
639
  std::string mat_string, std::unique_ptr<DAGCell>& c) const
640
{
641
  bool mat_found_by_name = false;
642
  // attempt to find a material with a matching name
643
  to_lower(mat_string);
644
  for (const auto& m : model::materials) {
645
    std::string m_name = m->name();
646
    to_lower(m_name);
647
    if (mat_string == m_name) {
648
      // assign the material with that name
649
      if (!mat_found_by_name) {
650
        mat_found_by_name = true;
651
        c->material_.push_back(m->id_);
652
        // report error if more than one material is found
653
      } else {
654
        fatal_error(fmt::format(
655
          "More than one material found with name '{}'. Please ensure "
656
          "materials "
657
          "have unique names if using this property to assign materials.",
658
          mat_string));
659
      }
660
    }
661
  }
662

663
  // if no material was set using a name, assign by id
664
  if (!mat_found_by_name) {
665
    bool found_by_id = true;
666
    try {
667
      auto id = std::stoi(mat_string);
668
      if (model::material_map.find(id) == model::material_map.end())
669
        found_by_id = false;
670
      c->material_.emplace_back(id);
671
    } catch (const std::invalid_argument&) {
672
      found_by_id = false;
673
    }
674

675
    // report failure for failed int conversion or missing material
676
    if (!found_by_id)
677
      fatal_error(
678
        fmt::format("Material with name/ID '{}' not found for volume (cell) {}",
679
          mat_string, c->id_));
680
  }
681

682
  if (settings::verbosity >= 10) {
683
    const auto& m = model::materials[model::material_map.at(c->material_[0])];
684
    std::stringstream msg;
685
    msg << "DAGMC material " << mat_string << " was assigned";
686
    if (mat_found_by_name) {
687
      msg << " using material name: " << m->name_;
688
    } else {
689
      msg << " using material id: " << m->id_;
690
    }
691
    write_message(msg.str(), 10);
692
  }
693
}
694

695
void DAGUniverse::read_uwuw_materials()
696
{
697
#ifdef OPENMC_UWUW_ENABLED
698
  // If no filename was provided, don't read UWUW materials
699
  if (filename_ == "")
700
    return;
701

702
  uwuw_ = std::make_shared<UWUW>(filename_.c_str());
703

704
  if (!uses_uwuw())
705
    return;
706

707
  // Notify user if UWUW materials are going to be used
708
  write_message("Found UWUW Materials in the DAGMC geometry file.", 6);
709

710
  // if we're using automatic IDs, update the UWUW material metadata
711
  if (adjust_material_ids_) {
712
    int32_t next_material_id = 0;
713
    for (const auto& m : model::materials) {
714
      next_material_id = std::max(m->id_, next_material_id);
715
    }
716
    next_material_id++;
717

718
    for (auto& mat : uwuw_->material_library) {
719
      mat.second->metadata["mat_number"] = next_material_id++;
720
    }
721
  }
722

723
  std::string mat_xml_string = get_uwuw_materials_xml();
724

725
  // create a pugi XML document from this string
726
  pugi::xml_document doc;
727
  auto result = doc.load_string(mat_xml_string.c_str());
728
  if (!result) {
729
    fatal_error("Error processing XML created using DAGMC UWUW materials.");
730
  }
731
  pugi::xml_node root = doc.document_element();
732
  for (pugi::xml_node material_node : root.children("material")) {
733
    model::materials.push_back(std::make_unique<Material>(material_node));
734
  }
735
#else
736
  fatal_error("DAGMC was not configured with UWUW.");
737
#endif // OPENMC_UWUW_ENABLED
738
}
739

740
void DAGUniverse::uwuw_assign_material(
741
  moab::EntityHandle vol_handle, std::unique_ptr<DAGCell>& c) const
742
{
743
#ifdef OPENMC_UWUW_ENABLED
744
  // lookup material in uwuw if present
745
  std::string uwuw_mat = dmd_ptr->volume_material_property_data_eh[vol_handle];
746
  if (uwuw_->material_library.count(uwuw_mat) != 0) {
747
    // Note: material numbers are set by UWUW
748
    int mat_number = uwuw_->material_library.get_material(uwuw_mat)
749
                       .metadata["mat_number"]
750
                       .asInt();
751
    c->material_.push_back(mat_number);
752
  } else {
753
    fatal_error(fmt::format("Material with value '{}' not found in the "
754
                            "UWUW material library",
755
      uwuw_mat));
756
  }
757
#else
758
  fatal_error("DAGMC was not configured with UWUW.");
759
#endif // OPENMC_UWUW_ENABLED
760
}
761

762
void DAGUniverse::override_assign_material(std::unique_ptr<DAGCell>& c,
763
  const MaterialOverrides& material_overrides) const
764
{
765
  // if Cell ID matches an override key, use it to override the material
766
  // assignment else if UWUW is used, get the material assignment from the DAGMC
767
  // metadata
768
  // Notify User that an override is being applied on a DAGMCCell
769
  write_message(fmt::format("Applying override for DAGMCCell {}", c->id_), 8);
770

771
  const auto& mat_overrides = material_overrides.at(c->id_);
772
  if (settings::verbosity >= 10) {
773
    std::stringstream override_values;
774
    for (size_t i = 0; i < mat_overrides.size(); ++i) {
775
      if (i > 0) {
776
        override_values << " ";
777
      }
778
      if (mat_overrides[i] == MATERIAL_VOID) {
779
        override_values << "void";
780
      } else {
781
        override_values << mat_overrides[i];
782
      }
783
    }
784
    auto msg = fmt::format("Overriding DAGMC cell {} property 'material' "
785
                           "with value(s): {}",
786
      c->id_, override_values.str());
787
    write_message(msg, 10);
788
  }
789

790
  // Override the material assignment for each cell instance using the legacy
791
  // assignement
792
  for (auto mat_id : mat_overrides) {
793
    if (mat_id != MATERIAL_VOID &&
794
        model::material_map.find(mat_id) == model::material_map.end()) {
795
      fatal_error(fmt::format(
796
        "Material with ID '{}' not found for DAGMC cell {}", mat_id, c->id_));
797
    }
798
    c->material_.push_back(mat_id);
799
  }
800
}
801

802
//==============================================================================
803
// DAGMC Cell implementation
804
//==============================================================================
805

806
DAGCell::DAGCell(std::shared_ptr<moab::DagMC> dag_ptr, int32_t dag_idx)
807
  : Cell {}, dagmc_ptr_(dag_ptr), dag_index_(dag_idx) {};
808

809
std::pair<double, int32_t> DAGCell::distance(
810
  Position r, Direction u, int32_t on_surface, GeometryState* p) const
811
{
812
  // if we've changed direction or we're not on a surface,
813
  // reset the history and update last direction
814
  if (u != p->last_dir()) {
815
    p->last_dir() = u;
816
    p->history().reset();
817
  }
818
  if (on_surface == SURFACE_NONE) {
819
    p->history().reset();
820
  }
821

822
  const auto& univ = model::universes[p->lowest_coord().universe()];
823

824
  DAGUniverse* dag_univ = static_cast<DAGUniverse*>(univ.get());
825
  if (!dag_univ)
826
    fatal_error("DAGMC call made for particle in a non-DAGMC universe");
827

828
  // initialize to lost particle conditions
829
  int surf_idx = -1;
830
  double dist = INFINITY;
831

832
  moab::EntityHandle vol = dagmc_ptr_->entity_by_index(3, dag_index_);
833
  moab::EntityHandle hit_surf;
834

835
  // create the ray
836
  double pnt[3] = {r.x, r.y, r.z};
837
  double dir[3] = {u.x, u.y, u.z};
838
  MB_CHK_ERR_CONT(
839
    dagmc_ptr_->ray_fire(vol, pnt, dir, hit_surf, dist, &p->history()));
840
  if (hit_surf != 0) {
841
    surf_idx =
842
      dag_univ->surf_idx_offset_ + dagmc_ptr_->index_by_handle(hit_surf);
843
  } else if (!dagmc_ptr_->is_implicit_complement(vol) ||
844
             is_root_universe(dag_univ->id_)) {
845
    // surface boundary conditions are ignored for projection plotting, meaning
846
    // that the particle may move through the graveyard (bounding) volume and
847
    // into the implicit complement on the other side where no intersection will
848
    // be found. Treating this as a lost particle is problematic when plotting.
849
    // Instead, the infinite distance and invalid surface index are returned.
850
    if (settings::run_mode == RunMode::PLOTTING)
851
      return {INFTY, -1};
852

853
    // the particle should be marked as lost immediately if an intersection
854
    // isn't found in a volume that is not the implicit complement. In the case
855
    // that the DAGMC model is the root universe of the geometry, even a missing
856
    // intersection in the implicit complement should trigger this condition.
857
    std::string material_id =
858
      p->material() == MATERIAL_VOID
859
        ? "-1 (VOID)"
860
        : std::to_string(model::materials[p->material()]->id());
861
    p->mark_as_lost(fmt::format(
862
      "No intersection found with DAGMC cell {}, filled with material {}", id_,
863
      material_id));
864
  }
865

866
  return {dist, surf_idx};
867
}
868

869
bool DAGCell::contains(Position r, Direction u, int32_t on_surface) const
870
{
871
  moab::ErrorCode rval;
872
  moab::EntityHandle vol = dagmc_ptr_->entity_by_index(3, dag_index_);
873

874
  int result = 0;
875
  double pnt[3] = {r.x, r.y, r.z};
876
  double dir[3] = {u.x, u.y, u.z};
877
  rval = dagmc_ptr_->point_in_volume(vol, pnt, result, dir);
878
  MB_CHK_ERR_CONT(rval);
879
  return result;
880
}
881

882
moab::EntityHandle DAGCell::mesh_handle() const
883
{
884
  return dagmc_ptr()->entity_by_index(3, dag_index());
885
}
886

887
void DAGCell::to_hdf5_inner(hid_t group_id) const
888
{
889
  write_string(group_id, "geom_type", "dagmc", false);
890
}
891

892
BoundingBox DAGCell::bounding_box() const
893
{
894
  moab::ErrorCode rval;
895
  moab::EntityHandle vol = dagmc_ptr_->entity_by_index(3, dag_index_);
896
  double min[3], max[3];
897
  rval = dagmc_ptr_->getobb(vol, min, max);
898
  MB_CHK_ERR_CONT(rval);
899
  return {{min[0], min[1], min[2]}, {max[0], max[1], max[2]}};
900
}
901

902
//==============================================================================
903
// DAGSurface implementation
904
//==============================================================================
905

906
DAGSurface::DAGSurface(std::shared_ptr<moab::DagMC> dag_ptr, int32_t dag_idx)
907
  : Surface {}, dagmc_ptr_(dag_ptr), dag_index_(dag_idx)
908
{} // empty constructor
909

910
moab::EntityHandle DAGSurface::mesh_handle() const
911
{
912
  return dagmc_ptr()->entity_by_index(2, dag_index());
913
}
914

915
double DAGSurface::evaluate(Position r) const
916
{
917
  return 0.0;
918
}
919

920
double DAGSurface::distance(Position r, Direction u, bool coincident) const
921
{
922
  moab::ErrorCode rval;
923
  moab::EntityHandle surf = dagmc_ptr_->entity_by_index(2, dag_index_);
924
  moab::EntityHandle hit_surf;
925
  double dist;
926
  double pnt[3] = {r.x, r.y, r.z};
927
  double dir[3] = {u.x, u.y, u.z};
928
  rval = dagmc_ptr_->ray_fire(surf, pnt, dir, hit_surf, dist, NULL, 0, 0);
929
  MB_CHK_ERR_CONT(rval);
930
  if (dist < 0.0)
931
    dist = INFTY;
932
  return dist;
933
}
934

935
Direction DAGSurface::normal(Position r) const
936
{
937
  moab::ErrorCode rval;
938
  moab::EntityHandle surf = dagmc_ptr_->entity_by_index(2, dag_index_);
939
  double pnt[3] = {r.x, r.y, r.z};
940
  double dir[3];
941
  rval = dagmc_ptr_->get_angle(surf, pnt, dir);
942
  MB_CHK_ERR_CONT(rval);
943
  return dir;
944
}
945

946
Direction DAGSurface::reflect(Position r, Direction u, GeometryState* p) const
947
{
948
  assert(p);
949
  double pnt[3] = {r.x, r.y, r.z};
950
  double dir[3];
951
  moab::ErrorCode rval =
952
    dagmc_ptr_->get_angle(mesh_handle(), pnt, dir, &p->history());
953
  MB_CHK_ERR_CONT(rval);
954
  return u.reflect(dir);
955
}
956

957
//==============================================================================
958
// Non-member functions
959
//==============================================================================
960

961
void read_dagmc_universes(pugi::xml_node node)
962
{
963
  for (pugi::xml_node dag_node : node.children("dagmc_universe")) {
964
    model::universes.push_back(std::make_unique<DAGUniverse>(dag_node));
965
    model::universe_map[model::universes.back()->id_] =
966
      model::universes.size() - 1;
967
  }
968
}
969

970
void check_dagmc_root_univ()
971
{
972
  const auto& ru = model::universes[model::root_universe];
973
  if (ru->geom_type() == GeometryType::DAG) {
974
    // if the root universe contains DAGMC geometry, warn the user
975
    // if it does not contain a graveyard volume
976
    auto dag_univ = dynamic_cast<DAGUniverse*>(ru.get());
977
    if (dag_univ && !dag_univ->has_graveyard()) {
978
      warning(
979
        "No graveyard volume found in the DagMC model. "
980
        "This may result in lost particles and rapid simulation failure.");
981
    }
982
  }
983
}
984

985
int32_t next_cell(int32_t surf, int32_t curr_cell, int32_t univ)
986
{
987
  auto surfp = dynamic_cast<DAGSurface*>(model::surfaces[surf].get());
988
  auto cellp = dynamic_cast<DAGCell*>(model::cells[curr_cell].get());
989
  auto univp = static_cast<DAGUniverse*>(model::universes[univ].get());
990

991
  moab::EntityHandle surf_handle = surfp->mesh_handle();
992
  moab::EntityHandle curr_vol = cellp->mesh_handle();
993

994
  moab::EntityHandle new_vol;
995
  moab::ErrorCode rval =
996
    cellp->dagmc_ptr()->next_vol(surf_handle, curr_vol, new_vol);
997
  if (rval != moab::MB_SUCCESS)
998
    return -1;
999

1000
  return univp->cell_index(new_vol);
1001
}
1002

1003
extern "C" int openmc_dagmc_universe_get_cell_ids(
1004
  int32_t univ_id, int32_t* ids, size_t* n)
1005
{
1006
  // make sure the universe id is a DAGMC Universe
1007
  const auto& univ = model::universes[model::universe_map[univ_id]];
1008
  if (univ->geom_type() != GeometryType::DAG) {
1009
    set_errmsg(fmt::format("Universe {} is not a DAGMC Universe", univ_id));
1010
    return OPENMC_E_INVALID_TYPE;
1011
  }
1012

1013
  std::vector<int32_t> dag_cell_ids;
1014
  for (const auto& cell_index : univ->cells_) {
1015
    const auto& cell = model::cells[cell_index];
1016
    if (cell->geom_type() == GeometryType::CSG) {
1017
      set_errmsg(fmt::format("Cell {} is not a DAGMC Cell", cell->id_));
1018
      return OPENMC_E_INVALID_TYPE;
1019
    }
1020
    dag_cell_ids.push_back(cell->id_);
1021
  }
1022
  std::copy(dag_cell_ids.begin(), dag_cell_ids.end(), ids);
1023
  *n = dag_cell_ids.size();
1024
  return 0;
1025
}
1026

1027
extern "C" int openmc_dagmc_universe_get_num_cells(int32_t univ_id, size_t* n)
1028
{
1029
  // make sure the universe id is a DAGMC Universe
1030
  const auto& univ = model::universes[model::universe_map[univ_id]];
1031
  if (univ->geom_type() != GeometryType::DAG) {
1032
    set_errmsg(fmt::format("Universe {} is not a DAGMC universe", univ_id));
1033
    return OPENMC_E_INVALID_TYPE;
1034
  }
1035
  *n = univ->cells_.size();
1036
  return 0;
1037
}
1038

1039
} // namespace openmc
1040

1041
#else
1042

1043
namespace openmc {
1044

UNCOV
1045
extern "C" int openmc_dagmc_universe_get_cell_ids(
×
1046
  int32_t univ_id, int32_t* ids, size_t* n)
1047
{
UNCOV
1048
  set_errmsg("OpenMC was not configured with DAGMC");
×
UNCOV
1049
  return OPENMC_E_UNASSIGNED;
×
1050
};
1051

UNCOV
1052
extern "C" int openmc_dagmc_universe_get_num_cells(int32_t univ_id, size_t* n)
×
1053
{
UNCOV
1054
  set_errmsg("OpenMC was not configured with DAGMC");
×
UNCOV
1055
  return OPENMC_E_UNASSIGNED;
×
1056
};
1057

1058
void read_dagmc_universes(pugi::xml_node node)
7,718✔
1059
{
1060
  if (check_for_node(node, "dagmc_universe")) {
7,718!
UNCOV
1061
    fatal_error("DAGMC Universes are present but OpenMC was not configured "
×
1062
                "with DAGMC");
1063
  }
1064
};
7,718✔
1065

1066
void check_dagmc_root_univ() {};
7,718✔
1067

1068
int32_t next_cell(int32_t surf, int32_t curr_cell, int32_t univ);
1069

1070
} // namespace openmc
1071

1072
#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

© 2026 Coveralls, Inc