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

openmc-dev / openmc / 26248631044

21 May 2026 07:35PM UTC coverage: 81.174% (-0.2%) from 81.374%
26248631044

Pull #3653

github

web-flow
Merge 5828c2742 into 7d09a1260
Pull Request #3653: Modernize CMake packaging

17987 of 26078 branches covered (68.97%)

Branch coverage included in aggregate %.

59151 of 68950 relevant lines covered (85.79%)

48415572.71 hits per line

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

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

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

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

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

78
  adjust_material_ids_ = false;
43✔
79
  if (check_for_node(node, "auto_mat_ids")) {
43!
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")) {
43✔
85
    for (pugi::xml_node cell_node : node.children("cell")) {
12✔
86
      if (!check_for_node(cell_node, "id")) {
9!
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"));
18✔
92

93
      if (check_for_node(cell_node, "region")) {
9!
94
        fatal_error(fmt::format(
×
95
          "DAGMC cell {} override cannot specify a region.", cell_id));
96
      }
97
      if (check_for_node(cell_node, "fill")) {
9!
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")) {
9!
103
        fatal_error(fmt::format(
×
104
          "DAGMC cell {} override cannot specify a universe.", cell_id));
105
      }
106
      if (check_for_node(cell_node, "translation") ||
18!
107
          check_for_node(cell_node, "rotation")) {
9✔
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")) {
9!
113
        fatal_error(fmt::format(
×
114
          "DAGMC cell {} override must specify material.", cell_id));
115
      }
116

117
      auto inserted = material_overrides.emplace(
9✔
118
        cell_id, parse_cell_material_xml(cell_node, cell_id));
9✔
119
      if (!inserted.second) {
9!
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")) {
9!
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")) {
9!
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")) {
40!
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);
43✔
154
}
123!
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,
2✔
166
  const std::string& filename, bool auto_geom_ids, bool auto_mat_ids)
2✔
167
  : dagmc_instance_(dagmc_ptr), filename_(filename),
4!
168
    adjust_geometry_ids_(auto_geom_ids), adjust_material_ids_(auto_mat_ids)
4!
169
{
170
  MaterialOverrides material_overrides;
2✔
171
  TemperatureOverrides temperature_overrides;
2✔
172
  DensityOverrides density_overrides;
2✔
173
  set_id();
2✔
174
  init_metadata();
2✔
175
  init_geometry(material_overrides, temperature_overrides, density_overrides);
2✔
176
}
6!
177

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

188
  // set the universe id
189
  id_ = next_univ_id;
2✔
190
}
2✔
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,
43✔
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();
43✔
206
#endif
207

208
  init_dagmc();
43✔
209

210
  init_metadata();
43✔
211

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

215
void DAGUniverse::init_dagmc()
43✔
216
{
217

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

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

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

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

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

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

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

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

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

280
    auto in_map = model::cell_map.find(c->id_);
205!
281
    if (in_map == model::cell_map.end()) {
205!
282
      model::cell_map[c->id_] = model::cells.size();
205✔
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);
205✔
297

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

302
    to_lower(mat_str);
205✔
303

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

318
    if (temperature_overrides.count(c->id_)) {
203!
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_)) {
203!
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;
203✔
374

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

381
    // assign cell temperature if not explicitly overridden
382
    if (c->sqrtkT_.empty()) {
122!
383
      const auto& mat =
122✔
384
        model::materials[model::material_map.at(c->material_[0])];
122✔
385
      if (dagmc_instance_->has_prop(vol_handle, "temp")) {
122✔
386
        rval = dagmc_instance_->prop_value(vol_handle, "temp", temp_value);
46✔
387
        MB_CHK_ERR_CONT(rval);
46!
388
        double temp = std::stod(temp_value);
46✔
389
        c->sqrtkT_.push_back(std::sqrt(K_BOLTZMANN * temp));
46✔
390
      } else if (mat->temperature() > 0.0) {
76!
391
        c->sqrtkT_.push_back(std::sqrt(K_BOLTZMANN * mat->temperature()));
76✔
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));
122✔
399
  }
203✔
400

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

406
  has_graveyard_ = graveyard;
43✔
407

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

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

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

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

433
    // set BCs
434
    std::string bc_value =
827✔
435
      dmd_ptr->get_surface_property("boundary", surf_handle);
