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

openmc-dev / openmc / 19355288173

14 Nov 2025 05:27AM UTC coverage: 81.961% (-0.09%) from 82.052%
19355288173

push

github

web-flow
Fix typo in DAGMC lost particle test (#3634)

16957 of 23514 branches covered (72.11%)

Branch coverage included in aggregate %.

54872 of 64124 relevant lines covered (85.57%)

42111683.55 hits per line

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

63.72
/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)
46✔
52
{
53
  if (check_for_node(node, "id")) {
46!
54
    id_ = std::stoi(get_node_value(node, "id"));
46✔
55
  } else {
56
    fatal_error("Must specify the id of the DAGMC universe");
×
57
  }
58

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

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

74
  adjust_material_ids_ = false;
46✔
75
  if (check_for_node(node, "auto_mat_ids")) {
46!
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")) {
46✔
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();
46✔
97
}
44✔
98

99
DAGUniverse::DAGUniverse(
×
100
  const std::string& filename, bool auto_geom_ids, bool auto_mat_ids)
×
101
  : filename_(filename), adjust_geometry_ids_(auto_geom_ids),
×
102
    adjust_material_ids_(auto_mat_ids)
×
103
{
104
  set_id();
×
105
  initialize();
×
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!
123
    if (u->id_ > next_univ_id)
×
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()
46✔
133
{
134
#ifdef OPENMC_UWUW_ENABLED
135
  // read uwuw materials from the .h5m file if present
136
  read_uwuw_materials();
46✔
137
#endif
138

139
  init_dagmc();
46✔
140

141
  init_metadata();
46✔
142

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

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

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

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

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

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

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

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

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

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

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

206
    auto in_map = model::cell_map.find(c->id_);
226✔
207
    if (in_map == model::cell_map.end()) {
226!
208
      model::cell_map[c->id_] = model::cells.size();
226✔
209
    } else {
210
      warning(fmt::format("DAGMC Cell IDs: {}", dagmc_ids_for_dim(3)));
×
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",
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);
452✔
223

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

228
    to_lower(mat_str);
226✔
229

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

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

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

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

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

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

277
  has_graveyard_ = graveyard;
46✔
278

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

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

293
    // set cell ids using global IDs
294
    auto s = std::make_unique<DAGSurface>(dagmc_instance_, i + 1);
918✔
295
    s->id_ = adjust_geometry_ids_ ? next_surf_id++
1,473✔
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,832✔
300
        settings::source_write_surf_id.empty()) {
914✔
301
      s->surf_source_ = true;
796✔
302
    }
303

304
    // set BCs
305
    std::string bc_value =
306
      dmd_ptr->get_surface_property("boundary", surf_handle);
1,836✔
307
    to_lower(bc_value);
918✔
308
    if (bc_value.empty() || bc_value == "transmit" ||
936!
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✔
316
    } else if (bc_value == "periodic") {
×
317
      fatal_error("Periodic boundary condition not supported in DAGMC.");
×
318
    } else {
319
      fatal_error(fmt::format("Unknown boundary condition \"{}\" specified "
×
320
                              "on surface {}",
321
        bc_value, s->id_));
×
322
    }
323

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

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

336
    // add to global array and map
337

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

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

352
int32_t DAGUniverse::cell_index(moab::EntityHandle vol) const
44,321✔
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,321✔
357
}
358

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
363
  return dagmc_ptr()->index_by_handle(surf) + surf_idx_offset_;
×
364
}
365

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

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

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

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

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
391
    if (id_vec[i + 1] > stop_id + 1) {
×
392

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

411
    i++;
×
412
  }
413

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

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

427
bool DAGUniverse::find_cell(GeometryState& p) const
5,139,914✔
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,914✔
432
  if (!found && model::universe_map[this->id_] != model::root_universe) {
5,139,914!
433
    p.lowest_coord().cell() = implicit_complement_idx();
25,453✔
434
    found = true;
25,453✔
435
  }
436
  return found;
5,139,914✔
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
185✔
458
{
459
#ifdef OPENMC_UWUW_ENABLED
460
  return uwuw_ && !uwuw_->material_library.empty();
185✔
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!
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

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

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

509
void DAGUniverse::legacy_assign_material(
123✔
510
  std::string mat_string, std::unique_ptr<DAGCell>& c) const
511
{
512
  bool mat_found_by_name = false;
123✔
513
  // attempt to find a material with a matching name
514
  to_lower(mat_string);
123✔
515
  for (const auto& m : model::materials) {
386✔
516
    std::string m_name = m->name();
263✔
517
    to_lower(m_name);
263✔
518
    if (mat_string == m_name) {
263✔
519
      // assign the material with that name
520
      if (!mat_found_by_name) {
102!
521
        mat_found_by_name = true;
102✔
522
        c->material_.push_back(m->id_);
102✔
523
        // report error if more than one material is found
524
      } else {
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
  }
263✔
533

534
  // if no material was set using a name, assign by id
535
  if (!mat_found_by_name) {
123✔
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) {
121!
554
    const auto& m = model::materials[model::material_map.at(c->material_[0])];
×
555
    std::stringstream msg;
×
556
    msg << "DAGMC material " << mat_string << " was assigned";
×
557
    if (mat_found_by_name) {
×
558
      msg << " using material name: " << m->name_;
×
559
    } else {
560
      msg << " using material id: " << m->id_;
×
561
    }
562
    write_message(msg.str(), 10);
×
563
  }
×
564
}
121✔
565

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

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

575
  if (!uses_uwuw())
46✔
576
    return;
42✔
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!
583
    int32_t next_material_id = 0;
×
584
    for (const auto& m : model::materials) {
×
585
      next_material_id = std::max(m->id_, next_material_id);
×
586
    }
587
    next_material_id++;
×
588

589
    for (auto& mat : uwuw_->material_library) {
×
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!
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 {
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).",
644
      c->id_);
×
645
    write_message(msg, 10);
×
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!
652
      fatal_error(fmt::format(
×
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)
226✔
664
  : Cell {}, dagmc_ptr_(dag_ptr), dag_index_(dag_idx) {};
226✔
665

666
std::pair<double, int32_t> DAGCell::distance(
351,670✔
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()) {
351,670✔
672
    p->last_dir() = u;
305,178✔
673
    p->history().reset();
305,178✔
674
  }
675
  if (on_surface == SURFACE_NONE) {
351,670✔
676
    p->history().reset();
285,527✔
677
  }
678

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

681
  DAGUniverse* dag_univ = static_cast<DAGUniverse*>(univ.get());
351,670✔
682
  if (!dag_univ)
351,670!
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;
351,670✔
687
  double dist = INFINITY;
351,670✔
688

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

692
  // create the ray
693
  double pnt[3] = {r.x, r.y, r.z};
351,670✔
694
  double dir[3] = {u.x, u.y, u.z};
351,670✔
695
  MB_CHK_ERR_CONT(
351,670!
696
    dagmc_ptr_->ray_fire(vol, pnt, dir, hit_surf, dist, &p->history()));
697
  if (hit_surf != 0) {
351,670✔
698
    surf_idx =
334,767✔
699
      dag_univ->surf_idx_offset_ + dagmc_ptr_->index_by_handle(hit_surf);
334,767✔
700
  } else if (!dagmc_ptr_->is_implicit_complement(vol) ||
33,785✔
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.
707
    if (settings::run_mode == RunMode::PLOTTING)
21!
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 =
715
      p->material() == MATERIAL_VOID
21✔
716
        ? "-1 (VOID)"
717
        : std::to_string(model::materials[p->material()]->id());
21!
718
    p->mark_as_lost(fmt::format(
21✔
719
      "No intersection found with DAGMC cell {}, filled with material {}", id_,
21✔
720
      material_id));
721
  }
18✔
722

723
  return {dist, surf_idx};
351,667✔
724
}
725

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

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

739
moab::EntityHandle DAGCell::mesh_handle() const
44,321✔
740
{
741
  return dagmc_ptr()->entity_by_index(3, dag_index());
44,321✔
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

749
BoundingBox DAGCell::bounding_box() const
×
750
{
751
  moab::ErrorCode rval;
752
  moab::EntityHandle vol = dagmc_ptr_->entity_by_index(3, dag_index_);
×
753
  double min[3], max[3];
754
  rval = dagmc_ptr_->getobb(vol, min, max);
×
755
  MB_CHK_ERR_CONT(rval);
×
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)
918✔
764
  : Surface {}, dagmc_ptr_(dag_ptr), dag_index_(dag_idx)
918✔
765
{} // empty constructor
918✔
766

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

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

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

792
Direction DAGSurface::normal(Position r) const
×
793
{
794
  moab::ErrorCode rval;
795
  moab::EntityHandle surf = dagmc_ptr_->entity_by_index(2, dag_index_);
×
796
  double pnt[3] = {r.x, r.y, r.z};
×
797
  double dir[3];
798
  rval = dagmc_ptr_->get_angle(surf, pnt, dir);
×
799
  MB_CHK_ERR_CONT(rval);
×
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)
844✔
819
{
820
  for (pugi::xml_node dag_node : node.children("dagmc_universe")) {
888✔
821
    model::universes.push_back(std::make_unique<DAGUniverse>(dag_node));
46✔
822
    model::universe_map[model::universes.back()->id_] =
44✔
823
      model::universes.size() - 1;
44✔
824
  }
825
}
842✔
826

827
void check_dagmc_root_univ()
844✔
828
{
829
  const auto& ru = model::universes[model::root_universe];
844✔
830
  if (ru->geom_type() == GeometryType::DAG) {
844✔
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());
24!
834
    if (dag_univ && !dag_univ->has_graveyard()) {
24!
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
}
844✔
841

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

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

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

857
  return univp->cell_index(new_vol);
44,321✔
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!
866
    set_errmsg(fmt::format("Universe {} is not a DAGMC Universe", univ_id));
×
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!
874
      set_errmsg(fmt::format("Cell {} is not a DAGMC Cell", cell->id_));
×
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!
889
    set_errmsg(fmt::format("Universe {} is not a DAGMC universe", univ_id));
×
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,510!
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