827✔
436
    to_lower(bc_value);
827✔
437
    if (bc_value.empty() || bc_value == "transmit" ||
845!
438
        bc_value == "transmission") {
18!
439
      // set to transmission by default (nullptr)
440
    } else if (bc_value == "vacuum") {
18✔
441
      s->bc_ = make_unique<VacuumBC>();
6✔
442
    } else if (bc_value == "reflective" || bc_value == "reflect" ||
24!
443
               bc_value == "reflecting") {
12!
444
      s->bc_ = make_unique<ReflectiveBC>();
12✔
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;
827✔
455
    rval = dagmc_instance_->moab_instance()->get_parent_meshsets(
827✔
456
      surf_handle, parent_vols);
457
    MB_CHK_ERR_CONT(rval);
827!
458

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

465
    // add to global array and map
466

467
    auto in_map = model::surface_map.find(s->id_);
827!
468
    if (in_map == model::surface_map.end()) {
827!
469
      model::surface_map[s->id_] = model::surfaces.size();
827✔
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));
827✔
478
  } // end surface loop
827✔
479
}
43✔
480

481
int32_t DAGUniverse::cell_index(moab::EntityHandle vol) const
46,742✔
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_;
46,742✔
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
25,622✔
547
{
548
  moab::EntityHandle ic;
25,622✔
549
  moab::ErrorCode rval =
25,622✔
550
    dagmc_instance_->geom_tool()->get_implicit_complement(ic);
51,244!
551
  MB_CHK_SET_ERR_CONT(rval, "Failed to get implicit complement");
25,622!
552
  // off-by-one: DAGMC indices start at one
553
  return cell_idx_offset_ + dagmc_instance_->index_by_handle(ic) - 1;
25,622✔
554
}
555

556
bool DAGUniverse::find_cell(GeometryState& p) const
5,143,714✔
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);
5,143,714✔
561
  if (!found && model::universe_map[this->id_] != model::root_universe) {
5,143,714!
562
    p.lowest_coord().cell() = implicit_complement_idx();
25,411✔
563
    found = true;
25,411✔
564
  }
565
  return found;
5,143,714✔
566
}
567

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

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

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

583
  close_group(group);
32✔
584
}
32✔
585

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

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

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

614
  return ss.str();
8✔
615
#else
616
  fatal_error("DAGMC was not configured with UWUW.");
617
#endif // OPENMC_UWUW_ENABLED
618
}
4✔
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(
107✔
639
  std::string mat_string, std::unique_ptr<DAGCell>& c) const
640
{
641
  bool mat_found_by_name = false;
107✔
642
  // attempt to find a material with a matching name
643
  to_lower(mat_string);
107✔
644
  for (const auto& m : model::materials) {
329✔
645
    std::string m_name = m->name();
222✔
646
    to_lower(m_name);
222✔
647
    if (mat_string == m_name) {
222✔
648
      // assign the material with that name
649
      if (!mat_found_by_name) {
85!
650
        mat_found_by_name = true;
85✔
651
        c->material_.push_back(m->id_);
85✔
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
  }
222✔
662

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

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

682
  if (settings::verbosity >= 10) {
105!
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
}
105✔
694

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

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

704
  if (!uses_uwuw())
43✔
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);
4✔
709

710
  // if we're using automatic IDs, update the UWUW material metadata
711
  if (adjust_material_ids_) {
4!
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();
4✔
724

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

740
void DAGUniverse::uwuw_assign_material(
12✔
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];
12✔
746
  if (uwuw_->material_library.count(uwuw_mat) != 0) {
24!
747
    // Note: material numbers are set by UWUW
748
    int mat_number = uwuw_->material_library.get_material(uwuw_mat)
12✔
749
                       .metadata["mat_number"]
12✔
750
                       .asInt();
12✔
751
    c->material_.push_back(mat_number);
12✔
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
}
12✔
761

762
void DAGUniverse::override_assign_material(std::unique_ptr<DAGCell>& c,
9✔
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);
9✔
770

771
  const auto& mat_overrides = material_overrides.at(c->id_);
9✔
772
  if (settings::verbosity >= 10) {
9!
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) {
27✔
793
    if (mat_id != MATERIAL_VOID &&
18!
794
        model::material_map.find(mat_id) == model::material_map.end()) {
14!
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);
18✔
799
  }
800
}
9✔
801

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

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

809
std::pair<double, int32_t> DAGCell::distance(
354,886✔
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()) {
354,886✔
815
    p->last_dir() = u;
306,767✔
816
    p->history().reset();
306,767✔
817
  }
818
  if (on_surface == SURFACE_NONE) {
354,886✔
819
    p->history().reset();
286,312✔
820
  }
821

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

824
  DAGUniverse* dag_univ = static_cast<DAGUniverse*>(univ.get());
354,886!
825
  if (!dag_univ)
354,886!
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;
354,886✔
830
  double dist = INFINITY;
354,886✔
831

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

835
  // create the ray
836
  double pnt[3] = {r.x, r.y, r.z};
354,886✔
837
  double dir[3] = {u.x, u.y, u.z};
354,886✔
838
  MB_CHK_ERR_CONT(
354,886!
839
    dagmc_ptr_->ray_fire(vol, pnt, dir, hit_surf, dist, &p->history()));
840
  if (hit_surf != 0) {
354,886✔
841
    surf_idx =
671,926✔
842
      dag_univ->surf_idx_offset_ + dagmc_ptr_->index_by_handle(hit_surf);
335,963✔
843
  } else if (!dagmc_ptr_->is_implicit_complement(vol) ||
37,846!
844
             is_root_universe(dag_univ->id_)) {
18,923✔
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};
354,886✔
867
}
868

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

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

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

887
void DAGCell::to_hdf5_inner(hid_t group_id) const
149✔
888
{
889
  write_string(group_id, "geom_type", "dagmc", false);
149✔
890
}
149✔
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)
827✔
907
  : Surface {}, dagmc_ptr_(dag_ptr), dag_index_(dag_idx)
827!
908
{} // empty constructor
827✔
909

910
moab::EntityHandle DAGSurface::mesh_handle() const
49,500✔
911
{
912
  return dagmc_ptr()->entity_by_index(2, dag_index());
49,500✔
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
2,758✔
947
{
948
  assert(p);
2,758!
949
  double pnt[3] = {r.x, r.y, r.z};
2,758✔
950
  double dir[3];
2,758✔
951
  moab::ErrorCode rval =
2,758✔
952
    dagmc_ptr_->get_angle(mesh_handle(), pnt, dir, &p->history());
2,758✔
953
  MB_CHK_ERR_CONT(rval);
2,758!
954
  return u.reflect(dir);
2,758✔
955
}
956

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

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

970
void check_dagmc_root_univ()
989✔
971
{
972
  const auto& ru = model::universes[model::root_universe];
989✔
973
  if (ru->geom_type() == GeometryType::DAG) {
989✔
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());
23!
977
    if (dag_univ && !dag_univ->has_graveyard()) {
23!
978
      warning(
2✔
979
        "No graveyard volume found in the DagMC model. "
980
        "This may result in lost particles and rapid simulation failure.");
981
    }
982
  }
983
}
989✔
984

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

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

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

1000
  return univp->cell_index(new_vol);
46,742✔
1001
}
1002

1003
extern "C" int openmc_dagmc_universe_get_cell_ids(
8✔
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]];
8✔
1008
  if (univ->geom_type() != GeometryType::DAG) {
8!
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;
8✔
1014
  for (const auto& cell_index : univ->cells_) {
42✔
1015
    const auto& cell = model::cells[cell_index];
34✔
1016
    if (cell->geom_type() == GeometryType::CSG) {
34!
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_);
34✔
1021
  }
1022
  std::copy(dag_cell_ids.begin(), dag_cell_ids.end(), ids);
8✔
1023
  *n = dag_cell_ids.size();
8✔
1024
  return 0;
8✔
1025
}
8✔
1026

1027
extern "C" int openmc_dagmc_universe_get_num_cells(int32_t univ_id, size_t* n)
8✔
1028
{
1029
  // make sure the universe id is a DAGMC Universe
1030
  const auto& univ = model::universes[model::universe_map[univ_id]];
8✔
1031
  if (univ->geom_type() != GeometryType::DAG) {
8!
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();
8✔
1036
  return 0;
8✔
1037
}
1038

1039
} // namespace openmc
1040

1041
#else
1042

1043
namespace openmc {
1044

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

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

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

1066
void check_dagmc_root_univ() {};
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