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

openmc-dev / openmc / 12354948589

16 Dec 2024 02:40PM UTC coverage: 84.827% (+0.004%) from 84.823%
12354948589

push

github

web-flow
Write and read mesh name attribute (#3221)

47 of 48 new or added lines in 2 files covered. (97.92%)

56 existing lines in 1 file now uncovered.

49858 of 58776 relevant lines covered (84.83%)

33891746.58 hits per line

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

87.34
/src/mesh.cpp
1
#include "openmc/mesh.h"
2
#include <algorithm> // for copy, equal, min, min_element
3
#include <cmath>     // for ceil
4
#include <cstddef>   // for size_t
5
#include <gsl/gsl-lite.hpp>
6
#include <string>
7

8
#ifdef OPENMC_MPI
9
#include "mpi.h"
10
#endif
11

12
#include "xtensor/xbuilder.hpp"
13
#include "xtensor/xeval.hpp"
14
#include "xtensor/xmath.hpp"
15
#include "xtensor/xsort.hpp"
16
#include "xtensor/xtensor.hpp"
17
#include "xtensor/xview.hpp"
18
#include <fmt/core.h> // for fmt
19

20
#include "openmc/capi.h"
21
#include "openmc/constants.h"
22
#include "openmc/container_util.h"
23
#include "openmc/error.h"
24
#include "openmc/file_utils.h"
25
#include "openmc/geometry.h"
26
#include "openmc/hdf5_interface.h"
27
#include "openmc/material.h"
28
#include "openmc/memory.h"
29
#include "openmc/message_passing.h"
30
#include "openmc/openmp_interface.h"
31
#include "openmc/particle_data.h"
32
#include "openmc/plot.h"
33
#include "openmc/random_dist.h"
34
#include "openmc/search.h"
35
#include "openmc/settings.h"
36
#include "openmc/tallies/filter.h"
37
#include "openmc/tallies/tally.h"
38
#include "openmc/volume_calc.h"
39
#include "openmc/xml_interface.h"
40

41
#ifdef LIBMESH
42
#include "libmesh/mesh_modification.h"
43
#include "libmesh/mesh_tools.h"
44
#include "libmesh/numeric_vector.h"
45
#endif
46

47
#ifdef DAGMC
48
#include "moab/FileOptions.hpp"
49
#endif
50

51
namespace openmc {
52

53
//==============================================================================
54
// Global variables
55
//==============================================================================
56

57
#ifdef LIBMESH
58
const bool LIBMESH_ENABLED = true;
59
#else
60
const bool LIBMESH_ENABLED = false;
61
#endif
62

63
namespace model {
64

65
std::unordered_map<int32_t, int32_t> mesh_map;
66
vector<unique_ptr<Mesh>> meshes;
67

68
} // namespace model
69

70
#ifdef LIBMESH
71
namespace settings {
72
unique_ptr<libMesh::LibMeshInit> libmesh_init;
73
const libMesh::Parallel::Communicator* libmesh_comm {nullptr};
74
} // namespace settings
75
#endif
76

77
//==============================================================================
78
// Helper functions
79
//==============================================================================
80

81
//! Update an intersection point if the given candidate is closer.
82
//
83
//! The first 6 arguments are coordinates for the starting point of a particle
84
//! and its intersection with a mesh surface.  If the distance between these
85
//! two points is shorter than the given `min_distance`, then the `r` argument
86
//! will be updated to match the intersection point, and `min_distance` will
87
//! also be updated.
88

89
inline bool check_intersection_point(double x1, double x0, double y1, double y0,
90
  double z1, double z0, Position& r, double& min_distance)
91
{
92
  double dist =
93
    std::pow(x1 - x0, 2) + std::pow(y1 - y0, 2) + std::pow(z1 - z0, 2);
94
  if (dist < min_distance) {
95
    r.x = x1;
96
    r.y = y1;
97
    r.z = z1;
98
    min_distance = dist;
99
    return true;
100
  }
101
  return false;
102
}
103

104
//==============================================================================
105
// Mesh implementation
106
//==============================================================================
107

108
Mesh::Mesh(pugi::xml_node node)
2,702✔
109
{
110
  // Read mesh id
111
  id_ = std::stoi(get_node_value(node, "id"));
2,702✔
112
  if (check_for_node(node, "name"))
2,702✔
113
    name_ = get_node_value(node, "name");
17✔
114
}
2,702✔
115

116
void Mesh::set_id(int32_t id)
1✔
117
{
118
  Expects(id >= 0 || id == C_NONE);
1✔
119

120
  // Clear entry in mesh map in case one was already assigned
121
  if (id_ != C_NONE) {
1✔
122
    model::mesh_map.erase(id_);
×
123
    id_ = C_NONE;
×
124
  }
125

126
  // Ensure no other mesh has the same ID
127
  if (model::mesh_map.find(id) != model::mesh_map.end()) {
1✔
128
    throw std::runtime_error {
×
129
      fmt::format("Two meshes have the same ID: {}", id)};
×
130
  }
131

132
  // If no ID is specified, auto-assign the next ID in the sequence
133
  if (id == C_NONE) {
1✔
134
    id = 0;
1✔
135
    for (const auto& m : model::meshes) {
3✔
136
      id = std::max(id, m->id_);
2✔
137
    }
138
    ++id;
1✔
139
  }
140

141
  // Update ID and entry in the mesh map
142
  id_ = id;
1✔
143
  model::mesh_map[id] = model::meshes.size() - 1;
1✔
144
}
1✔
145

146
vector<double> Mesh::volumes() const
156✔
147
{
148
  vector<double> volumes(n_bins());
156✔
149
  for (int i = 0; i < n_bins(); i++) {
982,476✔
150
    volumes[i] = this->volume(i);
982,320✔
151
  }
152
  return volumes;
156✔
153
}
×
154

155
int Mesh::material_volumes(
384✔
156
  int n_sample, int bin, gsl::span<MaterialVolume> result, uint64_t* seed) const
157
{
158
  vector<int32_t> materials;
384✔
159
  vector<int64_t> hits;
384✔
160

161
#pragma omp parallel
192✔
162
  {
163
    vector<int32_t> local_materials;
192✔
164
    vector<int64_t> local_hits;
192✔
165
    GeometryState geom;
192✔
166

167
#pragma omp for
168
    for (int i = 0; i < n_sample; ++i) {
26,166,192✔
169
      // Get seed for i-th sample
170
      uint64_t seed_i = future_seed(3 * i, *seed);
26,166,000✔
171

172
      // Sample position and set geometry state
173
      geom.r() = this->sample_element(bin, &seed_i);
26,166,000✔
174
      geom.u() = {1., 0., 0.};
26,166,000✔
175
      geom.n_coord() = 1;
26,166,000✔
176

177
      // If this location is not in the geometry at all, move on to next block
178
      if (!exhaustive_find_cell(geom))
26,166,000✔
179
        continue;
363,036✔
180

181
      int i_material = geom.material();
25,802,964✔
182

183
      // Check if this material was previously hit and if so, increment count
184
      auto it =
185
        std::find(local_materials.begin(), local_materials.end(), i_material);
25,802,964✔
186
      if (it == local_materials.end()) {
25,802,964✔
187
        local_materials.push_back(i_material);
384✔
188
        local_hits.push_back(1);
384✔
189
      } else {
190
        local_hits[it - local_materials.begin()]++;
25,802,580✔
191
      }
192
    } // omp for
193

194
    // Reduce index/hits lists from each thread into a single copy
195
    reduce_indices_hits(local_materials, local_hits, materials, hits);
192✔
196
  } // omp parallel
192✔
197

198
  // Advance RNG seed
199
  advance_prn_seed(3 * n_sample, seed);
384✔
200

201
  // Make sure span passed in is large enough
202
  if (hits.size() > result.size()) {
384✔
203
    return -1;
×
204
  }
205

206
  // Convert hits to fractions
207
  for (int i_mat = 0; i_mat < hits.size(); ++i_mat) {
1,152✔
208
    double fraction = double(hits[i_mat]) / n_sample;
768✔
209
    result[i_mat].material = materials[i_mat];
768✔
210
    result[i_mat].volume = fraction * this->volume(bin);
768✔
211
  }
212
  return hits.size();
384✔
213
}
384✔
214

215
vector<Mesh::MaterialVolume> Mesh::material_volumes(
×
216
  int n_sample, int bin, uint64_t* seed) const
217
{
218
  // Create result vector with space for 8 pairs
219
  vector<Mesh::MaterialVolume> result;
×
220
  result.reserve(8);
×
221

222
  int size = -1;
×
223
  while (true) {
224
    // Get material volumes
225
    size = this->material_volumes(
×
226
      n_sample, bin, {result.data(), result.data() + result.capacity()}, seed);
×
227

228
    // If capacity was sufficient, resize the vector and return
229
    if (size >= 0) {
×
230
      result.resize(size);
×
231
      break;
×
232
    }
233

234
    // Otherwise, increase capacity of the vector
235
    result.reserve(2 * result.capacity());
×
236
  }
237

238
  return result;
×
239
}
×
240

241
void Mesh::to_hdf5(hid_t group) const
2,940✔
242
{
243
  // Create group for mesh
244
  std::string group_name = fmt::format("mesh {}", id_);
5,314✔
245
  hid_t mesh_group = create_group(group, group_name.c_str());
2,940✔
246

247
  // Write mesh type
248
  write_attribute(mesh_group, "type", this->get_mesh_type());
2,940✔
249

250
  // Write mesh ID
251
  write_attribute(mesh_group, "id", id_);
2,940✔
252

253
  // Write mesh name
254
  write_dataset(mesh_group, "name", name_);
2,940✔
255

256
  // Write mesh data
257
  this->to_hdf5_inner(mesh_group);
2,940✔
258

259
  // Close group
260
  close_group(mesh_group);
2,940✔
261
}
2,940✔
262

263
//==============================================================================
264
// Structured Mesh implementation
265
//==============================================================================
266

267
std::string StructuredMesh::bin_label(int bin) const
5,432,808✔
268
{
269
  MeshIndex ijk = get_indices_from_bin(bin);
5,432,808✔
270

271
  if (n_dimension_ > 2) {
5,432,808✔
272
    return fmt::format("Mesh Index ({}, {}, {})", ijk[0], ijk[1], ijk[2]);
10,833,264✔
273
  } else if (n_dimension_ > 1) {
16,176✔
274
    return fmt::format("Mesh Index ({}, {})", ijk[0], ijk[1]);
31,752✔
275
  } else {
276
    return fmt::format("Mesh Index ({})", ijk[0]);
600✔
277
  }
278
}
279

280
xt::xtensor<int, 1> StructuredMesh::get_x_shape() const
2,850✔
281
{
282
  // because method is const, shape_ is const as well and can't be adapted
283
  auto tmp_shape = shape_;
2,850✔
284
  return xt::adapt(tmp_shape, {n_dimension_});
5,700✔
285
}
286

287
Position StructuredMesh::sample_element(
51,712,212✔
288
  const MeshIndex& ijk, uint64_t* seed) const
289
{
290
  // lookup the lower/upper bounds for the mesh element
291
  double x_min = negative_grid_boundary(ijk, 0);
51,712,212✔
292
  double x_max = positive_grid_boundary(ijk, 0);
51,712,212✔
293

294
  double y_min = (n_dimension_ >= 2) ? negative_grid_boundary(ijk, 1) : 0.0;
51,712,212✔
295
  double y_max = (n_dimension_ >= 2) ? positive_grid_boundary(ijk, 1) : 0.0;
51,712,212✔
296

297
  double z_min = (n_dimension_ == 3) ? negative_grid_boundary(ijk, 2) : 0.0;
51,712,212✔
298
  double z_max = (n_dimension_ == 3) ? positive_grid_boundary(ijk, 2) : 0.0;
51,712,212✔
299

300
  return {x_min + (x_max - x_min) * prn(seed),
51,712,212✔
301
    y_min + (y_max - y_min) * prn(seed), z_min + (z_max - z_min) * prn(seed)};
51,712,212✔
302
}
303

304
//==============================================================================
305
// Unstructured Mesh implementation
306
//==============================================================================
307

308
UnstructuredMesh::UnstructuredMesh(pugi::xml_node node) : Mesh(node)
45✔
309
{
310

311
  // check the mesh type
312
  if (check_for_node(node, "type")) {
45✔
313
    auto temp = get_node_value(node, "type", true, true);
45✔
314
    if (temp != mesh_type) {
45✔
315
      fatal_error(fmt::format("Invalid mesh type: {}", temp));
×
316
    }
317
  }
45✔
318

319
  // check if a length unit multiplier was specified
320
  if (check_for_node(node, "length_multiplier")) {
45✔
321
    length_multiplier_ = std::stod(get_node_value(node, "length_multiplier"));
×
322
  }
323

324
  // get the filename of the unstructured mesh to load
325
  if (check_for_node(node, "filename")) {
45✔
326
    filename_ = get_node_value(node, "filename");
45✔
327
    if (!file_exists(filename_)) {
45✔
328
      fatal_error("Mesh file '" + filename_ + "' does not exist!");
×
329
    }
330
  } else {
331
    fatal_error(fmt::format(
×
332
      "No filename supplied for unstructured mesh with ID: {}", id_));
×
333
  }
334

335
  if (check_for_node(node, "options")) {
45✔
336
    options_ = get_node_value(node, "options");
16✔
337
  }
338

339
  // check if mesh tally data should be written with
340
  // statepoint files
341
  if (check_for_node(node, "output")) {
45✔
342
    output_ = get_node_value_bool(node, "output");
×
343
  }
344
}
45✔
345

346
void UnstructuredMesh::determine_bounds()
23✔
347
{
348
  double xmin = INFTY;
23✔
349
  double ymin = INFTY;
23✔
350
  double zmin = INFTY;
23✔
351
  double xmax = -INFTY;
23✔
352
  double ymax = -INFTY;
23✔
353
  double zmax = -INFTY;
23✔
354
  int n = this->n_vertices();
23✔
355
  for (int i = 0; i < n; ++i) {
53,604✔
356
    auto v = this->vertex(i);
53,581✔
357
    xmin = std::min(v.x, xmin);
53,581✔
358
    ymin = std::min(v.y, ymin);
53,581✔
359
    zmin = std::min(v.z, zmin);
53,581✔
360
    xmax = std::max(v.x, xmax);
53,581✔
361
    ymax = std::max(v.y, ymax);
53,581✔
362
    zmax = std::max(v.z, zmax);
53,581✔
363
  }
364
  lower_left_ = {xmin, ymin, zmin};
23✔
365
  upper_right_ = {xmax, ymax, zmax};
23✔
366
}
23✔
367

368
Position UnstructuredMesh::sample_tet(
601,230✔
369
  std::array<Position, 4> coords, uint64_t* seed) const
370
{
371
  // Uniform distribution
372
  double s = prn(seed);
601,230✔
373
  double t = prn(seed);
601,230✔
374
  double u = prn(seed);
601,230✔
375

376
  // From PyNE implementation of moab tet sampling C. Rocchini & P. Cignoni
377
  // (2000) Generating Random Points in a Tetrahedron, Journal of Graphics
378
  // Tools, 5:4, 9-12, DOI: 10.1080/10867651.2000.10487528
379
  if (s + t > 1) {
601,230✔
380
    s = 1.0 - s;
301,419✔
381
    t = 1.0 - t;
301,419✔
382
  }
383
  if (s + t + u > 1) {
601,230✔
384
    if (t + u > 1) {
400,902✔
385
      double old_t = t;
200,169✔
386
      t = 1.0 - u;
200,169✔
387
      u = 1.0 - s - old_t;
200,169✔
388
    } else if (t + u <= 1) {
200,733✔
389
      double old_s = s;
200,733✔
390
      s = 1.0 - t - u;
200,733✔
391
      u = old_s + t + u - 1;
200,733✔
392
    }
393
  }
394
  return s * (coords[1] - coords[0]) + t * (coords[2] - coords[0]) +
1,202,460✔
395
         u * (coords[3] - coords[0]) + coords[0];
1,803,690✔
396
}
397

398
const std::string UnstructuredMesh::mesh_type = "unstructured";
399

400
std::string UnstructuredMesh::get_mesh_type() const
30✔
401
{
402
  return mesh_type;
30✔
403
}
404

405
void UnstructuredMesh::surface_bins_crossed(
×
406
  Position r0, Position r1, const Direction& u, vector<int>& bins) const
407
{
408
  fatal_error("Unstructured mesh surface tallies are not implemented.");
×
409
}
410

411
std::string UnstructuredMesh::bin_label(int bin) const
193,712✔
412
{
413
  return fmt::format("Mesh Index ({})", bin);
193,712✔
414
};
415

416
void UnstructuredMesh::to_hdf5_inner(hid_t mesh_group) const
30✔
417
{
418
  write_dataset(mesh_group, "filename", filename_);
30✔
419
  write_dataset(mesh_group, "library", this->library());
30✔
420
  if (!options_.empty()) {
30✔
421
    write_attribute(mesh_group, "options", options_);
8✔
422
  }
423

424
  if (length_multiplier_ > 0.0)
30✔
425
    write_dataset(mesh_group, "length_multiplier", length_multiplier_);
×
426

427
  // write vertex coordinates
428
  xt::xtensor<double, 2> vertices({static_cast<size_t>(this->n_vertices()), 3});
30✔
429
  for (int i = 0; i < this->n_vertices(); i++) {
67,928✔
430
    auto v = this->vertex(i);
67,898✔
431
    xt::view(vertices, i, xt::all()) = xt::xarray<double>({v.x, v.y, v.z});
67,898✔
432
  }
433
  write_dataset(mesh_group, "vertices", vertices);
30✔
434

435
  int num_elem_skipped = 0;
30✔
436

437
  // write element types and connectivity
438
  vector<double> volumes;
30✔
439
  xt::xtensor<int, 2> connectivity({static_cast<size_t>(this->n_bins()), 8});
30✔
440
  xt::xtensor<int, 2> elem_types({static_cast<size_t>(this->n_bins()), 1});
30✔
441
  for (int i = 0; i < this->n_bins(); i++) {
337,742✔
442
    auto conn = this->connectivity(i);
337,712✔
443

444
    volumes.emplace_back(this->volume(i));
337,712✔
445

446
    // write linear tet element
447
    if (conn.size() == 4) {
337,712✔
448
      xt::view(elem_types, i, xt::all()) =
671,424✔
449
        static_cast<int>(ElementType::LINEAR_TET);
671,424✔
450
      xt::view(connectivity, i, xt::all()) =
671,424✔
451
        xt::xarray<int>({conn[0], conn[1], conn[2], conn[3], -1, -1, -1, -1});
1,007,136✔
452
      // write linear hex element
453
    } else if (conn.size() == 8) {
2,000✔
454
      xt::view(elem_types, i, xt::all()) =
4,000✔
455
        static_cast<int>(ElementType::LINEAR_HEX);
4,000✔
456
      xt::view(connectivity, i, xt::all()) = xt::xarray<int>({conn[0], conn[1],
8,000✔
457
        conn[2], conn[3], conn[4], conn[5], conn[6], conn[7]});
6,000✔
458
    } else {
459
      num_elem_skipped++;
×
460
      xt::view(elem_types, i, xt::all()) =
×
461
        static_cast<int>(ElementType::UNSUPPORTED);
462
      xt::view(connectivity, i, xt::all()) = -1;
×
463
    }
464
  }
337,712✔
465

466
  // warn users that some elements were skipped
467
  if (num_elem_skipped > 0) {
30✔
468
    warning(fmt::format("The connectivity of {} elements "
×
469
                        "on mesh {} were not written "
470
                        "because they are not of type linear tet/hex.",
471
      num_elem_skipped, this->id_));
×
472
  }
473

474
  write_dataset(mesh_group, "volumes", volumes);
30✔
475
  write_dataset(mesh_group, "connectivity", connectivity);
30✔
476
  write_dataset(mesh_group, "element_types", elem_types);
30✔
477
}
30✔
478

479
void UnstructuredMesh::set_length_multiplier(double length_multiplier)
23✔
480
{
481
  length_multiplier_ = length_multiplier;
23✔
482
}
23✔
483

484
ElementType UnstructuredMesh::element_type(int bin) const
120,000✔
485
{
486
  auto conn = connectivity(bin);
120,000✔
487

488
  if (conn.size() == 4)
120,000✔
489
    return ElementType::LINEAR_TET;
120,000✔
490
  else if (conn.size() == 8)
×
491
    return ElementType::LINEAR_HEX;
×
492
  else
493
    return ElementType::UNSUPPORTED;
×
494
}
120,000✔
495

496
StructuredMesh::MeshIndex StructuredMesh::get_indices(
918,589,556✔
497
  Position r, bool& in_mesh) const
498
{
499
  MeshIndex ijk;
500
  in_mesh = true;
918,589,556✔
501
  for (int i = 0; i < n_dimension_; ++i) {
2,147,483,647✔
502
    ijk[i] = get_index_in_direction(r[i], i);
2,147,483,647✔
503

504
    if (ijk[i] < 1 || ijk[i] > shape_[i])
2,147,483,647✔
505
      in_mesh = false;
91,285,024✔
506
  }
507
  return ijk;
918,589,556✔
508
}
509

510
int StructuredMesh::get_bin_from_indices(const MeshIndex& ijk) const
977,206,656✔
511
{
512
  switch (n_dimension_) {
977,206,656✔
513
  case 1:
956,736✔
514
    return ijk[0] - 1;
956,736✔
515
  case 2:
21,602,040✔
516
    return (ijk[1] - 1) * shape_[0] + ijk[0] - 1;
21,602,040✔
517
  case 3:
954,647,880✔
518
    return ((ijk[2] - 1) * shape_[1] + (ijk[1] - 1)) * shape_[0] + ijk[0] - 1;
954,647,880✔
519
  default:
×
520
    throw std::runtime_error {"Invalid number of mesh dimensions"};
×
521
  }
522
}
523

524
StructuredMesh::MeshIndex StructuredMesh::get_indices_from_bin(int bin) const
60,385,068✔
525
{
526
  MeshIndex ijk;
527
  if (n_dimension_ == 1) {
60,385,068✔
528
    ijk[0] = bin + 1;
300✔
529
  } else if (n_dimension_ == 2) {
60,384,768✔
530
    ijk[0] = bin % shape_[0] + 1;
15,876✔
531
    ijk[1] = bin / shape_[0] + 1;
15,876✔
532
  } else if (n_dimension_ == 3) {
60,368,892✔
533
    ijk[0] = bin % shape_[0] + 1;
60,368,892✔
534
    ijk[1] = (bin % (shape_[0] * shape_[1])) / shape_[0] + 1;
60,368,892✔
535
    ijk[2] = bin / (shape_[0] * shape_[1]) + 1;
60,368,892✔
536
  }
537
  return ijk;
60,385,068✔
538
}
539

540
int StructuredMesh::get_bin(Position r) const
312,342,896✔
541
{
542
  // Determine indices
543
  bool in_mesh;
544
  MeshIndex ijk = get_indices(r, in_mesh);
312,342,896✔
545
  if (!in_mesh)
312,342,896✔
546
    return -1;
22,164,978✔
547

548
  // Convert indices to bin
549
  return get_bin_from_indices(ijk);
290,177,918✔
550
}
551

552
int StructuredMesh::n_bins() const
999,476✔
553
{
554
  return std::accumulate(
999,476✔
555
    shape_.begin(), shape_.begin() + n_dimension_, 1, std::multiplies<>());
1,998,952✔
556
}
557

558
int StructuredMesh::n_surface_bins() const
563✔
559
{
560
  return 4 * n_dimension_ * n_bins();
563✔
561
}
562

563
xt::xtensor<double, 1> StructuredMesh::count_sites(
×
564
  const SourceSite* bank, int64_t length, bool* outside) const
565
{
566
  // Determine shape of array for counts
567
  std::size_t m = this->n_bins();
×
568
  vector<std::size_t> shape = {m};
×
569

570
  // Create array of zeros
571
  xt::xarray<double> cnt {shape, 0.0};
×
572
  bool outside_ = false;
×
573

574
  for (int64_t i = 0; i < length; i++) {
×
575
    const auto& site = bank[i];
×
576

577
    // determine scoring bin for entropy mesh
578
    int mesh_bin = get_bin(site.r);
×
579

580
    // if outside mesh, skip particle
581
    if (mesh_bin < 0) {
×
582
      outside_ = true;
×
583
      continue;
×
584
    }
585

586
    // Add to appropriate bin
587
    cnt(mesh_bin) += site.wgt;
×
588
  }
589

590
  // Create copy of count data. Since ownership will be acquired by xtensor,
591
  // std::allocator must be used to avoid Valgrind mismatched free() / delete
592
  // warnings.
593
  int total = cnt.size();
×
594
  double* cnt_reduced = std::allocator<double> {}.allocate(total);
×
595

596
#ifdef OPENMC_MPI
597
  // collect values from all processors
598
  MPI_Reduce(
599
    cnt.data(), cnt_reduced, total, MPI_DOUBLE, MPI_SUM, 0, mpi::intracomm);
600

601
  // Check if there were sites outside the mesh for any processor
602
  if (outside) {
603
    MPI_Reduce(&outside_, outside, 1, MPI_C_BOOL, MPI_LOR, 0, mpi::intracomm);
604
  }
605
#else
606
  std::copy(cnt.data(), cnt.data() + total, cnt_reduced);
607
  if (outside)
608
    *outside = outside_;
609
#endif
610

611
  // Adapt reduced values in array back into an xarray
612
  auto arr = xt::adapt(cnt_reduced, total, xt::acquire_ownership(), shape);
×
613
  xt::xarray<double> counts = arr;
×
614

615
  return counts;
×
616
}
617

618
// raytrace through the mesh. The template class T will do the tallying.
619
// A modern optimizing compiler can recognize the noop method of T and eleminate
620
// that call entirely.
621
template<class T>
622
void StructuredMesh::raytrace_mesh(
606,622,392✔
623
  Position r0, Position r1, const Direction& u, T tally) const
624
{
625
  // TODO: when c++-17 is available, use "if constexpr ()" to compile-time
626
  // enable/disable tally calls for now, T template type needs to provide both
627
  // surface and track methods, which might be empty. modern optimizing
628
  // compilers will (hopefully) eliminate the complete code (including
629
  // calculation of parameters) but for the future: be explicit
630

631
  // Compute the length of the entire track.
632
  double total_distance = (r1 - r0).norm();
606,622,392✔
633
  if (total_distance == 0.0 && settings::solver_type != SolverType::RANDOM_RAY)
606,622,392✔
634
    return;
5,711,856✔
635

636
  const int n = n_dimension_;
600,910,536✔
637

638
  // Flag if position is inside the mesh
639
  bool in_mesh;
640

641
  // Position is r = r0 + u * traveled_distance, start at r0
642
  double traveled_distance {0.0};
600,910,536✔
643

644
  // Calculate index of current cell. Offset the position a tiny bit in
645
  // direction of flight
646
  MeshIndex ijk = get_indices(r0 + TINY_BIT * u, in_mesh);
600,910,536✔
647

648
  // if track is very short, assume that it is completely inside one cell.
649
  // Only the current cell will score and no surfaces
650
  if (total_distance < 2 * TINY_BIT) {
600,910,536✔
651
    if (in_mesh) {
12✔
652
      tally.track(ijk, 1.0);
×
653
    }
654
    return;
12✔
655
  }
656

657
  // translate start and end positions,
658
  // this needs to come after the get_indices call because it does its own
659
  // translation
660
  local_coords(r0);
600,910,524✔
661
  local_coords(r1);
600,910,524✔
662

663
  // Calculate initial distances to next surfaces in all three dimensions
664
  std::array<MeshDistance, 3> distances;
1,201,821,048✔
665
  for (int k = 0; k < n; ++k) {
2,147,483,647✔
666
    distances[k] = distance_to_grid_boundary(ijk, k, r0, u, 0.0);
1,784,699,532✔
667
  }
668

669
  // Loop until r = r1 is eventually reached
670
  while (true) {
348,780,110✔
671

672
    if (in_mesh) {
949,690,634✔
673

674
      // find surface with minimal distance to current position
675
      const auto k = std::min_element(distances.begin(), distances.end()) -
860,085,061✔
676
                     distances.begin();
860,085,061✔
677

678
      // Tally track length delta since last step
679
      tally.track(ijk,
860,085,061✔
680
        (std::min(distances[k].distance, total_distance) - traveled_distance) /
860,085,061✔
681
          total_distance);
682

683
      // update position and leave, if we have reached end position
684
      traveled_distance = distances[k].distance;
860,085,061✔
685
      if (traveled_distance >= total_distance)
860,085,061✔
686
        return;
516,641,075✔
687

688
      // If we have not reached r1, we have hit a surface. Tally outward current
689
      tally.surface(ijk, k, distances[k].max_surface, false);
343,443,986✔
690

691
      // Update cell and calculate distance to next surface in k-direction.
692
      // The two other directions are still valid!
693
      ijk[k] = distances[k].next_index;
343,443,986✔
694
      distances[k] =
343,443,986✔
695
        distance_to_grid_boundary(ijk, k, r0, u, traveled_distance);
343,443,986✔
696

697
      // Check if we have left the interior of the mesh
698
      in_mesh = ((ijk[k] >= 1) && (ijk[k] <= shape_[k]));
343,443,986✔
699

700
      // If we are still inside the mesh, tally inward current for the next cell
701
      if (in_mesh)
343,443,986✔
702
        tally.surface(ijk, k, !distances[k].max_surface, true);
320,807,295✔
703

704
    } else { // not inside mesh
705

706
      // For all directions outside the mesh, find the distance that we need to
707
      // travel to reach the next surface. Use the largest distance, as only
708
      // this will cross all outer surfaces.
709
      int k_max {0};
89,605,573✔
710
      for (int k = 0; k < n; ++k) {
356,080,819✔
711
        if ((ijk[k] < 1 || ijk[k] > shape_[k]) &&
357,423,393✔
712
            (distances[k].distance > traveled_distance)) {
90,948,147✔
713
          traveled_distance = distances[k].distance;
90,162,387✔
714
          k_max = k;
90,162,387✔
715
        }
716
      }
717

718
      // If r1 is not inside the mesh, exit here
719
      if (traveled_distance >= total_distance)
89,605,573✔
720
        return;
84,269,449✔
721

722
      // Calculate the new cell index and update all distances to next surfaces.
723
      ijk = get_indices(r0 + (traveled_distance + TINY_BIT) * u, in_mesh);
5,336,124✔
724
      for (int k = 0; k < n; ++k) {
21,115,236✔
725
        distances[k] =
15,779,112✔
726
          distance_to_grid_boundary(ijk, k, r0, u, traveled_distance);
15,779,112✔
727
      }
728

729
      // If inside the mesh, Tally inward current
730
      if (in_mesh)
5,336,124✔
731
        tally.surface(ijk, k_max, !distances[k_max].max_surface, true);
3,959,064✔
732
    }
733
  }
734
}
735

230,883,562✔
736
void StructuredMesh::bins_crossed(Position r0, Position r1, const Direction& u,
737
  vector<int>& bins, vector<double>& lengths) const
738
{
739

740
  // Helper tally class.
741
  // stores a pointer to the mesh class and references to bins and lengths
742
  // parameters. Performs the actual tally through the track method.
743
  struct TrackAggregator {
744
    TrackAggregator(
745
      const StructuredMesh* _mesh, vector<int>& _bins, vector<double>& _lengths)
230,883,562✔
746
      : mesh(_mesh), bins(_bins), lengths(_lengths)
230,883,562✔
747
    {}
×
748
    void surface(const MeshIndex& ijk, int k, bool max, bool inward) const {}
749
    void track(const MeshIndex& ijk, double l) const
230,883,562✔
750
    {
751
      bins.push_back(mesh->get_bin_from_indices(ijk));
752
      lengths.push_back(l);
753
    }
754

755
    const StructuredMesh* mesh;
230,883,562✔
756
    vector<int>& bins;
757
    vector<double>& lengths;
758
  };
759

230,883,562✔
760
  // Perform the mesh raytrace with the helper class.
761
  raytrace_mesh(r0, r1, u, TrackAggregator(this, bins, lengths));
762
}
763

230,883,562✔
764
void StructuredMesh::surface_bins_crossed(
×
765
  Position r0, Position r1, const Direction& u, vector<int>& bins) const
×
766
{
767

×
768
  // Helper tally class.
769
  // stores a pointer to the mesh class and a reference to the bins parameter.
770
  // Performs the actual tally through the surface method.
771
  struct SurfaceAggregator {
772
    SurfaceAggregator(const StructuredMesh* _mesh, vector<int>& _bins)
773
      : mesh(_mesh), bins(_bins)
230,883,562✔
774
    {}
230,883,562✔
775
    void surface(const MeshIndex& ijk, int k, bool max, bool inward) const
776
    {
777
      int i_bin =
461,767,124✔
778
        4 * mesh->n_dimension_ * mesh->get_bin_from_indices(ijk) + 4 * k;
921,698,104✔
779
      if (max)
690,814,542✔
780
        i_bin += 2;
781
      if (inward)
782
        i_bin += 1;
783
      bins.push_back(i_bin);
57,346,531✔
784
    }
785
    void track(const MeshIndex& idx, double l) const {}
288,230,093✔
786

787
    const StructuredMesh* mesh;
788
    vector<int>& bins;
285,334,288✔
789
  };
285,334,288✔
790

791
  // Perform the mesh raytrace with the helper class.
792
  raytrace_mesh(r0, r1, u, SurfaceAggregator(this, bins));
285,334,288✔
793
}
285,334,288✔
794

795
//==============================================================================
796
// RegularMesh implementation
797
//==============================================================================
285,334,288✔
798

285,334,288✔
799
RegularMesh::RegularMesh(pugi::xml_node node) : StructuredMesh {node}
228,231,345✔
800
{
801
  // Determine number of dimensions for mesh
802
  if (!check_for_node(node, "dimension")) {
57,102,943✔
803
    fatal_error("Must specify <dimension> on a regular mesh.");
804
  }
805

806
  xt::xtensor<int, 1> shape = get_node_xarray<int>(node, "dimension");
57,102,943✔
807
  int n = n_dimension_ = shape.size();
57,102,943✔
808
  if (n != 1 && n != 2 && n != 3) {
57,102,943✔
809
    fatal_error("Mesh must be one, two, or three dimensions.");
810
  }
811
  std::copy(shape.begin(), shape.end(), shape_.begin());
57,102,943✔
812

813
  // Check that dimensions are all greater than zero
814
  if (xt::any(shape <= 0)) {
57,102,943✔
815
    fatal_error("All entries on the <dimension> element for a tally "
54,956,430✔
816
                "mesh must be positive.");
817
  }
818

819
  // Check for lower-left coordinates
820
  if (check_for_node(node, "lower_left")) {
821
    // Read mesh lower-left corner location
822
    lower_left_ = get_node_xarray<double>(node, "lower_left");
2,895,805✔
823
  } else {
11,231,920✔
824
    fatal_error("Must specify <lower_left> on a mesh.");
11,388,472✔
825
  }
3,052,357✔
826

2,987,893✔
827
  // Make sure lower_left and dimension match
2,987,893✔
828
  if (shape.size() != lower_left_.size()) {
829
    fatal_error("Number of entries on <lower_left> must be the same "
830
                "as the number of entries on <dimension>.");
831
  }
832

2,895,805✔
833
  if (check_for_node(node, "width")) {
2,652,217✔
834
    // Make sure one of upper-right or width were specified
835
    if (check_for_node(node, "upper_right")) {
836
      fatal_error("Cannot specify both <upper_right> and <width> on a mesh.");
243,588✔
837
    }
860,256✔
838

616,668✔
839
    width_ = get_node_xarray<double>(node, "width");
616,668✔
840

841
    // Check to ensure width has same dimensions
842
    auto n = width_.size();
843
    if (n != lower_left_.size()) {
243,588✔
844
      fatal_error("Number of entries on <width> must be the same as "
218,592✔
845
                  "the number of entries on <lower_left>.");
846
    }
847

848
    // Check for negative widths
375,738,830✔
849
    if (xt::any(width_ < 0.0)) {
850
      fatal_error("Cannot have a negative <width> on a tally mesh.");
851
    }
852

853
    // Set width and upper right coordinate
854
    upper_right_ = xt::eval(lower_left_ + shape * width_);
855

856
  } else if (check_for_node(node, "upper_right")) {
857
    upper_right_ = get_node_xarray<double>(node, "upper_right");
858

375,738,830✔
859
    // Check to ensure width has same dimensions
375,738,830✔
860
    auto n = upper_right_.size();
5,711,856✔
861
    if (n != lower_left_.size()) {
862
      fatal_error("Number of entries on <upper_right> must be the "
370,026,974✔
863
                  "same as the number of entries on <lower_left>.");
864
    }
865

866
    // Check that upper-right is above lower-left
867
    if (xt::any(upper_right_ < lower_left_)) {
868
      fatal_error("The <upper_right> coordinates must be greater than "
370,026,974✔
869
                  "the <lower_left> coordinates on a tally mesh.");
870
    }
871

872
    // Set width
370,026,974✔
873
    width_ = xt::eval((upper_right_ - lower_left_) / shape);
874
  } else {
875
    fatal_error("Must specify either <upper_right> or <width> on a mesh.");
876
  }
370,026,974✔
877

12✔
878
  // Set material volumes
×
879
  volume_frac_ = 1.0 / xt::prod(shape)();
880

12✔
881
  element_volume_ = 1.0;
882
  for (int i = 0; i < n_dimension_; i++) {
883
    element_volume_ *= width_[i];
884
  }
885
}
886

370,026,962✔
887
int RegularMesh::get_index_in_direction(double r, int i) const
370,026,962✔
888
{
889
  return std::ceil((r - lower_left_[i]) / width_[i]);
890
}
740,053,924✔
891

1,463,911,952✔
892
const std::string RegularMesh::mesh_type = "regular";
1,093,884,990✔
893

894
std::string RegularMesh::get_mesh_type() const
895
{
896
  return mesh_type;
291,433,579✔
897
}
898

661,460,541✔
899
double RegularMesh::positive_grid_boundary(const MeshIndex& ijk, int i) const
900
{
901
  return lower_left_[i] + ijk[i] * width_[i];
574,750,773✔
902
}
574,750,773✔
903

904
double RegularMesh::negative_grid_boundary(const MeshIndex& ijk, int i) const
905
{
574,750,773✔
906
  return lower_left_[i] + (ijk[i] - 1) * width_[i];
574,750,773✔
907
}
908

909
StructuredMesh::MeshDistance RegularMesh::distance_to_grid_boundary(
910
  const MeshIndex& ijk, int i, const Position& r0, const Direction& u,
574,750,773✔
911
  double l) const
574,750,773✔
912
{
288,409,730✔
913
  MeshDistance d;
914
  d.next_index = ijk[i];
915
  if (std::abs(u[i]) < FP_PRECISION)
286,341,043✔
916
    return d;
917

918
  d.max_surface = (u[i] > 0);
919
  if (d.max_surface && (ijk[i] <= shape_[i])) {
286,341,043✔
920
    d.next_index++;
286,341,043✔
921
    d.distance = (positive_grid_boundary(ijk, i) - r0[i]) / u[i];
286,341,043✔
922
  } else if (!d.max_surface && (ijk[i] >= 1)) {
923
    d.next_index--;
924
    d.distance = (negative_grid_boundary(ijk, i) - r0[i]) / u[i];
286,341,043✔
925
  }
926
  return d;
927
}
286,341,043✔
928

265,850,865✔
929
std::pair<vector<double>, vector<double>> RegularMesh::plot(
930
  Position plot_ll, Position plot_ur) const
931
{
932
  // Figure out which axes lie in the plane of the plot.
933
  array<int, 2> axes {-1, -1};
934
  if (plot_ur.z == plot_ll.z) {
935
    axes[0] = 0;
86,709,768✔
936
    if (n_dimension_ > 1)
344,848,899✔
937
      axes[1] = 1;
346,034,921✔
938
  } else if (plot_ur.y == plot_ll.y) {
87,895,790✔
939
    axes[0] = 0;
87,174,494✔
940
    if (n_dimension_ > 2)
87,174,494✔
941
      axes[1] = 2;
942
  } else if (plot_ur.x == plot_ll.x) {
943
    if (n_dimension_ > 1)
944
      axes[0] = 1;
945
    if (n_dimension_ > 2)
86,709,768✔
946
      axes[1] = 2;
81,617,232✔
947
  } else {
948
    fatal_error("Can only plot mesh lines on an axis-aligned plot");
949
  }
5,092,536✔
950

20,254,980✔
951
  // Get the coordinates of the mesh lines along both of the axes.
15,162,444✔
952
  array<vector<double>, 2> axis_lines;
15,162,444✔
953
  for (int i_ax = 0; i_ax < 2; ++i_ax) {
954
    int axis = axes[i_ax];
955
    if (axis == -1)
956
      continue;
5,092,536✔
957
    auto& lines {axis_lines[i_ax]};
3,740,472✔
958

959
    double coord = lower_left_[axis];
960
    for (int i = 0; i < shape_[axis] + 1; ++i) {
961
      if (coord >= plot_ll[axis] && coord <= plot_ur[axis])
962
        lines.push_back(coord);
375,738,830✔
963
      coord += width_[axis];
964
    }
965
  }
966

967
  return {axis_lines[0], axis_lines[1]};
968
}
969

970
void RegularMesh::to_hdf5_inner(hid_t mesh_group) const
375,738,830✔
971
{
972
  write_dataset(mesh_group, "type", "regular");
375,738,830✔
973
  write_dataset(mesh_group, "dimension", get_x_shape());
375,738,830✔
974
  write_dataset(mesh_group, "lower_left", lower_left_);
555,932,380✔
975
  write_dataset(mesh_group, "upper_right", upper_right_);
574,750,773✔
976
  write_dataset(mesh_group, "width", width_);
977
}
574,750,773✔
978

574,750,773✔
979
xt::xtensor<double, 1> RegularMesh::count_sites(
574,750,773✔
980
  const SourceSite* bank, int64_t length, bool* outside) const
981
{
982
  // Determine shape of array for counts
983
  std::size_t m = this->n_bins();
984
  vector<std::size_t> shape = {m};
985

986
  // Create array of zeros
987
  xt::xarray<double> cnt {shape, 0.0};
375,738,830✔
988
  bool outside_ = false;
375,738,830✔
989

990
  for (int64_t i = 0; i < length; i++) {
230,883,562✔
991
    const auto& site = bank[i];
992

993
    // determine scoring bin for entropy mesh
994
    int mesh_bin = get_bin(site.r);
995

996
    // if outside mesh, skip particle
997
    if (mesh_bin < 0) {
998
      outside_ = true;
230,883,562✔
999
      continue;
230,883,562✔
1000
    }
230,883,562✔
1001

112,277,965✔
1002
    // Add to appropriate bin
1003
    cnt(mesh_bin) += site.wgt;
1004
  }
112,277,965✔
1005

112,277,965✔
1006
  // Create copy of count data. Since ownership will be acquired by xtensor,
56,095,338✔
1007
  // std::allocator must be used to avoid Valgrind mismatched free() / delete
112,277,965✔
1008
  // warnings.
55,175,022✔
1009
  int total = cnt.size();
112,277,965✔
1010
  double* cnt_reduced = std::allocator<double> {}.allocate(total);
112,277,965✔
1011

285,334,288✔
1012
#ifdef OPENMC_MPI
1013
  // collect values from all processors
1014
  MPI_Reduce(
1015
    cnt.data(), cnt_reduced, total, MPI_DOUBLE, MPI_SUM, 0, mpi::intracomm);
1016

1017
  // Check if there were sites outside the mesh for any processor
1018
  if (outside) {
230,883,562✔
1019
    MPI_Reduce(&outside_, outside, 1, MPI_C_BOOL, MPI_LOR, 0, mpi::intracomm);
230,883,562✔
1020
  }
1021
#else
1022
  std::copy(cnt.data(), cnt.data() + total, cnt_reduced);
1023
  if (outside)
1024
    *outside = outside_;
1025
#endif
1,828✔
1026

1027
  // Adapt reduced values in array back into an xarray
1028
  auto arr = xt::adapt(cnt_reduced, total, xt::acquire_ownership(), shape);
1,828✔
UNCOV
1029
  xt::xarray<double> counts = arr;
×
1030

1031
  return counts;
1032
}
1,828✔
1033

1,828✔
1034
double RegularMesh::volume(const MeshIndex& ijk) const
1,828✔
UNCOV
1035
{
×
1036
  return element_volume_;
1037
}
1,828✔
1038

1039
//==============================================================================
1040
// RectilinearMesh implementation
1,828✔
UNCOV
1041
//==============================================================================
×
1042

1043
RectilinearMesh::RectilinearMesh(pugi::xml_node node) : StructuredMesh {node}
1044
{
1045
  n_dimension_ = 3;
1046

1,828✔
1047
  grid_[0] = get_node_array<double>(node, "x_grid");
1048
  grid_[1] = get_node_array<double>(node, "y_grid");
1,828✔
1049
  grid_[2] = get_node_array<double>(node, "z_grid");
UNCOV
1050

×
1051
  if (int err = set_grid()) {
1052
    fatal_error(openmc_err_msg);
1053
  }
1054
}
1,828✔
UNCOV
1055

×
1056
const std::string RectilinearMesh::mesh_type = "rectilinear";
1057

1058
std::string RectilinearMesh::get_mesh_type() const
1059
{
1,828✔
1060
  return mesh_type;
1061
}
51✔
UNCOV
1062

×
1063
double RectilinearMesh::positive_grid_boundary(
1064
  const MeshIndex& ijk, int i) const
1065
{
51✔
1066
  return grid_[i][ijk[i]];
1067
}
1068

51✔
1069
double RectilinearMesh::negative_grid_boundary(
51✔
UNCOV
1070
  const MeshIndex& ijk, int i) const
×
1071
{
1072
  return grid_[i][ijk[i] - 1];
1073
}
1074

1075
StructuredMesh::MeshDistance RectilinearMesh::distance_to_grid_boundary(
51✔
UNCOV
1076
  const MeshIndex& ijk, int i, const Position& r0, const Direction& u,
×
1077
  double l) const
1078
{
1079
  MeshDistance d;
1080
  d.next_index = ijk[i];
51✔
1081
  if (std::abs(u[i]) < FP_PRECISION)
1082
    return d;
1,777✔
1083

1,777✔
1084
  d.max_surface = (u[i] > 0);
1085
  if (d.max_surface && (ijk[i] <= shape_[i])) {
1086
    d.next_index++;
1,777✔
1087
    d.distance = (positive_grid_boundary(ijk, i) - r0[i]) / u[i];
1,777✔
UNCOV
1088
  } else if (!d.max_surface && (ijk[i] > 0)) {
×
1089
    d.next_index--;
1090
    d.distance = (negative_grid_boundary(ijk, i) - r0[i]) / u[i];
1091
  }
1092
  return d;
1093
}
1,777✔
UNCOV
1094

×
1095
int RectilinearMesh::set_grid()
1096
{
1097
  shape_ = {static_cast<int>(grid_[0].size()) - 1,
1098
    static_cast<int>(grid_[1].size()) - 1,
1099
    static_cast<int>(grid_[2].size()) - 1};
1,777✔
1100

UNCOV
1101
  for (const auto& g : grid_) {
×
1102
    if (g.size() < 2) {
1103
      set_errmsg("x-, y-, and z- grids for rectilinear meshes "
1104
                 "must each have at least 2 points");
1105
      return OPENMC_E_INVALID_ARGUMENT;
1,828✔
1106
    }
1107
    if (std::adjacent_find(g.begin(), g.end(), std::greater_equal<>()) !=
1,828✔
1108
        g.end()) {
7,045✔
1109
      set_errmsg("Values in for x-, y-, and z- grids for "
5,217✔
1110
                 "rectilinear meshes must be sorted and unique.");
1111
      return OPENMC_E_INVALID_ARGUMENT;
1,828✔
1112
    }
1113
  }
2,147,483,647✔
1114

1115
  lower_left_ = {grid_[0].front(), grid_[1].front(), grid_[2].front()};
2,147,483,647✔
1116
  upper_right_ = {grid_[0].back(), grid_[1].back(), grid_[2].back()};
1117

1118
  return 0;
1119
}
1120

3,697✔
1121
int RectilinearMesh::get_index_in_direction(double r, int i) const
1122
{
3,697✔
1123
  return lower_bound_index(grid_[i].begin(), grid_[i].end(), r) + 1;
1124
}
1125

910,064,822✔
1126
std::pair<vector<double>, vector<double>> RectilinearMesh::plot(
1127
  Position plot_ll, Position plot_ur) const
910,064,822✔
1128
{
1129
  // Figure out which axes lie in the plane of the plot.
1130
  array<int, 2> axes {-1, -1};
909,619,168✔
1131
  if (plot_ur.z == plot_ll.z) {
1132
    axes = {0, 1};
909,619,168✔
1133
  } else if (plot_ur.y == plot_ll.y) {
1134
    axes = {0, 2};
1135
  } else if (plot_ur.x == plot_ll.x) {
1,533,821,414✔
1136
    axes = {1, 2};
1137
  } else {
1138
    fatal_error("Can only plot mesh lines on an axis-aligned plot");
1139
  }
1,533,821,414✔
1140

1,533,821,414✔
1141
  // Get the coordinates of the mesh lines along both of the axes.
1,533,821,414✔
UNCOV
1142
  array<vector<double>, 2> axis_lines;
×
1143
  for (int i_ax = 0; i_ax < 2; ++i_ax) {
1144
    int axis = axes[i_ax];
1,533,821,414✔
1145
    vector<double>& lines {axis_lines[i_ax]};
1,533,821,414✔
1146

756,368,186✔
1147
    for (auto coord : grid_[axis]) {
756,368,186✔
1148
      if (coord >= plot_ll[axis] && coord <= plot_ur[axis])
777,453,228✔
1149
        lines.push_back(coord);
755,922,532✔
1150
    }
755,922,532✔
1151
  }
1152

1,533,821,414✔
1153
  return {axis_lines[0], axis_lines[1]};
1154
}
1155

24✔
1156
void RectilinearMesh::to_hdf5_inner(hid_t mesh_group) const
1157
{
1158
  write_dataset(mesh_group, "type", "rectilinear");
1159
  write_dataset(mesh_group, "x_grid", grid_[0]);
24✔
1160
  write_dataset(mesh_group, "y_grid", grid_[1]);
24✔
1161
  write_dataset(mesh_group, "z_grid", grid_[2]);
24✔
1162
}
24✔
1163

24✔
1164
double RectilinearMesh::volume(const MeshIndex& ijk) const
×
UNCOV
1165
{
×
1166
  double vol {1.0};
×
UNCOV
1167

×
UNCOV
1168
  for (int i = 0; i < n_dimension_; i++) {
×
UNCOV
1169
    vol *= grid_[i][ijk[i]] - grid_[i][ijk[i] - 1];
×
UNCOV
1170
  }
×
UNCOV
1171
  return vol;
×
UNCOV
1172
}
×
1173

1174
//==============================================================================
×
1175
// CylindricalMesh implementation
1176
//==============================================================================
1177

1178
CylindricalMesh::CylindricalMesh(pugi::xml_node node)
24✔
1179
  : PeriodicStructuredMesh {node}
72✔
1180
{
48✔
1181
  n_dimension_ = 3;
48✔
UNCOV
1182
  grid_[0] = get_node_array<double>(node, "r_grid");
×
1183
  grid_[1] = get_node_array<double>(node, "phi_grid");
48✔
1184
  grid_[2] = get_node_array<double>(node, "z_grid");
1185
  origin_ = get_node_position(node, "origin");
48✔
1186

312✔
1187
  if (int err = set_grid()) {
264✔
1188
    fatal_error(openmc_err_msg);
264✔
1189
  }
264✔
1190
}
1191

1192
const std::string CylindricalMesh::mesh_type = "cylindrical";
1193

48✔
1194
std::string CylindricalMesh::get_mesh_type() const
24✔
1195
{
1196
  return mesh_type;
2,084✔
1197
}
1198

2,084✔
1199
StructuredMesh::MeshIndex CylindricalMesh::get_indices(
2,084✔
1200
  Position r, bool& in_mesh) const
2,084✔
1201
{
2,084✔
1202
  local_coords(r);
2,084✔
1203

2,084✔
1204
  Position mapped_r;
1205
  mapped_r[0] = std::hypot(r.x, r.y);
11,678✔
1206
  mapped_r[2] = r[2];
1207

1208
  if (mapped_r[0] < FP_PRECISION) {
1209
    mapped_r[1] = 0.0;
11,678✔
1210
  } else {
11,678✔
1211
    mapped_r[1] = std::atan2(r.y, r.x);
1212
    if (mapped_r[1] < 0)
1213
      mapped_r[1] += 2 * M_PI;
11,678✔
1214
  }
11,678✔
1215

1216
  MeshIndex idx = StructuredMesh::get_indices(mapped_r, in_mesh);
11,460,565✔
1217

11,448,887✔
1218
  idx[1] = sanitize_phi(idx[1]);
1219

1220
  return idx;
11,448,887✔
1221
}
1222

1223
Position CylindricalMesh::sample_element(
11,448,887✔
UNCOV
1224
  const MeshIndex& ijk, uint64_t* seed) const
×
UNCOV
1225
{
×
1226
  double r_min = this->r(ijk[0] - 1);
1227
  double r_max = this->r(ijk[0]);
1228

1229
  double phi_min = this->phi(ijk[1] - 1);
11,448,887✔
1230
  double phi_max = this->phi(ijk[1]);
1231

1232
  double z_min = this->z(ijk[2] - 1);
1233
  double z_max = this->z(ijk[2]);
1234

1235
  double r_min_sq = r_min * r_min;
11,678✔
1236
  double r_max_sq = r_max * r_max;
11,678✔
1237
  double r = std::sqrt(uniform_distribution(r_min_sq, r_max_sq, seed));
1238
  double phi = uniform_distribution(phi_min, phi_max, seed);
1239
  double z = uniform_distribution(z_min, z_max, seed);
1240

6,750✔
1241
  double x = r * std::cos(phi);
6,750✔
1242
  double y = r * std::sin(phi);
1243

1244
  return origin_ + Position(x, y, z);
6,750✔
1245
}
6,750✔
1246

1247
double CylindricalMesh::find_r_crossing(
1248
  const Position& r, const Direction& u, double l, int shell) const
4,928✔
1249
{
4,928✔
1250

4,928✔
1251
  if ((shell < 0) || (shell > shape_[0]))
1252
    return INFTY;
1253

1254
  // solve r.x^2 + r.y^2 == r0^2
11,678✔
1255
  // x^2 + 2*s*u*x + s^2*u^2 + s^2*v^2+2*s*v*y + y^2 -r0^2 = 0
11,678✔
1256
  // s^2 * (u^2 + v^2) + 2*s*(u*x+v*y) + x^2+y^2-r0^2 = 0
1257

23,356✔
1258
  const double r0 = grid_[0][shell];
11,678✔
1259
  if (r0 == 0.0)
1260
    return INFTY;
982,884✔
1261

1262
  const double denominator = u.x * u.x + u.y * u.y;
982,884✔
1263

1264
  // Direction of flight is in z-direction. Will never intersect r.
1265
  if (std::abs(denominator) < FP_PRECISION)
1266
    return INFTY;
1267

1268
  // inverse of dominator to help the compiler to speed things up
1269
  const double inv_denominator = 1.0 / denominator;
111✔
1270

1271
  const double p = (u.x * r.x + u.y * r.y) * inv_denominator;
111✔
1272
  double c = r.x * r.x + r.y * r.y - r0 * r0;
1273
  double D = p * p - c * inv_denominator;
111✔
1274

111✔
1275
  if (D < 0.0)
111✔
1276
    return INFTY;
1277

111✔
UNCOV
1278
  D = std::sqrt(D);
×
1279

1280
  // the solution -p - D is always smaller as -p + D : Check this one first
111✔
1281
  if (std::abs(c) <= RADIAL_MESH_TOL)
1282
    return INFTY;
1283

1284
  if (-p - D > l)
320✔
1285
    return -p - D;
1286
  if (-p + D > l)
320✔
1287
    return -p + D;
1288

1289
  return INFTY;
52,807,226✔
1290
}
1291

1292
double CylindricalMesh::find_phi_crossing(
52,807,226✔
1293
  const Position& r, const Direction& u, double l, int shell) const
1294
{
1295
  // Phi grid is [0, 2Ï€], thus there is no real surface to cross
52,350,481✔
1296
  if (full_phi_ && (shape_[1] == 1))
1297
    return INFTY;
1298

52,350,481✔
1299
  shell = sanitize_phi(shell);
1300

1301
  const double p0 = grid_[1][shell];
103,273,884✔
1302

1303
  // solve y(s)/x(s) = tan(p0) = sin(p0)/cos(p0)
1304
  // => x(s) * cos(p0) = y(s) * sin(p0)
1305
  // => (y + s * v) * cos(p0) = (x + s * u) * sin(p0)
103,273,884✔
1306
  // = s * (v * cos(p0) - u * sin(p0)) = - (y * cos(p0) - x * sin(p0))
103,273,884✔
1307

103,273,884✔
UNCOV
1308
  const double c0 = std::cos(p0);
×
1309
  const double s0 = std::sin(p0);
1310

103,273,884✔
1311
  const double denominator = (u.x * s0 - u.y * c0);
103,273,884✔
1312

51,367,226✔
1313
  // Check if direction of flight is not parallel to phi surface
51,367,226✔
1314
  if (std::abs(denominator) > FP_PRECISION) {
51,906,658✔
1315
    const double s = -(r.x * s0 - r.y * c0) / denominator;
50,910,481✔
1316
    // Check if solution is in positive direction of flight and crosses the
50,910,481✔
1317
    // correct phi surface (not -phi)
1318
    if ((s > l) && ((c0 * (r.x + s * u.x) + s0 * (r.y + s * u.y)) > 0.0))
103,273,884✔
1319
      return s;
1320
  }
1321

181✔
1322
  return INFTY;
1323
}
181✔
1324

181✔
1325
StructuredMesh::MeshDistance CylindricalMesh::find_z_crossing(
181✔
1326
  const Position& r, const Direction& u, double l, int shell) const
1327
{
724✔
1328
  MeshDistance d;
543✔
UNCOV
1329
  d.next_index = shell;
×
1330

1331
  // Direction of flight is within xy-plane. Will never intersect z.
×
1332
  if (std::abs(u.z) < FP_PRECISION)
1333
    return d;
543✔
1334

1,086✔
UNCOV
1335
  d.max_surface = (u.z > 0.0);
×
1336
  if (d.max_surface && (shell <= shape_[2])) {
UNCOV
1337
    d.next_index += 1;
×
1338
    d.distance = (grid_[2][shell] - r.z) / u.z;
1339
  } else if (!d.max_surface && (shell > 0)) {
1340
    d.next_index -= 1;
1341
    d.distance = (grid_[2][shell - 1] - r.z) / u.z;
181✔
1342
  }
181✔
1343
  return d;
1344
}
181✔
1345

1346
StructuredMesh::MeshDistance CylindricalMesh::distance_to_grid_boundary(
1347
  const MeshIndex& ijk, int i, const Position& r0, const Direction& u,
147,970,326✔
1348
  double l) const
1349
{
147,970,326✔
1350
  Position r = r0 - origin_;
1351

1352
  if (i == 0) {
12✔
1353

1354
    return std::min(
1355
      MeshDistance(ijk[i] + 1, true, find_r_crossing(r, u, l, ijk[i])),
1356
      MeshDistance(ijk[i] - 1, false, find_r_crossing(r, u, l, ijk[i] - 1)));
12✔
1357

12✔
1358
  } else if (i == 1) {
×
1359

12✔
1360
    return std::min(MeshDistance(sanitize_phi(ijk[i] + 1), true,
12✔
UNCOV
1361
                      find_phi_crossing(r, u, l, ijk[i])),
×
UNCOV
1362
      MeshDistance(sanitize_phi(ijk[i] - 1), false,
×
1363
        find_phi_crossing(r, u, l, ijk[i] - 1)));
UNCOV
1364

×
1365
  } else {
1366
    return find_z_crossing(r, u, l, ijk[i]);
1367
  }
1368
}
12✔
1369

36✔
1370
int CylindricalMesh::set_grid()
24✔
1371
{
24✔
1372
  shape_ = {static_cast<int>(grid_[0].size()) - 1,
1373
    static_cast<int>(grid_[1].size()) - 1,
120✔
1374
    static_cast<int>(grid_[2].size()) - 1};
96✔
1375

96✔
1376
  for (const auto& g : grid_) {
1377
    if (g.size() < 2) {
1378
      set_errmsg("r-, phi-, and z- grids for cylindrical meshes "
1379
                 "must each have at least 2 points");
24✔
1380
      return OPENMC_E_INVALID_ARGUMENT;
12✔
1381
    }
1382
    if (std::adjacent_find(g.begin(), g.end(), std::greater_equal<>()) !=
118✔
1383
        g.end()) {
1384
      set_errmsg("Values in for r-, phi-, and z- grids for "
118✔
1385
                 "cylindrical meshes must be sorted and unique.");
118✔
1386
      return OPENMC_E_INVALID_ARGUMENT;
118✔
1387
    }
118✔
1388
  }
118✔
1389
  if (grid_[0].front() < 0.0) {
1390
    set_errmsg("r-grid for "
228✔
1391
               "cylindrical meshes must start at r >= 0.");
1392
    return OPENMC_E_INVALID_ARGUMENT;
228✔
1393
  }
1394
  if (grid_[1].front() < 0.0) {
912✔
1395
    set_errmsg("phi-grid for "
684✔
1396
               "cylindrical meshes must start at phi >= 0.");
1397
    return OPENMC_E_INVALID_ARGUMENT;
228✔
1398
  }
1399
  if (grid_[1].back() > 2.0 * PI) {
1400
    set_errmsg("phi-grids for "
1401
               "cylindrical meshes must end with theta <= 2*pi.");
1402

1403
    return OPENMC_E_INVALID_ARGUMENT;
1404
  }
401✔
1405

401✔
1406
  full_phi_ = (grid_[1].front() == 0.0) && (grid_[1].back() == 2.0 * PI);
1407

401✔
1408
  lower_left_ = {origin_[0] - grid_[0].back(), origin_[1] - grid_[0].back(),
401✔
1409
    origin_[2] + grid_[2].front()};
401✔
1410
  upper_right_ = {origin_[0] + grid_[0].back(), origin_[1] + grid_[0].back(),
401✔
1411
    origin_[2] + grid_[2].back()};
401✔
1412

1413
  return 0;
401✔
1414
}
×
1415

1416
int CylindricalMesh::get_index_in_direction(double r, int i) const
401✔
1417
{
1418
  return lower_bound_index(grid_[i].begin(), grid_[i].end(), r) + 1;
1419
}
1420

504✔
1421
std::pair<vector<double>, vector<double>> CylindricalMesh::plot(
1422
  Position plot_ll, Position plot_ur) const
504✔
1423
{
1424
  fatal_error("Plot of cylindrical Mesh not implemented");
1425

49,788,192✔
1426
  // Figure out which axes lie in the plane of the plot.
1427
  array<vector<double>, 2> axis_lines;
1428
  return {axis_lines[0], axis_lines[1]};
49,788,192✔
1429
}
1430

49,788,192✔
1431
void CylindricalMesh::to_hdf5_inner(hid_t mesh_group) const
49,788,192✔
1432
{
49,788,192✔
1433
  write_dataset(mesh_group, "type", "cylindrical");
1434
  write_dataset(mesh_group, "r_grid", grid_[0]);
49,788,192✔
UNCOV
1435
  write_dataset(mesh_group, "phi_grid", grid_[1]);
×
1436
  write_dataset(mesh_group, "z_grid", grid_[2]);
1437
  write_dataset(mesh_group, "origin", origin_);
49,788,192✔
1438
}
49,788,192✔
1439

24,869,868✔
1440
double CylindricalMesh::volume(const MeshIndex& ijk) const
1441
{
1442
  double r_i = grid_[0][ijk[0] - 1];
49,788,192✔
1443
  double r_o = grid_[0][ijk[0]];
1444

49,788,192✔
1445
  double phi_i = grid_[1][ijk[1] - 1];
1446
  double phi_o = grid_[1][ijk[1]];
49,788,192✔
1447

1448
  double z_i = grid_[2][ijk[2] - 1];
1449
  double z_o = grid_[2][ijk[2]];
816,000✔
1450

1451
  return 0.5 * (r_o * r_o - r_i * r_i) * (phi_o - phi_i) * (z_o - z_i);
1452
}
816,000✔
1453

816,000✔
1454
//==============================================================================
1455
// SphericalMesh implementation
816,000✔
1456
//==============================================================================
816,000✔
1457

1458
SphericalMesh::SphericalMesh(pugi::xml_node node)
816,000✔
1459
  : PeriodicStructuredMesh {node}
816,000✔
1460
{
1461
  n_dimension_ = 3;
816,000✔
1462

816,000✔
1463
  grid_[0] = get_node_array<double>(node, "r_grid");
816,000✔
1464
  grid_[1] = get_node_array<double>(node, "theta_grid");
816,000✔
1465
  grid_[2] = get_node_array<double>(node, "phi_grid");
816,000✔
1466
  origin_ = get_node_position(node, "origin");
1467

816,000✔
1468
  if (int err = set_grid()) {
816,000✔
1469
    fatal_error(openmc_err_msg);
1470
  }
816,000✔
1471
}
1472

1473
const std::string SphericalMesh::mesh_type = "spherical";
146,628,600✔
1474

1475
std::string SphericalMesh::get_mesh_type() const
1476
{
1477
  return mesh_type;
146,628,600✔
1478
}
17,850,072✔
1479

1480
StructuredMesh::MeshIndex SphericalMesh::get_indices(
1481
  Position r, bool& in_mesh) const
1482
{
1483
  local_coords(r);
1484

128,778,528✔
1485
  Position mapped_r;
128,778,528✔
1486
  mapped_r[0] = r.norm();
7,044,084✔
1487

1488
  if (mapped_r[0] < FP_PRECISION) {
121,734,444✔
1489
    mapped_r[1] = 0.0;
1490
    mapped_r[2] = 0.0;
1491
  } else {
121,734,444✔
UNCOV
1492
    mapped_r[1] = std::acos(r.z / mapped_r.x);
×
1493
    mapped_r[2] = std::atan2(r.y, r.x);
1494
    if (mapped_r[2] < 0)
1495
      mapped_r[2] += 2 * M_PI;
121,734,444✔
1496
  }
1497

121,734,444✔
1498
  MeshIndex idx = StructuredMesh::get_indices(mapped_r, in_mesh);
121,734,444✔
1499

121,734,444✔
1500
  idx[1] = sanitize_theta(idx[1]);
1501
  idx[2] = sanitize_phi(idx[2]);
121,734,444✔
1502

16,859,556✔
1503
  return idx;
1504
}
104,874,888✔
1505

1506
Position SphericalMesh::sample_element(
1507
  const MeshIndex& ijk, uint64_t* seed) const
104,874,888✔
1508
{
7,057,536✔
1509
  double r_min = this->r(ijk[0] - 1);
1510
  double r_max = this->r(ijk[0]);
97,817,352✔
1511

19,920,936✔
1512
  double theta_min = this->theta(ijk[1] - 1);
77,896,416✔
1513
  double theta_max = this->theta(ijk[1]);
47,239,464✔
1514

1515
  double phi_min = this->phi(ijk[2] - 1);
30,656,952✔
1516
  double phi_max = this->phi(ijk[2]);
1517

1518
  double cos_theta = uniform_distribution(theta_min, theta_max, seed);
72,236,472✔
1519
  double sin_theta = std::sin(std::acos(cos_theta));
1520
  double phi = uniform_distribution(phi_min, phi_max, seed);
1521
  double r_min_cub = std::pow(r_min, 3);
1522
  double r_max_cub = std::pow(r_max, 3);
72,236,472✔
1523
  // might be faster to do rejection here?
32,466,432✔
1524
  double r = std::cbrt(uniform_distribution(r_min_cub, r_max_cub, seed));
1525

39,770,040✔
1526
  double x = r * std::cos(phi) * sin_theta;
1527
  double y = r * std::sin(phi) * sin_theta;
39,770,040✔
1528
  double z = r * cos_theta;
1529

1530
  return origin_ + Position(x, y, z);
1531
}
1532

1533
double SphericalMesh::find_r_crossing(
1534
  const Position& r, const Direction& u, double l, int shell) const
39,770,040✔
1535
{
39,770,040✔
1536
  if ((shell < 0) || (shell > shape_[0]))
1537
    return INFTY;
39,770,040✔
1538

1539
  // solve |r+s*u| = r0
1540
  // |r+s*u| = |r| + 2*s*r*u + s^2 (|u|==1 !)
39,770,040✔
1541
  const double r0 = grid_[0][shell];
39,770,040✔
1542
  if (r0 == 0.0)
1543
    return INFTY;
1544
  const double p = r.dot(u);
39,770,040✔
1545
  double c = r.dot(r) - r0 * r0;
18,739,656✔
1546
  double D = p * p - c;
1547

1548
  if (std::abs(c) <= RADIAL_MESH_TOL)
21,030,384✔
1549
    return INFTY;
1550

1551
  if (D >= 0.0) {
39,212,208✔
1552
    D = std::sqrt(D);
1553
    // the solution -p - D is always smaller as -p + D : Check this one first
1554
    if (-p - D > l)
39,212,208✔
1555
      return -p - D;
39,212,208✔
1556
    if (-p + D > l)
1557
      return -p + D;
1558
  }
39,212,208✔
1559

480,000✔
1560
  return INFTY;
1561
}
38,732,208✔
1562

38,732,208✔
1563
double SphericalMesh::find_theta_crossing(
17,729,088✔
1564
  const Position& r, const Direction& u, double l, int shell) const
17,729,088✔
1565
{
21,003,120✔
1566
  // Theta grid is [0, π], thus there is no real surface to cross
18,037,548✔
1567
  if (full_theta_ && (shape_[1] == 1))
18,037,548✔
1568
    return INFTY;
1569

38,732,208✔
1570
  shell = sanitize_theta(shell);
1571

1572
  // solving z(s) = cos/theta) * r(s) with r(s) = r+s*u
148,644,744✔
1573
  // yields
1574
  // a*s^2 + 2*b*s + c == 0 with
1575
  // a = cos(theta)^2 - u.z * u.z
1576
  // b = r*u * cos(theta)^2 - u.z * r.z
148,644,744✔
1577
  // c = r*r * cos(theta)^2 - r.z^2
1578

148,644,744✔
1579
  const double cos_t = std::cos(grid_[1][shell]);
1580
  const bool sgn = std::signbit(cos_t);
73,314,300✔
1581
  const double cos_t_2 = cos_t * cos_t;
73,314,300✔
1582

146,628,600✔
1583
  const double a = cos_t_2 - u.z * u.z;
1584
  const double b = r.dot(u) * cos_t_2 - r.z * u.z;
75,330,444✔
1585
  const double c = r.dot(r) * cos_t_2 - r.z * r.z;
1586

36,118,236✔
1587
  // if factor of s^2 is zero, direction of flight is parallel to theta surface
36,118,236✔
1588
  if (std::abs(a) < FP_PRECISION) {
36,118,236✔
1589
    // if b vanishes, direction of flight is within theta surface and crossing
72,236,472✔
1590
    // is not possible
1591
    if (std::abs(b) < FP_PRECISION)
1592
      return INFTY;
39,212,208✔
1593

1594
    const double s = -0.5 * c / b;
1595
    // Check if solution is in positive direction of flight and has correct sign
1596
    if ((s > l) && (std::signbit(r.z + s * u.z) == sgn))
425✔
1597
      return s;
1598

425✔
1599
    // no crossing is possible
425✔
1600
    return INFTY;
425✔
1601
  }
1602

1,700✔
1603
  const double p = b / a;
1,275✔
UNCOV
1604
  double D = p * p - c / a;
×
1605

1606
  if (D < 0.0)
×
1607
    return INFTY;
1608

1,275✔
1609
  D = std::sqrt(D);
2,550✔
UNCOV
1610

×
1611
  // the solution -p-D is always smaller as -p+D : Check this one first
1612
  double s = -p - D;
×
1613
  // Check if solution is in positive direction of flight and has correct sign
1614
  if ((s > l) && (std::signbit(r.z + s * u.z) == sgn))
1615
    return s;
425✔
UNCOV
1616

×
1617
  s = -p + D;
UNCOV
1618
  // Check if solution is in positive direction of flight and has correct sign
×
1619
  if ((s > l) && (std::signbit(r.z + s * u.z) == sgn))
1620
    return s;
425✔
UNCOV
1621

×
1622
  return INFTY;
UNCOV
1623
}
×
1624

1625
double SphericalMesh::find_phi_crossing(
425✔
UNCOV
1626
  const Position& r, const Direction& u, double l, int shell) const
×
1627
{
1628
  // Phi grid is [0, 2Ï€], thus there is no real surface to cross
UNCOV
1629
  if (full_phi_ && (shape_[2] == 1))
×
1630
    return INFTY;
1631

1632
  shell = sanitize_phi(shell);
425✔
1633

1634
  const double p0 = grid_[2][shell];
850✔
1635

850✔
1636
  // solve y(s)/x(s) = tan(p0) = sin(p0)/cos(p0)
850✔
1637
  // => x(s) * cos(p0) = y(s) * sin(p0)
850✔
1638
  // => (y + s * v) * cos(p0) = (x + s * u) * sin(p0)
1639
  // = s * (v * cos(p0) - u * sin(p0)) = - (y * cos(p0) - x * sin(p0))
425✔
1640

1641
  const double c0 = std::cos(p0);
1642
  const double s0 = std::sin(p0);
149,364,576✔
1643

1644
  const double denominator = (u.x * s0 - u.y * c0);
149,364,576✔
1645

1646
  // Check if direction of flight is not parallel to phi surface
UNCOV
1647
  if (std::abs(denominator) > FP_PRECISION) {
×
1648
    const double s = -(r.x * s0 - r.y * c0) / denominator;
1649
    // Check if solution is in positive direction of flight and crosses the
UNCOV
1650
    // correct phi surface (not -phi)
×
1651
    if ((s > l) && ((c0 * (r.x + s * u.x) + s0 * (r.y + s * u.y)) > 0.0))
1652
      return s;
1653
  }
1654

1655
  return INFTY;
1656
}
1657

396✔
1658
StructuredMesh::MeshDistance SphericalMesh::distance_to_grid_boundary(
1659
  const MeshIndex& ijk, int i, const Position& r0, const Direction& u,
396✔
1660
  double l) const
396✔
1661
{
396✔
1662

396✔
1663
  if (i == 0) {
396✔
1664
    return std::min(
396✔
1665
      MeshDistance(ijk[i] + 1, true, find_r_crossing(r0, u, l, ijk[i])),
1666
      MeshDistance(ijk[i] - 1, false, find_r_crossing(r0, u, l, ijk[i] - 1)));
336✔
1667

1668
  } else if (i == 1) {
336✔
1669
    return std::min(MeshDistance(sanitize_theta(ijk[i] + 1), true,
336✔
1670
                      find_theta_crossing(r0, u, l, ijk[i])),
1671
      MeshDistance(sanitize_theta(ijk[i] - 1), false,
336✔
1672
        find_theta_crossing(r0, u, l, ijk[i] - 1)));
336✔
1673

1674
  } else {
336✔
1675
    return std::min(MeshDistance(sanitize_phi(ijk[i] + 1), true,
336✔
1676
                      find_phi_crossing(r0, u, l, ijk[i])),
1677
      MeshDistance(sanitize_phi(ijk[i] - 1), false,
336✔
1678
        find_phi_crossing(r0, u, l, ijk[i] - 1)));
1679
  }
1680
}
1681

1682
int SphericalMesh::set_grid()
1683
{
1684
  shape_ = {static_cast<int>(grid_[0].size()) - 1,
317✔
1685
    static_cast<int>(grid_[1].size()) - 1,
317✔
1686
    static_cast<int>(grid_[2].size()) - 1};
1687

317✔
1688
  for (const auto& g : grid_) {
1689
    if (g.size() < 2) {
317✔
1690
      set_errmsg("x-, y-, and z- grids for spherical meshes "
317✔
1691
                 "must each have at least 2 points");
317✔
1692
      return OPENMC_E_INVALID_ARGUMENT;
317✔
1693
    }
1694
    if (std::adjacent_find(g.begin(), g.end(), std::greater_equal<>()) !=
317✔
1695
        g.end()) {
×
1696
      set_errmsg("Values in for r-, theta-, and phi- grids for "
1697
                 "spherical meshes must be sorted and unique.");
317✔
1698
      return OPENMC_E_INVALID_ARGUMENT;
1699
    }
1700
    if (g.front() < 0.0) {
1701
      set_errmsg("r-, theta-, and phi- grids for "
372✔
1702
                 "spherical meshes must start at v >= 0.");
1703
      return OPENMC_E_INVALID_ARGUMENT;
372✔
1704
    }
1705
  }
1706
  if (grid_[1].back() > PI) {
73,258,044✔
1707
    set_errmsg("theta-grids for "
1708
               "spherical meshes must end with theta <= pi.");
1709

73,258,044✔
1710
    return OPENMC_E_INVALID_ARGUMENT;
1711
  }
73,258,044✔
1712
  if (grid_[2].back() > 2 * PI) {
73,258,044✔
1713
    set_errmsg("phi-grids for "
1714
               "spherical meshes must end with phi <= 2*pi.");
73,258,044✔
1715
    return OPENMC_E_INVALID_ARGUMENT;
×
1716
  }
×
1717

1718
  full_theta_ = (grid_[1].front() == 0.0) && (grid_[1].back() == PI);
73,258,044✔
1719
  full_phi_ = (grid_[2].front() == 0.0) && (grid_[2].back() == 2 * PI);
73,258,044✔
1720

73,258,044✔
1721
  double r = grid_[0].back();
36,598,944✔
1722
  lower_left_ = {origin_[0] - r, origin_[1] - r, origin_[2] - r};
1723
  upper_right_ = {origin_[0] + r, origin_[1] + r, origin_[2] + r};
1724

73,258,044✔
1725
  return 0;
1726
}
73,258,044✔
1727

73,258,044✔
1728
int SphericalMesh::get_index_in_direction(double r, int i) const
1729
{
73,258,044✔
1730
  return lower_bound_index(grid_[i].begin(), grid_[i].end(), r) + 1;
1731
}
1732

1,440,000✔
1733
std::pair<vector<double>, vector<double>> SphericalMesh::plot(
1734
  Position plot_ll, Position plot_ur) const
1735
{
1,440,000✔
1736
  fatal_error("Plot of spherical Mesh not implemented");
1,440,000✔
1737

1738
  // Figure out which axes lie in the plane of the plot.
1,440,000✔
1739
  array<vector<double>, 2> axis_lines;
1,440,000✔
1740
  return {axis_lines[0], axis_lines[1]};
1741
}
1,440,000✔
1742

1,440,000✔
1743
void SphericalMesh::to_hdf5_inner(hid_t mesh_group) const
1744
{
1,440,000✔
1745
  write_dataset(mesh_group, "type", SphericalMesh::mesh_type);
1,440,000✔
1746
  write_dataset(mesh_group, "r_grid", grid_[0]);
1,440,000✔
1747
  write_dataset(mesh_group, "theta_grid", grid_[1]);
1,440,000✔
1748
  write_dataset(mesh_group, "phi_grid", grid_[2]);
1,440,000✔
1749
  write_dataset(mesh_group, "origin", origin_);
1750
}
1,440,000✔
1751

1752
double SphericalMesh::volume(const MeshIndex& ijk) const
1,440,000✔
1753
{
1,440,000✔
1754
  double r_i = grid_[0][ijk[0] - 1];
1,440,000✔
1755
  double r_o = grid_[0][ijk[0]];
1756

1,440,000✔
1757
  double theta_i = grid_[1][ijk[1] - 1];
1758
  double theta_o = grid_[1][ijk[1]];
1759

480,342,048✔
1760
  double phi_i = grid_[2][ijk[2] - 1];
1761
  double phi_o = grid_[2][ijk[2]];
1762

480,342,048✔
1763
  return (1.0 / 3.0) * (r_o * r_o * r_o - r_i * r_i * r_i) *
43,677,828✔
1764
         (std::cos(theta_i) - std::cos(theta_o)) * (phi_o - phi_i);
1765
}
1766

1767
//==============================================================================
436,664,220✔
1768
// Helper functions for the C API
436,664,220✔
1769
//==============================================================================
7,577,856✔
1770

429,086,364✔
1771
int check_mesh(int32_t index)
429,086,364✔
1772
{
429,086,364✔
1773
  if (index < 0 || index >= model::meshes.size()) {
1774
    set_errmsg("Index in meshes array is out of bounds.");
429,086,364✔
1775
    return OPENMC_E_OUT_OF_BOUNDS;
11,548,560✔
1776
  }
1777
  return 0;
417,537,804✔
1778
}
388,160,352✔
1779

1780
template<class T>
388,160,352✔
1781
int check_mesh_type(int32_t index)
69,316,164✔
1782
{
318,844,188✔
1783
  if (int err = check_mesh(index))
192,088,092✔
1784
    return err;
1785

1786
  T* mesh = dynamic_cast<T*>(model::meshes[index].get());
156,133,548✔
1787
  if (!mesh) {
1788
    set_errmsg("This function is not valid for input mesh.");
1789
    return OPENMC_E_INVALID_TYPE;
117,164,688✔
1790
  }
1791
  return 0;
1792
}
1793

117,164,688✔
1794
template<class T>
76,498,272✔
1795
bool is_mesh_type(int32_t index)
1796
{
40,666,416✔
1797
  T* mesh = dynamic_cast<T*>(model::meshes[index].get());
1798
  return mesh;
1799
}
1800

1801
//==============================================================================
1802
// C API functions
1803
//==============================================================================
1804

1805
// Return the type of mesh as a C string
40,666,416✔
1806
extern "C" int openmc_mesh_get_type(int32_t index, char* type)
40,666,416✔
1807
{
40,666,416✔
1808
  if (int err = check_mesh(index))
1809
    return err;
40,666,416✔
1810

40,666,416✔
1811
  std::strcpy(type, model::meshes[index].get()->get_mesh_type().c_str());
40,666,416✔
1812

1813
  return 0;
1814
}
40,666,416✔
1815

1816
//! Extend the meshes array by n elements
UNCOV
1817
extern "C" int openmc_extend_meshes(
×
1818
  int32_t n, const char* type, int32_t* index_start, int32_t* index_end)
×
1819
{
UNCOV
1820
  if (index_start)
×
1821
    *index_start = model::meshes.size();
1822
  std::string mesh_type;
×
UNCOV
1823

×
1824
  for (int i = 0; i < n; ++i) {
1825
    if (RegularMesh::mesh_type == type) {
UNCOV
1826
      model::meshes.push_back(make_unique<RegularMesh>());
×
1827
    } else if (RectilinearMesh::mesh_type == type) {
1828
      model::meshes.push_back(make_unique<RectilinearMesh>());
1829
    } else if (CylindricalMesh::mesh_type == type) {
40,666,416✔
1830
      model::meshes.push_back(make_unique<CylindricalMesh>());
40,666,416✔
1831
    } else if (SphericalMesh::mesh_type == type) {
1832
      model::meshes.push_back(make_unique<SphericalMesh>());
40,666,416✔
1833
    } else {
11,532,864✔
1834
      throw std::runtime_error {"Unknown mesh type: " + std::string(type)};
1835
    }
29,133,552✔
1836
  }
1837
  if (index_end)
1838
    *index_end = model::meshes.size() - 1;
29,133,552✔
1839

1840
  return 0;
29,133,552✔
1841
}
5,527,560✔
1842

1843
//! Adds a new unstructured mesh to OpenMC
23,605,992✔
1844
extern "C" int openmc_add_unstructured_mesh(
1845
  const char filename[], const char library[], int* id)
23,605,992✔
1846
{
11,064,504✔
1847
  std::string lib_name(library);
1848
  std::string mesh_file(filename);
12,541,488✔
1849
  bool valid_lib = false;
1850

1851
#ifdef DAGMC
118,858,440✔
1852
  if (lib_name == MOABMesh::mesh_lib_type) {
1853
    model::meshes.push_back(std::move(make_unique<MOABMesh>(mesh_file)));
1854
    valid_lib = true;
1855
  }
118,858,440✔
1856
#endif
76,498,272✔
1857

1858
#ifdef LIBMESH
42,360,168✔
1859
  if (lib_name == LibMesh::mesh_lib_type) {
1860
    model::meshes.push_back(std::move(make_unique<LibMesh>(mesh_file)));
42,360,168✔
1861
    valid_lib = true;
1862
  }
1863
#endif
1864

1865
  if (!valid_lib) {
1866
    set_errmsg(fmt::format("Mesh library {} is not supported "
1867
                           "by this build of OpenMC",
42,360,168✔
1868
      lib_name));
42,360,168✔
1869
    return OPENMC_E_INVALID_ARGUMENT;
1870
  }
42,360,168✔
1871

1872
  // auto-assign new ID
1873
  model::meshes.back()->set_id(-1);
42,360,168✔
1874
  *id = model::meshes.back()->id_;
42,360,168✔
1875

1876
  return 0;
1877
}
42,360,168✔
1878

18,707,712✔
1879
//! Return the index in the meshes array of a mesh with a given ID
1880
extern "C" int openmc_get_mesh_index(int32_t id, int32_t* index)
1881
{
23,652,456✔
1882
  auto pair = model::mesh_map.find(id);
1883
  if (pair == model::mesh_map.end()) {
1884
    set_errmsg("No mesh exists with ID=" + std::to_string(id) + ".");
358,182,588✔
1885
    return OPENMC_E_INVALID_ID;
1886
  }
1887
  *index = pair->second;
1888
  return 0;
1889
}
358,182,588✔
1890

240,171,024✔
1891
//! Return the ID of a mesh
240,171,024✔
1892
extern "C" int openmc_mesh_get_id(int32_t index, int32_t* id)
480,342,048✔
1893
{
1894
  if (int err = check_mesh(index))
118,011,564✔
1895
    return err;
58,582,344✔
1896
  *id = model::meshes[index]->id_;
58,582,344✔
1897
  return 0;
58,582,344✔
1898
}
117,164,688✔
1899

1900
//! Set the ID of a mesh
1901
extern "C" int openmc_mesh_set_id(int32_t index, int32_t id)
59,429,220✔
1902
{
59,429,220✔
1903
  if (int err = check_mesh(index))
59,429,220✔
1904
    return err;
118,858,440✔
1905
  model::meshes[index]->id_ = id;
1906
  model::mesh_map[id] = index;
1907
  return 0;
1908
}
341✔
1909

1910
//! Get the number of elements in a mesh
341✔
1911
extern "C" int openmc_mesh_get_n_elements(int32_t index, size_t* n)
341✔
1912
{
341✔
1913
  if (int err = check_mesh(index))
1914
    return err;
1,364✔
1915
  *n = model::meshes[index]->n_bins();
1,023✔
UNCOV
1916
  return 0;
×
1917
}
1918

×
1919
//! Get the volume of each element in the mesh
1920
extern "C" int openmc_mesh_get_volumes(int32_t index, double* volumes)
1,023✔
1921
{
2,046✔
UNCOV
1922
  if (int err = check_mesh(index))
×
1923
    return err;
UNCOV
1924
  for (int i = 0; i < model::meshes[index]->n_bins(); ++i) {
×
1925
    volumes[i] = model::meshes[index]->volume(i);
1926
  }
1,023✔
UNCOV
1927
  return 0;
×
1928
}
1929

×
1930
//! Get the bounding box of a mesh
1931
extern "C" int openmc_mesh_bounding_box(int32_t index, double* ll, double* ur)
1932
{
341✔
UNCOV
1933
  if (int err = check_mesh(index))
×
1934
    return err;
1935

UNCOV
1936
  BoundingBox bbox = model::meshes[index]->bounding_box();
×
1937

1938
  // set lower left corner values
341✔
UNCOV
1939
  ll[0] = bbox.xmin;
×
1940
  ll[1] = bbox.ymin;
UNCOV
1941
  ll[2] = bbox.zmin;
×
1942

1943
  // set upper right corner values
1944
  ur[0] = bbox.xmax;
341✔
1945
  ur[1] = bbox.ymax;
341✔
1946
  ur[2] = bbox.zmax;
1947
  return 0;
341✔
1948
}
341✔
1949

341✔
1950
extern "C" int openmc_mesh_material_volumes(int32_t index, int n_sample,
1951
  int bin, int result_size, void* result, int* hits, uint64_t* seed)
341✔
1952
{
1953
  auto result_ = reinterpret_cast<Mesh::MaterialVolume*>(result);
1954
  if (!result_) {
219,774,132✔
1955
    set_errmsg("Invalid result pointer passed to openmc_mesh_material_volumes");
1956
    return OPENMC_E_INVALID_ARGUMENT;
219,774,132✔
1957
  }
1958

UNCOV
1959
  if (int err = check_mesh(index))
×
1960
    return err;
1961

UNCOV
1962
  int n = model::meshes[index]->material_volumes(
×
1963
    n_sample, bin, {result_, result_ + result_size}, seed);
1964
  *hits = n;
1965
  return (n == -1) ? OPENMC_E_ALLOCATE : 0;
1966
}
1967

1968
extern "C" int openmc_mesh_get_plot_bins(int32_t index, Position origin,
1969
  Position width, int basis, int* pixels, int32_t* data)
312✔
1970
{
1971
  if (int err = check_mesh(index))
312✔
1972
    return err;
312✔
1973
  const auto& mesh = model::meshes[index].get();
312✔
1974

312✔
1975
  int pixel_width = pixels[0];
312✔
1976
  int pixel_height = pixels[1];
312✔
1977

1978
  // get pixel size
600✔
1979
  double in_pixel = (width[0]) / static_cast<double>(pixel_width);
1980
  double out_pixel = (width[1]) / static_cast<double>(pixel_height);
600✔
1981

600✔
1982
  // setup basis indices and initial position centered on pixel
1983
  int in_i, out_i;
600✔
1984
  Position xyz = origin;
600✔
1985
  enum class PlotBasis { xy = 1, xz = 2, yz = 3 };
1986
  PlotBasis basis_enum = static_cast<PlotBasis>(basis);
600✔
1987
  switch (basis_enum) {
600✔
1988
  case PlotBasis::xy:
1989
    in_i = 0;
600✔
1990
    out_i = 1;
600✔
1991
    break;
1992
  case PlotBasis::xz:
1993
    in_i = 0;
1994
    out_i = 2;
1995
    break;
1996
  case PlotBasis::yz:
1997
    in_i = 1;
8,960✔
1998
    out_i = 2;
1999
    break;
8,960✔
2000
  default:
×
2001
    UNREACHABLE();
×
2002
  }
2003

8,960✔
2004
  // set initial position
2005
  xyz[in_i] = origin[in_i] - width[0] / 2. + in_pixel / 2.;
2006
  xyz[out_i] = origin[out_i] + width[1] / 2. - out_pixel / 2.;
2007

1,673✔
2008
#pragma omp parallel
2009
  {
1,673✔
2010
    Position r = xyz;
×
2011

2012
#pragma omp for
1,673✔
2013
    for (int y = 0; y < pixel_height; y++) {
1,673✔
2014
      r[out_i] = xyz[out_i] - out_pixel * y;
×
2015
      for (int x = 0; x < pixel_width; x++) {
×
2016
        r[in_i] = xyz[in_i] + in_pixel * x;
2017
        data[pixel_width * y + x] = mesh->get_bin(r);
1,673✔
2018
      }
2019
    }
156✔
2020
  }
2021

156✔
2022
  return 0;
×
2023
}
2024

156✔
2025
//! Get the dimension of a regular mesh
156✔
2026
extern "C" int openmc_regular_mesh_get_dimension(
×
2027
  int32_t index, int** dims, int* n)
×
2028
{
2029
  if (int err = check_mesh_type<RegularMesh>(index))
156✔
2030
    return err;
2031
  RegularMesh* mesh = dynamic_cast<RegularMesh*>(model::meshes[index].get());
156✔
2032
  *dims = mesh->shape_.data();
2033
  *n = mesh->n_dimension_;
156✔
2034
  return 0;
×
2035
}
2036

156✔
2037
//! Set the dimension of a regular mesh
156✔
2038
extern "C" int openmc_regular_mesh_set_dimension(
×
2039
  int32_t index, int n, const int* dims)
×
2040
{
2041
  if (int err = check_mesh_type<RegularMesh>(index))
156✔
2042
    return err;
2043
  RegularMesh* mesh = dynamic_cast<RegularMesh*>(model::meshes[index].get());
248✔
2044

2045
  // Copy dimension
248✔
2046
  mesh->n_dimension_ = n;
×
2047
  std::copy(dims, dims + n, mesh->shape_.begin());
2048
  return 0;
248✔
2049
}
248✔
2050

×
2051
//! Get the regular mesh parameters
×
2052
extern "C" int openmc_regular_mesh_get_params(
2053
  int32_t index, double** ll, double** ur, double** width, int* n)
248✔
2054
{
2055
  if (int err = check_mesh_type<RegularMesh>(index))
1,113✔
2056
    return err;
2057
  RegularMesh* m = dynamic_cast<RegularMesh*>(model::meshes[index].get());
1,113✔
2058

×
2059
  if (m->lower_left_.dimension() == 0) {
2060
    set_errmsg("Mesh parameters have not been set.");
1,113✔
2061
    return OPENMC_E_ALLOCATE;
1,113✔
2062
  }
×
2063

×
2064
  *ll = m->lower_left_.data();
2065
  *ur = m->upper_right_.data();
1,113✔
2066
  *width = m->width_.data();
2067
  *n = m->n_dimension_;
2068
  return 0;
2069
}
2070

2071
//! Set the regular mesh parameters
2072
extern "C" int openmc_regular_mesh_set_params(
2073
  int32_t index, int n, const double* ll, const double* ur, const double* width)
2074
{
2075
  if (int err = check_mesh_type<RegularMesh>(index))
2076
    return err;
2077
  RegularMesh* m = dynamic_cast<RegularMesh*>(model::meshes[index].get());
2078

2079
  if (m->n_dimension_ == -1) {
2080
    set_errmsg("Need to set mesh dimension before setting parameters.");
1,983✔
2081
    return OPENMC_E_UNASSIGNED;
2082
  }
1,983✔
2083

×
2084
  vector<std::size_t> shape = {static_cast<std::size_t>(n)};
2085
  if (ll && ur) {
1,983✔
2086
    m->lower_left_ = xt::adapt(ll, n, xt::no_ownership(), shape);
2087
    m->upper_right_ = xt::adapt(ur, n, xt::no_ownership(), shape);
1,983✔
2088
    m->width_ = (m->upper_right_ - m->lower_left_) / m->get_x_shape();
2089
  } else if (ll && width) {
2090
    m->lower_left_ = xt::adapt(ll, n, xt::no_ownership(), shape);
2091
    m->width_ = xt::adapt(width, n, xt::no_ownership(), shape);
441✔
2092
    m->upper_right_ = m->lower_left_ + m->get_x_shape() * m->width_;
2093
  } else if (ur && width) {
2094
    m->upper_right_ = xt::adapt(ur, n, xt::no_ownership(), shape);
441✔
2095
    m->width_ = xt::adapt(width, n, xt::no_ownership(), shape);
441✔
2096
    m->lower_left_ = m->upper_right_ - m->get_x_shape() * m->width_;
441✔
2097
  } else {
2098
    set_errmsg("At least two parameters must be specified.");
882✔
2099
    return OPENMC_E_INVALID_ARGUMENT;
441✔
2100
  }
323✔
2101

118✔
2102
  // Set material volumes
70✔
2103

48✔
2104
  // TODO: incorporate this into method in RegularMesh that can be called from
24✔
2105
  // here and from constructor
24✔
2106
  m->volume_frac_ = 1.0 / xt::prod(m->get_x_shape())();
24✔
2107
  m->element_volume_ = 1.0;
2108
  for (int i = 0; i < m->n_dimension_; i++) {
×
2109
    m->element_volume_ *= m->width_[i];
2110
  }
2111

441✔
2112
  return 0;
×
2113
}
2114

441✔
2115
//! Set the mesh parameters for rectilinear, cylindrical and spharical meshes
441✔
2116
template<class C>
2117
int openmc_structured_mesh_set_grid_impl(int32_t index, const double* grid_x,
2118
  const int nx, const double* grid_y, const int ny, const double* grid_z,
×
2119
  const int nz)
2120
{
2121
  if (int err = check_mesh_type<C>(index))
×
2122
    return err;
×
2123

×
2124
  C* m = dynamic_cast<C*>(model::meshes[index].get());
2125

2126
  m->n_dimension_ = 3;
2127

2128
  m->grid_[0].reserve(nx);
2129
  m->grid_[1].reserve(ny);
2130
  m->grid_[2].reserve(nz);
2131

2132
  for (int i = 0; i < nx; i++) {
2133
    m->grid_[0].push_back(grid_x[i]);
2134
  }
2135
  for (int i = 0; i < ny; i++) {
2136
    m->grid_[1].push_back(grid_y[i]);
2137
  }
2138
  for (int i = 0; i < nz; i++) {
2139
    m->grid_[2].push_back(grid_z[i]);
×
2140
  }
×
2141

2142
  int err = m->set_grid();
2143
  return err;
×
2144
}
2145

2146
//! Get the mesh parameters for rectilinear, cylindrical and spherical meshes
2147
template<class C>
×
2148
int openmc_structured_mesh_get_grid_impl(int32_t index, double** grid_x,
×
2149
  int* nx, double** grid_y, int* ny, double** grid_z, int* nz)
2150
{
×
2151
  if (int err = check_mesh_type<C>(index))
2152
    return err;
2153
  C* m = dynamic_cast<C*>(model::meshes[index].get());
2154

633✔
2155
  if (m->lower_left_.dimension() == 0) {
2156
    set_errmsg("Mesh parameters have not been set.");
633✔
2157
    return OPENMC_E_ALLOCATE;
633✔
2158
  }
×
2159

×
2160
  *grid_x = m->grid_[0].data();
2161
  *nx = m->grid_[0].size();
633✔
2162
  *grid_y = m->grid_[1].data();
633✔
2163
  *ny = m->grid_[1].size();
2164
  *grid_z = m->grid_[2].data();
2165
  *nz = m->grid_[2].size();
2166

4,095✔
2167
  return 0;
2168
}
4,095✔
2169

×
2170
//! Get the rectilinear mesh grid
4,095✔
2171
extern "C" int openmc_rectilinear_mesh_get_grid(int32_t index, double** grid_x,
4,095✔
2172
  int* nx, double** grid_y, int* ny, double** grid_z, int* nz)
2173
{
2174
  return openmc_structured_mesh_get_grid_impl<RectilinearMesh>(
2175
    index, grid_x, nx, grid_y, ny, grid_z, nz);
441✔
2176
}
2177

441✔
2178
//! Set the rectilienar mesh parameters
×
2179
extern "C" int openmc_rectilinear_mesh_set_grid(int32_t index,
441✔
2180
  const double* grid_x, const int nx, const double* grid_y, const int ny,
441✔
2181
  const double* grid_z, const int nz)
441✔
2182
{
2183
  return openmc_structured_mesh_set_grid_impl<RectilinearMesh>(
2184
    index, grid_x, nx, grid_y, ny, grid_z, nz);
2185
}
192✔
2186

2187
//! Get the cylindrical mesh grid
192✔
2188
extern "C" int openmc_cylindrical_mesh_get_grid(int32_t index, double** grid_x,
×
2189
  int* nx, double** grid_y, int* ny, double** grid_z, int* nz)
192✔
2190
{
192✔
2191
  return openmc_structured_mesh_get_grid_impl<CylindricalMesh>(
2192
    index, grid_x, nx, grid_y, ny, grid_z, nz);
2193
}
2194

96✔
2195
//! Set the cylindrical mesh parameters
2196
extern "C" int openmc_cylindrical_mesh_set_grid(int32_t index,
96✔
2197
  const double* grid_x, const int nx, const double* grid_y, const int ny,
×
2198
  const double* grid_z, const int nz)
1,056✔
2199
{
960✔
2200
  return openmc_structured_mesh_set_grid_impl<CylindricalMesh>(
2201
    index, grid_x, nx, grid_y, ny, grid_z, nz);
96✔
2202
}
2203

2204
//! Get the spherical mesh grid
2205
extern "C" int openmc_spherical_mesh_get_grid(int32_t index, double** grid_x,
48✔
2206
  int* nx, double** grid_y, int* ny, double** grid_z, int* nz)
2207
{
48✔
2208

×
2209
  return openmc_structured_mesh_get_grid_impl<SphericalMesh>(
2210
    index, grid_x, nx, grid_y, ny, grid_z, nz);
48✔
2211
  ;
2212
}
2213

48✔
2214
//! Set the spherical mesh parameters
48✔
2215
extern "C" int openmc_spherical_mesh_set_grid(int32_t index,
48✔
2216
  const double* grid_x, const int nx, const double* grid_y, const int ny,
2217
  const double* grid_z, const int nz)
2218
{
48✔
2219
  return openmc_structured_mesh_set_grid_impl<SphericalMesh>(
48✔
2220
    index, grid_x, nx, grid_y, ny, grid_z, nz);
48✔
2221
}
48✔
2222

2223
#ifdef DAGMC
2224

384✔
2225
const std::string MOABMesh::mesh_lib_type = "moab";
2226

2227
MOABMesh::MOABMesh(pugi::xml_node node) : UnstructuredMesh(node)
384✔
2228
{
384✔
2229
  initialize();
×
2230
}
×
2231

2232
MOABMesh::MOABMesh(const std::string& filename, double length_multiplier)
2233
{
384✔
2234
  filename_ = filename;
×
2235
  set_length_multiplier(length_multiplier);
2236
  initialize();
768✔
2237
}
384✔
2238

384✔
2239
MOABMesh::MOABMesh(std::shared_ptr<moab::Interface> external_mbi)
384✔
2240
{
2241
  mbi_ = external_mbi;
2242
  filename_ = "unknown (external file)";
48✔
2243
  this->initialize();
2244
}
2245

48✔
2246
void MOABMesh::initialize()
×
2247
{
48✔
2248

2249
  // Create the MOAB interface and load data from file
48✔
2250
  this->create_interface();
48✔
2251

2252
  // Initialise MOAB error code
2253
  moab::ErrorCode rval = moab::MB_SUCCESS;
48✔
2254

48✔
2255
  // Set the dimension
2256
  n_dimension_ = 3;
2257

2258
  // set member range of tetrahedral entities
48✔
2259
  rval = mbi_->get_entities_by_dimension(0, n_dimension_, ehs_);
2260
  if (rval != moab::MB_SUCCESS) {
48✔
2261
    fatal_error("Failed to get all tetrahedral elements");
48✔
2262
  }
48✔
2263

48✔
2264
  if (!ehs_.all_of_type(moab::MBTET)) {
48✔
2265
    warning("Non-tetrahedral elements found in unstructured "
48✔
2266
            "mesh file: " +
×
2267
            filename_);
×
2268
  }
×
2269

×
2270
  // set member range of vertices
×
2271
  int vertex_dim = 0;
×
2272
  rval = mbi_->get_entities_by_dimension(0, vertex_dim, verts_);
×
2273
  if (rval != moab::MB_SUCCESS) {
×
2274
    fatal_error("Failed to get all vertex handles");
×
2275
  }
×
2276

2277
  // make an entity set for all tetrahedra
2278
  // this is used for convenience later in output
2279
  rval = mbi_->create_meshset(moab::MESHSET_SET, tetset_);
48✔
2280
  if (rval != moab::MB_SUCCESS) {
48✔
2281
    fatal_error("Failed to create an entity set for the tetrahedral elements");
2282
  }
24✔
2283

2284
  rval = mbi_->add_entities(tetset_, ehs_);
24✔
2285
  if (rval != moab::MB_SUCCESS) {
2286
    fatal_error("Failed to add tetrahedra to an entity set.");
2287
  }
504✔
2288

480✔
2289
  if (length_multiplier_ > 0.0) {
10,080✔
2290
    // get the connectivity of all tets
9,600✔
2291
    moab::Range adj;
9,600✔
2292
    rval = mbi_->get_adjacencies(ehs_, 0, true, adj, moab::Interface::UNION);
2293
    if (rval != moab::MB_SUCCESS) {
2294
      fatal_error("Failed to get adjacent vertices of tetrahedra.");
2295
    }
2296
    // scale all vertex coords by multiplier (done individually so not all
48✔
2297
    // coordinates are in memory twice at once)
2298
    for (auto vert : adj) {
2299
      // retrieve coords
2300
      std::array<double, 3> coord;
12✔
2301
      rval = mbi_->get_coords(&vert, 1, coord.data());
2302
      if (rval != moab::MB_SUCCESS) {
2303
        fatal_error("Could not get coordinates of vertex.");
12✔
2304
      }
×
2305
      // scale coords
12✔
2306
      for (auto& c : coord) {
12✔
2307
        c *= length_multiplier_;
12✔
2308
      }
12✔
2309
      // set new coords
2310
      rval = mbi_->set_coords(&vert, 1, coord.data());
2311
      if (rval != moab::MB_SUCCESS) {
2312
        fatal_error("Failed to set new vertex coordinates");
347✔
2313
      }
2314
    }
2315
  }
347✔
2316

×
2317
  // Determine bounds of mesh
347✔
2318
  this->determine_bounds();
2319
}
2320

347✔
2321
void MOABMesh::prepare_for_point_location()
347✔
2322
{
347✔
2323
  // if the KDTree has already been constructed, do nothing
2324
  if (kdtree_)
2325
    return;
2326

371✔
2327
  // build acceleration data structures
2328
  compute_barycentric_data(ehs_);
2329
  build_kdtree(ehs_);
371✔
2330
}
×
2331

371✔
2332
void MOABMesh::create_interface()
2333
{
371✔
2334
  // Do not create a MOAB instance if one is already in memory
×
2335
  if (mbi_)
×
2336
    return;
2337

2338
  // create MOAB instance
371✔
2339
  mbi_ = std::make_shared<moab::Core>();
371✔
2340

371✔
2341
  // load unstructured mesh file
371✔
2342
  moab::ErrorCode rval = mbi_->load_file(filename_.c_str());
371✔
2343
  if (rval != moab::MB_SUCCESS) {
2344
    fatal_error("Failed to load the unstructured mesh file: " + filename_);
2345
  }
2346
}
383✔
2347

2348
void MOABMesh::build_kdtree(const moab::Range& all_tets)
2349
{
383✔
2350
  moab::Range all_tris;
×
2351
  int adj_dim = 2;
383✔
2352
  write_message("Getting tet adjacencies...", 7);
2353
  moab::ErrorCode rval = mbi_->get_adjacencies(
383✔
2354
    all_tets, adj_dim, true, all_tris, moab::Interface::UNION);
×
2355
  if (rval != moab::MB_SUCCESS) {
×
2356
    fatal_error("Failed to get adjacent triangles for tets");
2357
  }
2358

383✔
2359
  if (!all_tris.all_of_type(moab::MBTRI)) {
383✔
2360
    warning("Non-triangle elements found in tet adjacencies in "
359✔
2361
            "unstructured mesh file: " +
359✔
2362
            filename_);
359✔
2363
  }
24✔
2364

12✔
2365
  // combine into one range
12✔
2366
  moab::Range all_tets_and_tris;
12✔
2367
  all_tets_and_tris.merge(all_tets);
12✔
2368
  all_tets_and_tris.merge(all_tris);
12✔
2369

12✔
2370
  // create a kd-tree instance
12✔
2371
  write_message(
2372
    7, "Building adaptive k-d tree for tet mesh with ID {}...", id_);
×
2373
  kdtree_ = make_unique<moab::AdaptiveKDTree>(mbi_.get());
×
2374

2375
  // Determine what options to use
2376
  std::ostringstream options_stream;
2377
  if (options_.empty()) {
2378
    options_stream << "MAX_DEPTH=20;PLANE_SET=2;";
2379
  } else {
2380
    options_stream << options_;
383✔
2381
  }
383✔
2382
  moab::FileOptions file_opts(options_stream.str().c_str());
1,532✔
2383

1,149✔
2384
  // Build the k-d tree
2385
  rval = kdtree_->build_tree(all_tets_and_tris, &kdtree_root_, &file_opts);
2386
  if (rval != moab::MB_SUCCESS) {
383✔
2387
    fatal_error("Failed to construct KDTree for the "
383✔
2388
                "unstructured mesh file: " +
2389
                filename_);
2390
  }
2391
}
118✔
2392

2393
void MOABMesh::intersect_track(const moab::CartVect& start,
2394
  const moab::CartVect& dir, double track_len, vector<double>& hits) const
2395
{
118✔
2396
  hits.clear();
×
2397

2398
  moab::ErrorCode rval;
118✔
2399
  vector<moab::EntityHandle> tris;
2400
  // get all intersections with triangles in the tet mesh
118✔
2401
  // (distances are relative to the start point, not the previous intersection)
2402
  rval = kdtree_->ray_intersect_triangles(kdtree_root_, FP_COINCIDENT,
118✔
2403
    dir.array(), start.array(), tris, hits, 0, track_len);
118✔
2404
  if (rval != moab::MB_SUCCESS) {
118✔
2405
    fatal_error(
2406
      "Failed to compute intersections on unstructured mesh: " + filename_);
932✔
2407
  }
814✔
2408

2409
  // remove duplicate intersection distances
438✔
2410
  std::unique(hits.begin(), hits.end());
320✔
2411

2412
  // sorts by first component of std::pair by default
414✔
2413
  std::sort(hits.begin(), hits.end());
296✔
2414
}
2415

2416
void MOABMesh::bins_crossed(Position r0, Position r1, const Direction& u,
118✔
2417
  vector<int>& bins, vector<double>& lengths) const
118✔
2418
{
2419
  moab::CartVect start(r0.x, r0.y, r0.z);
24✔
2420
  moab::CartVect end(r1.x, r1.y, r1.z);
2421
  moab::CartVect dir(u.x, u.y, u.z);
2422
  dir.normalize();
2423

24✔
2424
  double track_len = (end - start).length();
×
2425
  if (track_len == 0.0)
2426
    return;
24✔
2427

2428
  start -= TINY_BIT * dir;
24✔
2429
  end += TINY_BIT * dir;
2430

24✔
2431
  vector<double> hits;
24✔
2432
  intersect_track(start, dir, track_len, hits);
24✔
2433

2434
  bins.clear();
96✔
2435
  lengths.clear();
72✔
2436

2437
  // if there are no intersections the track may lie entirely
96✔
2438
  // within a single tet. If this is the case, apply entire
72✔
2439
  // score to that tet and return.
2440
  if (hits.size() == 0) {
108✔
2441
    Position midpoint = r0 + u * (track_len * 0.5);
84✔
2442
    int bin = this->get_bin(midpoint);
2443
    if (bin != -1) {
2444
      bins.push_back(bin);
24✔
2445
      lengths.push_back(1.0);
24✔
2446
    }
2447
    return;
24✔
2448
  }
2449

2450
  // for each segment in the set of tracks, try to look up a tet
2451
  // at the midpoint of the segment
24✔
2452
  Position current = r0;
×
2453
  double last_dist = 0.0;
2454
  for (const auto& hit : hits) {
24✔
2455
    // get the segment length
2456
    double segment_length = hit - last_dist;
24✔
2457
    last_dist = hit;
2458
    // find the midpoint of this segment
24✔
2459
    Position midpoint = current + u * (segment_length * 0.5);
24✔
2460
    // try to find a tet for this position
24✔
2461
    int bin = this->get_bin(midpoint);
2462

96✔
2463
    // determine the start point for this segment
72✔
2464
    current = r0 + u * hit;
2465

108✔
2466
    if (bin == -1) {
84✔
2467
      continue;
2468
    }
84✔
2469

60✔
2470
    bins.push_back(bin);
2471
    lengths.push_back(segment_length / track_len);
2472
  }
24✔
2473

24✔
2474
  // tally remaining portion of track after last hit if
2475
  // the last segment of the track is in the mesh but doesn't
70✔
2476
  // reach the other side of the tet
2477
  if (hits.back() < track_len) {
2478
    Position segment_start = r0 + u * hits.back();
2479
    double segment_length = track_len - hits.back();
70✔
2480
    Position midpoint = segment_start + u * (segment_length * 0.5);
×
2481
    int bin = this->get_bin(midpoint);
2482
    if (bin != -1) {
70✔
2483
      bins.push_back(bin);
2484
      lengths.push_back(segment_length / track_len);
70✔
2485
    }
2486
  }
70✔
2487
};
70✔
2488

70✔
2489
moab::EntityHandle MOABMesh::get_tet(const Position& r) const
2490
{
740✔
2491
  moab::CartVect pos(r.x, r.y, r.z);
670✔
2492
  // find the leaf of the kd-tree for this position
2493
  moab::AdaptiveKDTreeIter kdtree_iter;
234✔
2494
  moab::ErrorCode rval = kdtree_->point_search(pos.array(), kdtree_iter);
164✔
2495
  if (rval != moab::MB_SUCCESS) {
2496
    return 0;
222✔
2497
  }
152✔
2498

2499
  // retrieve the tet elements of this leaf
2500
  moab::EntityHandle leaf = kdtree_iter.handle();
70✔
2501
  moab::Range tets;
70✔
2502
  rval = mbi_->get_entities_by_dimension(leaf, 3, tets, false);
2503
  if (rval != moab::MB_SUCCESS) {
2504
    warning("MOAB error finding tets.");
2505
  }
2506

442✔
2507
  // loop over the tets in this leaf, returning the containing tet if found
2508
  for (const auto& tet : tets) {
2509
    if (point_in_tet(pos, tet)) {
442✔
2510
      return tet;
×
2511
    }
442✔
2512
  }
2513

442✔
2514
  // if no tet is found, return an invalid handle
×
2515
  return 0;
×
2516
}
2517

2518
double MOABMesh::volume(int bin) const
442✔
2519
{
442✔
2520
  return tet_volume(get_ent_handle_from_bin(bin));
442✔
2521
}
442✔
2522

442✔
2523
std::string MOABMesh::library() const
442✔
2524
{
2525
  return mesh_lib_type;
442✔
2526
}
2527

132✔
2528
// Sample position within a tet for MOAB type tets
2529
Position MOABMesh::sample_element(int32_t bin, uint64_t* seed) const
2530
{
132✔
2531

×
2532
  moab::EntityHandle tet_ent = get_ent_handle_from_bin(bin);
132✔
2533

2534
  // Get vertex coordinates for MOAB tet
132✔
2535
  const moab::EntityHandle* conn1;
×
2536
  int conn1_size;
×
2537
  moab::ErrorCode rval = mbi_->get_connectivity(tet_ent, conn1, conn1_size);
2538
  if (rval != moab::MB_SUCCESS || conn1_size != 4) {
2539
    fatal_error(fmt::format(
132✔
2540
      "Failed to get tet connectivity or connectivity size ({}) is invalid.",
132✔
2541
      conn1_size));
132✔
2542
  }
132✔
2543
  moab::CartVect p[4];
132✔
2544
  rval = mbi_->get_coords(conn1, conn1_size, p[0].array());
132✔
2545
  if (rval != moab::MB_SUCCESS) {
2546
    fatal_error("Failed to get tet coords");
132✔
2547
  }
2548

132✔
2549
  std::array<Position, 4> tet_verts;
2550
  for (int i = 0; i < 4; i++) {
2551
    tet_verts[i] = {p[i][0], p[i][1], p[i][2]};
132✔
2552
  }
×
2553
  // Samples position within tet using Barycentric stuff
132✔
2554
  return this->sample_tet(tet_verts, seed);
2555
}
132✔
2556

×
2557
double MOABMesh::tet_volume(moab::EntityHandle tet) const
×
2558
{
2559
  vector<moab::EntityHandle> conn;
2560
  moab::ErrorCode rval = mbi_->get_connectivity(&tet, 1, conn);
132✔
2561
  if (rval != moab::MB_SUCCESS) {
132✔
2562
    fatal_error("Failed to get tet connectivity");
132✔
2563
  }
132✔
2564

132✔
2565
  moab::CartVect p[4];
132✔
2566
  rval = mbi_->get_coords(conn.data(), conn.size(), p[0].array());
2567
  if (rval != moab::MB_SUCCESS) {
132✔
2568
    fatal_error("Failed to get tet coords");
2569
  }
178✔
2570

2571
  return 1.0 / 6.0 * (((p[1] - p[0]) * (p[2] - p[0])) % (p[3] - p[0]));
2572
}
178✔
2573

×
2574
int MOABMesh::get_bin(Position r) const
178✔
2575
{
2576
  moab::EntityHandle tet = get_tet(r);
178✔
2577
  if (tet == 0) {
×
2578
    return -1;
×
2579
  } else {
2580
    return get_bin_from_ent_handle(tet);
2581
  }
178✔
2582
}
178✔
2583

178✔
2584
void MOABMesh::compute_barycentric_data(const moab::Range& tets)
178✔
2585
{
178✔
2586
  moab::ErrorCode rval;
178✔
2587

2588
  baryc_data_.clear();
178✔
2589
  baryc_data_.resize(tets.size());
2590

2591
  // compute the barycentric data for each tet element
2592
  // and store it as a 3x3 matrix
178✔
2593
  for (auto& tet : tets) {
2594
    vector<moab::EntityHandle> verts;
2595
    rval = mbi_->get_connectivity(&tet, 1, verts);
178✔
2596
    if (rval != moab::MB_SUCCESS) {
178✔
2597
      fatal_error("Failed to get connectivity of tet on umesh: " + filename_);
2598
    }
2599

2600
    moab::CartVect p[4];
70✔
2601
    rval = mbi_->get_coords(verts.data(), verts.size(), p[0].array());
2602
    if (rval != moab::MB_SUCCESS) {
2603
      fatal_error("Failed to get coordinates of a tet in umesh: " + filename_);
2604
    }
70✔
2605

70✔
2606
    moab::Matrix3 a(p[1] - p[0], p[2] - p[0], p[3] - p[0], true);
2607

2608
    // invert now to avoid this cost later
2609
    a = a.transpose().inverse();
132✔
2610
    baryc_data_.at(get_bin_from_ent_handle(tet)) = a;
2611
  }
2612
}
132✔
2613

132✔
2614
bool MOABMesh::point_in_tet(
2615
  const moab::CartVect& r, moab::EntityHandle tet) const
2616
{
2617

24✔
2618
  moab::ErrorCode rval;
2619

2620
  // get tet vertices
2621
  vector<moab::EntityHandle> verts;
24✔
2622
  rval = mbi_->get_connectivity(&tet, 1, verts);
24✔
2623
  if (rval != moab::MB_SUCCESS) {
2624
    warning("Failed to get vertices of tet in umesh: " + filename_);
2625
    return false;
2626
  }
132✔
2627

2628
  // first vertex is used as a reference point for the barycentric data -
2629
  // retrieve its coordinates
2630
  moab::CartVect p_zero;
132✔
2631
  rval = mbi_->get_coords(verts.data(), 1, p_zero.array());
132✔
2632
  if (rval != moab::MB_SUCCESS) {
2633
    warning("Failed to get coordinates of a vertex in "
2634
            "unstructured mesh: " +
2635
            filename_);
2636
    return false;
24✔
2637
  }
2638

2639
  // look up barycentric data
2640
  int idx = get_bin_from_ent_handle(tet);
24✔
2641
  const moab::Matrix3& a_inv = baryc_data_[idx];
24✔
2642

2643
  moab::CartVect bary_coords = a_inv * (r - p_zero);
2644

2645
  return (bary_coords[0] >= 0.0 && bary_coords[1] >= 0.0 &&
2646
          bary_coords[2] >= 0.0 &&
2647
          bary_coords[0] + bary_coords[1] + bary_coords[2] <= 1.0);
2648
}
22✔
2649

2650
int MOABMesh::get_bin_from_index(int idx) const
22✔
2651
{
22✔
2652
  if (idx >= n_bins()) {
2653
    fatal_error(fmt::format("Invalid bin index: {}", idx));
2654
  }
2655
  return ehs_[idx] - ehs_[0];
2656
}
2657

2658
int MOABMesh::get_index(const Position& r, bool* in_mesh) const
2659
{
2660
  int bin = get_bin(r);
1✔
2661
  *in_mesh = bin != -1;
2662
  return bin;
1✔
2663
}
1✔
2664

1✔
2665
int MOABMesh::get_index_from_bin(int bin) const
1✔
2666
{
2667
  return bin;
23✔
2668
}
2669

2670
std::pair<vector<double>, vector<double>> MOABMesh::plot(
2671
  Position plot_ll, Position plot_ur) const
23✔
2672
{
2673
  // TODO: Implement mesh lines
2674
  return {};
23✔
2675
}
2676

2677
int MOABMesh::get_vert_idx_from_handle(moab::EntityHandle vert) const
23✔
2678
{
2679
  int idx = vert - verts_[0];
2680
  if (idx >= n_vertices()) {
23✔
2681
    fatal_error(
23✔
2682
      fmt::format("Invalid vertex idx {} (# vertices {})", idx, n_vertices()));
2683
  }
2684
  return idx;
2685
}
23✔
2686

2687
int MOABMesh::get_bin_from_ent_handle(moab::EntityHandle eh) const
2688
{
2689
  int bin = eh - ehs_[0];
2690
  if (bin >= n_bins()) {
2691
    fatal_error(fmt::format("Invalid bin: {}", bin));
2692
  }
23✔
2693
  return bin;
23✔
2694
}
23✔
2695

2696
moab::EntityHandle MOABMesh::get_ent_handle_from_bin(int bin) const
2697
{
2698
  if (bin >= n_bins()) {
2699
    fatal_error(fmt::format("Invalid bin index: ", bin));
2700
  }
23✔
2701
  return ehs_[0] + bin;
23✔
2702
}
2703

2704
int MOABMesh::n_bins() const
2705
{
23✔
2706
  return ehs_.size();
23✔
2707
}
2708

2709
int MOABMesh::n_surface_bins() const
2710
{
23✔
2711
  // collect all triangles in the set of tets for this mesh
2712
  moab::Range tris;
2713
  moab::ErrorCode rval;
2714
  rval = mbi_->get_entities_by_type(0, moab::MBTRI, tris);
2715
  if (rval != moab::MB_SUCCESS) {
2716
    warning("Failed to get all triangles in the mesh instance");
2717
    return -1;
2718
  }
2719
  return 2 * tris.size();
2720
}
2721

2722
Position MOABMesh::centroid(int bin) const
2723
{
2724
  moab::ErrorCode rval;
2725

2726
  auto tet = this->get_ent_handle_from_bin(bin);
2727

2728
  // look up the tet connectivity
2729
  vector<moab::EntityHandle> conn;
2730
  rval = mbi_->get_connectivity(&tet, 1, conn);
2731
  if (rval != moab::MB_SUCCESS) {
2732
    warning("Failed to get connectivity of a mesh element.");
2733
    return {};
2734
  }
2735

2736
  // get the coordinates
2737
  vector<moab::CartVect> coords(conn.size());
2738
  rval = mbi_->get_coords(conn.data(), conn.size(), coords[0].array());
2739
  if (rval != moab::MB_SUCCESS) {
23✔
2740
    warning("Failed to get the coordinates of a mesh element.");
23✔
2741
    return {};
2742
  }
19✔
2743

2744
  // compute the centroid of the element vertices
2745
  moab::CartVect centroid(0.0, 0.0, 0.0);
19✔
2746
  for (const auto& coord : coords) {
2747
    centroid += coord;
2748
  }
2749
  centroid /= double(coords.size());
19✔
2750

19✔
2751
  return {centroid[0], centroid[1], centroid[2]};
2752
}
2753

23✔
2754
int MOABMesh::n_vertices() const
2755
{
2756
  return verts_.size();
23✔
2757
}
1✔
2758

2759
Position MOABMesh::vertex(int id) const
2760
{
22✔
2761

2762
  moab::ErrorCode rval;
2763

22✔
2764
  moab::EntityHandle vert = verts_[id];
22✔
2765

2766
  moab::CartVect coords;
2767
  rval = mbi_->get_coords(&vert, 1, coords.array());
2768
  if (rval != moab::MB_SUCCESS) {
2769
    fatal_error("Failed to get the coordinates of a vertex.");
19✔
2770
  }
2771

19✔
2772
  return {coords[0], coords[1], coords[2]};
19✔
2773
}
19✔
2774

19✔
2775
std::vector<int> MOABMesh::connectivity(int bin) const
2776
{
19✔
2777
  moab::ErrorCode rval;
2778

2779
  auto tet = get_ent_handle_from_bin(bin);
2780

19✔
2781
  // look up the tet connectivity
2782
  vector<moab::EntityHandle> conn;
2783
  rval = mbi_->get_connectivity(&tet, 1, conn);
2784
  if (rval != moab::MB_SUCCESS) {
2785
    fatal_error("Failed to get connectivity of a mesh element.");
2786
    return {};
2787
  }
19✔
2788

19✔
2789
  std::vector<int> verts(4);
19✔
2790
  for (int i = 0; i < verts.size(); i++) {
2791
    verts[i] = get_vert_idx_from_handle(conn[i]);
2792
  }
19✔
2793

19✔
2794
  return verts;
19✔
2795
}
2796

2797
std::pair<moab::Tag, moab::Tag> MOABMesh::get_score_tags(
19✔
2798
  std::string score) const
19✔
2799
{
3✔
2800
  moab::ErrorCode rval;
2801
  // add a tag to the mesh
16✔
2802
  // all scores are treated as a single value
2803
  // with an uncertainty
19✔
2804
  moab::Tag value_tag;
2805

2806
  // create the value tag if not present and get handle
19✔
2807
  double default_val = 0.0;
19✔
2808
  auto val_string = score + "_mean";
2809
  rval = mbi_->tag_get_handle(val_string.c_str(), 1, moab::MB_TYPE_DOUBLE,
2810
    value_tag, moab::MB_TAG_DENSE | moab::MB_TAG_CREAT, &default_val);
2811
  if (rval != moab::MB_SUCCESS) {
2812
    auto msg =
19✔
2813
      fmt::format("Could not create or retrieve the value tag for the score {}"
2814
                  " on unstructured mesh {}",
1,519,602✔
2815
        score, id_);
2816
    fatal_error(msg);
2817
  }
1,519,602✔
2818

2819
  // create the std dev tag if not present and get handle
2820
  moab::Tag error_tag;
1,519,602✔
2821
  std::string err_string = score + "_std_dev";
2822
  rval = mbi_->tag_get_handle(err_string.c_str(), 1, moab::MB_TYPE_DOUBLE,
2823
    error_tag, moab::MB_TAG_DENSE | moab::MB_TAG_CREAT, &default_val);
1,519,602✔
2824
  if (rval != moab::MB_SUCCESS) {
2825
    auto msg =
1,519,602✔
2826
      fmt::format("Could not create or retrieve the error tag for the score {}"
2827
                  " on unstructured mesh {}",
2828
        score, id_);
2829
    fatal_error(msg);
2830
  }
2831

1,519,602✔
2832
  // return the populated tag handles
2833
  return {value_tag, error_tag};
2834
}
1,519,602✔
2835

1,519,602✔
2836
void MOABMesh::add_score(const std::string& score)
2837
{
1,519,602✔
2838
  auto score_tags = get_score_tags(score);
2839
  tag_names_.push_back(score);
2840
}
1,519,602✔
2841

1,519,602✔
2842
void MOABMesh::remove_scores()
1,519,602✔
2843
{
1,519,602✔
2844
  for (const auto& name : tag_names_) {
2845
    auto value_name = name + "_mean";
1,519,602✔
2846
    moab::Tag tag;
1,519,602✔
2847
    moab::ErrorCode rval = mbi_->tag_get_handle(value_name.c_str(), tag);
701,483✔
2848
    if (rval != moab::MB_SUCCESS)
2849
      return;
1,519,602✔
2850

1,519,602✔
2851
    rval = mbi_->tag_delete(tag);
2852
    if (rval != moab::MB_SUCCESS) {
1,519,602✔
2853
      auto msg = fmt::format("Failed to delete mesh tag for the score {}"
1,519,602✔
2854
                             " on unstructured mesh {}",
2855
        name, id_);
1,519,602✔
2856
      fatal_error(msg);
1,519,602✔
2857
    }
2858

2859
    auto std_dev_name = name + "_std_dev";
2860
    rval = mbi_->tag_get_handle(std_dev_name.c_str(), tag);
2861
    if (rval != moab::MB_SUCCESS) {
1,519,602✔
2862
      auto msg =
701,483✔
2863
        fmt::format("Std. Dev. mesh tag does not exist for the score {}"
701,483✔
2864
                    " on unstructured mesh {}",
701,483✔
2865
          name, id_);
223,515✔
2866
    }
223,515✔
2867

2868
    rval = mbi_->tag_delete(tag);
701,483✔
2869
    if (rval != moab::MB_SUCCESS) {
2870
      auto msg = fmt::format("Failed to delete mesh tag for the score {}"
2871
                             " on unstructured mesh {}",
2872
        name, id_);
2873
      fatal_error(msg);
818,119✔
2874
    }
818,119✔
2875
  }
5,506,248✔
2876
  tag_names_.clear();
2877
}
4,688,129✔
2878

4,688,129✔
2879
void MOABMesh::set_score_data(const std::string& score,
2880
  const vector<double>& values, const vector<double>& std_dev)
4,688,129✔
2881
{
2882
  auto score_tags = this->get_score_tags(score);
4,688,129✔
2883

2884
  moab::ErrorCode rval;
2885
  // set the score value
4,688,129✔
2886
  rval = mbi_->tag_set_data(score_tags.first, ehs_, values.data());
2887
  if (rval != moab::MB_SUCCESS) {
4,688,129✔
2888
    auto msg = fmt::format("Failed to set the tally value for score '{}' "
20,480✔
2889
                           "on unstructured mesh {}",
2890
      score, id_);
2891
    warning(msg);
4,667,649✔
2892
  }
4,667,649✔
2893

2894
  // set the error value
2895
  rval = mbi_->tag_set_data(score_tags.second, ehs_, std_dev.data());
2896
  if (rval != moab::MB_SUCCESS) {
2897
    auto msg = fmt::format("Failed to set the tally error for score '{}' "
2898
                           "on unstructured mesh {}",
818,119✔
2899
      score, id_);
818,119✔
2900
    warning(msg);
818,119✔
2901
  }
818,119✔
2902
}
818,119✔
2903

818,119✔
2904
void MOABMesh::write(const std::string& base_filename) const
762,819✔
2905
{
762,819✔
2906
  // add extension to the base name
2907
  auto filename = base_filename + ".vtk";
2908
  write_message(5, "Writing unstructured mesh {}...", filename);
1,519,602✔
2909
  filename = settings::path_output + filename;
2910

7,279,728✔
2911
  // write the tetrahedral elements of the mesh only
2912
  // to avoid clutter from zero-value data on other
7,279,728✔
2913
  // elements during visualization
2914
  moab::ErrorCode rval;
7,279,728✔
2915
  rval = mbi_->write_mesh(filename.c_str(), &tetset_, 1);
7,279,728✔
2916
  if (rval != moab::MB_SUCCESS) {
7,279,728✔
2917
    auto msg = fmt::format("Failed to write unstructured mesh {}", id_);
1,010,077✔
2918
    warning(msg);
2919
  }
2920
}
2921

6,269,651✔
2922
#endif
6,269,651✔
2923

6,269,651✔
2924
#ifdef LIBMESH
6,269,651✔
2925

2926
const std::string LibMesh::mesh_lib_type = "libmesh";
2927

2928
LibMesh::LibMesh(pugi::xml_node node) : UnstructuredMesh(node), adaptive_(false)
2929
{
259,367,091✔
2930
  // filename_ and length_multiplier_ will already be set by the
259,364,245✔
2931
  // UnstructuredMesh constructor
6,266,805✔
2932
  set_mesh_pointer_from_filename(filename_);
2933
  set_length_multiplier(length_multiplier_);
2934
  initialize();
2935
}
2936

2,846✔
2937
// create the mesh from a pointer to a libMesh Mesh
7,279,728✔
2938
LibMesh::LibMesh(libMesh::MeshBase& input_mesh, double length_multiplier)
2939
  : adaptive_(input_mesh.n_active_elem() != input_mesh.n_elem())
155,856✔
2940
{
2941
  if (!dynamic_cast<libMesh::ReplicatedMesh*>(&input_mesh)) {
155,856✔
2942
    fatal_error("At present LibMesh tallies require a replicated mesh. Please "
2943
                "ensure 'input_mesh' is a libMesh::ReplicatedMesh.");
2944
  }
30✔
2945

2946
  m_ = &input_mesh;
30✔
2947
  set_length_multiplier(length_multiplier);
2948
  initialize();
2949
}
2950

200,410✔
2951
// create the mesh from an input file
2952
LibMesh::LibMesh(const std::string& filename, double length_multiplier)
2953
  : adaptive_(false)
200,410✔
2954
{
2955
  set_mesh_pointer_from_filename(filename);
2956
  set_length_multiplier(length_multiplier);
2957
  initialize();
2958
}
200,410✔
2959

200,410✔
2960
void LibMesh::set_mesh_pointer_from_filename(const std::string& filename)
2961
{
2962
  filename_ = filename;
2963
  unique_m_ =
2964
    make_unique<libMesh::ReplicatedMesh>(*settings::libmesh_comm, n_dimension_);
1,002,050✔
2965
  m_ = unique_m_.get();
200,410✔
2966
  m_->read(filename_);
200,410✔
2967
}
2968

2969
// build a libMesh equation system for storing values
2970
void LibMesh::build_eqn_sys()
200,410✔
2971
{
1,002,050✔
2972
  eq_system_name_ = fmt::format("mesh_{}_system", id_);
801,640✔
2973
  equation_systems_ = make_unique<libMesh::EquationSystems>(*m_);
2974
  libMesh::ExplicitSystem& eq_sys =
2975
    equation_systems_->add_system<libMesh::ExplicitSystem>(eq_system_name_);
400,820✔
2976
}
2977

2978
// intialize from mesh file
155,856✔
2979
void LibMesh::initialize()
2980
{
155,856✔
2981
  if (!settings::libmesh_comm) {
155,856✔
2982
    fatal_error(
155,856✔
2983
      "Attempting to use an unstructured mesh without a libMesh communicator.");
2984
  }
2985

2986
  // assuming that unstructured meshes used in OpenMC are 3D
779,280✔
2987
  n_dimension_ = 3;
155,856✔
2988

155,856✔
2989
  if (length_multiplier_ > 0.0) {
2990
    libMesh::MeshTools::Modification::scale(*m_, length_multiplier_);
2991
  }
2992
  // if OpenMC is managing the libMesh::MeshBase instance, prepare the mesh.
311,712✔
2993
  // Otherwise assume that it is prepared by its owning application
155,856✔
2994
  if (unique_m_) {
2995
    m_->prepare_for_use();
7,279,728✔
2996
  }
2997

7,279,728✔
2998
  // ensure that the loaded mesh is 3 dimensional
7,279,728✔
2999
  if (m_->mesh_dimension() != n_dimension_) {
1,012,923✔
3000
    fatal_error(fmt::format("Mesh file {} specified for use in an unstructured "
3001
                            "mesh is not a 3D mesh.",
6,266,805✔
3002
      filename_));
3003
  }
3004

3005
  for (int i = 0; i < num_threads(); i++) {
19✔
3006
    pl_.emplace_back(m_->sub_point_locator());
3007
    pl_.back()->set_contains_point_tol(FP_COINCIDENT);
3008
    pl_.back()->enable_out_of_mesh_mode();
3009
  }
19✔
3010

19✔
3011
  // store first element in the mesh to use as an offset for bin indices
3012
  auto first_elem = *m_->elements_begin();
3013
  first_element_id_ = first_elem->id();
3014

227,731✔
3015
  // if the mesh is adaptive elements aren't guaranteed by libMesh to be
227,712✔
3016
  // contiguous in ID space, so we need to map from bin indices (defined over
227,712✔
3017
  // active elements) to global dof ids
227,712✔
3018
  if (adaptive_) {
3019
    bin_to_elem_map_.reserve(m_->n_active_elem());
3020
    elem_to_bin_map_.resize(m_->n_elem(), -1);
3021
    for (auto it = m_->active_elements_begin(); it != m_->active_elements_end();
1,138,560✔
3022
         it++) {
227,712✔
3023
      auto elem = *it;
227,712✔
3024

3025
      bin_to_elem_map_.push_back(elem->id());
3026
      elem_to_bin_map_[elem->id()] = bin_to_elem_map_.size() - 1;
3027
    }
227,712✔
3028
  }
3029

3030
  // bounding box for the mesh for quick rejection checks
227,712✔
3031
  bbox_ = libMesh::MeshTools::create_bounding_box(*m_);
227,712✔
3032
  libMesh::Point ll = bbox_.min();
227,712✔
3033
  libMesh::Point ur = bbox_.max();
19✔
3034
  lower_left_ = {ll(0), ll(1), ll(2)};
3035
  upper_right_ = {ur(0), ur(1), ur(2)};
259,364,245✔
3036
}
3037

3038
// Sample position within a tet for LibMesh type tets
3039
Position LibMesh::sample_element(int32_t bin, uint64_t* seed) const
3040
{
3041
  const auto& elem = get_element_from_bin(bin);
3042
  // Get tet vertex coordinates from LibMesh
259,364,245✔
3043
  std::array<Position, 4> tet_verts;
259,364,245✔
3044
  for (int i = 0; i < elem.n_nodes(); i++) {
259,364,245✔
3045
    auto node_ref = elem.node_ref(i);
3046
    tet_verts[i] = {node_ref(0), node_ref(1), node_ref(2)};
3047
  }
3048
  // Samples position within tet using Barycentric coordinates
3049
  return this->sample_tet(tet_verts, seed);
3050
}
3051

259,364,245✔
3052
Position LibMesh::centroid(int bin) const
259,364,245✔
3053
{
259,364,245✔
3054
  const auto& elem = this->get_element_from_bin(bin);
3055
  auto centroid = elem.vertex_average();
3056
  return {centroid(0), centroid(1), centroid(2)};
3057
}
3058

3059
int LibMesh::n_vertices() const
3060
{
3061
  return m_->n_nodes();
259,364,245✔
3062
}
259,364,245✔
3063

3064
Position LibMesh::vertex(int vertex_id) const
259,364,245✔
3065
{
3066
  const auto node_ref = m_->node_ref(vertex_id);
420,046,390✔
3067
  return {node_ref(0), node_ref(1), node_ref(2)};
441,624,143✔
3068
}
280,941,998✔
3069

259,364,245✔
3070
std::vector<int> LibMesh::connectivity(int elem_id) const
3071
{
3072
  std::vector<int> conn;
3073
  const auto* elem_ptr = m_->elem_ptr(elem_id);
3074
  for (int i = 0; i < elem_ptr->n_nodes(); i++) {
3075
    conn.push_back(elem_ptr->node_id(i));
3076
  }
3077
  return conn;
3078
}
3079

3080
std::string LibMesh::library() const
3081
{
3082
  return mesh_lib_type;
3083
}
3084

3085
int LibMesh::n_bins() const
3086
{
3087
  return m_->n_active_elem();
3088
}
3089

3090
int LibMesh::n_surface_bins() const
3091
{
3092
  int n_bins = 0;
3093
  for (int i = 0; i < this->n_bins(); i++) {
3094
    const libMesh::Elem& e = get_element_from_bin(i);
3095
    n_bins += e.n_faces();
3096
    // if this is a boundary element, it will only be visited once,
3097
    // the number of surface bins is incremented to
3098
    for (auto neighbor_ptr : e.neighbor_ptr_range()) {
767,424✔
3099
      // null neighbor pointer indicates a boundary face
3100
      if (!neighbor_ptr) {
767,424✔
3101
        n_bins++;
767,424✔
3102
      }
3103
    }
3104
  }
3105
  return n_bins;
767,424✔
3106
}
3107

3108
void LibMesh::add_score(const std::string& var_name)
265,858,762✔
3109
{
3110
  if (adaptive_) {
265,858,762✔
3111
    warning(fmt::format(
265,858,762✔
3112
      "Exodus output cannot be provided as unstructured mesh {} is adaptive.",
3113
      this->id_));
3114

265,858,762✔
3115
    return;
3116
  }
3117

548,122✔
3118
  if (!equation_systems_) {
3119
    build_eqn_sys();
548,122✔
3120
  }
3121

3122
  // check if this is a new variable
548,122✔
3123
  std::string value_name = var_name + "_mean";
3124
  if (!variable_map_.count(value_name)) {
3125
    auto& eqn_sys = equation_systems_->get_system(eq_system_name_);
266,598,805✔
3126
    auto var_num =
3127
      eqn_sys.add_variable(value_name, libMesh::CONSTANT, libMesh::MONOMIAL);
266,598,805✔
3128
    variable_map_[value_name] = var_num;
3129
  }
3130

3131
  std::string std_dev_name = var_name + "_std_dev";
3132
  // check if this is a new variable
3133
  if (!variable_map_.count(std_dev_name)) {
3134
    auto& eqn_sys = equation_systems_->get_system(eq_system_name_);
3135
    auto var_num =
3136
      eqn_sys.add_variable(std_dev_name, libMesh::CONSTANT, libMesh::MONOMIAL);
3137
    variable_map_[std_dev_name] = var_num;
3138
  }
3139
}
3140

3141
void LibMesh::remove_scores()
3142
{
3143
  if (equation_systems_) {
3144
    auto& eqn_sys = equation_systems_->get_system(eq_system_name_);
3145
    eqn_sys.clear();
3146
    variable_map_.clear();
3147
  }
3148
}
3149

3150
void LibMesh::set_score_data(const std::string& var_name,
3151
  const vector<double>& values, const vector<double>& std_dev)
3152
{
3153
  if (adaptive_) {
3154
    warning(fmt::format(
3155
      "Exodus output cannot be provided as unstructured mesh {} is adaptive.",
3156
      this->id_));
3157

3158
    return;
3159
  }
3160

3161
  if (!equation_systems_) {
3162
    build_eqn_sys();
3163
  }
3164

3165
  auto& eqn_sys = equation_systems_->get_system(eq_system_name_);
3166

3167
  if (!eqn_sys.is_initialized()) {
3168
    equation_systems_->init();
3169
  }
3170

3171
  const libMesh::DofMap& dof_map = eqn_sys.get_dof_map();
3172

3173
  // look up the value variable
3174
  std::string value_name = var_name + "_mean";
3175
  unsigned int value_num = variable_map_.at(value_name);
795,427✔
3176
  // look up the std dev variable
3177
  std::string std_dev_name = var_name + "_std_dev";
795,427✔
3178
  unsigned int std_dev_num = variable_map_.at(std_dev_name);
3179

3180
  for (auto it = m_->local_elements_begin(); it != m_->local_elements_end();
81,537✔
3181
       it++) {
3182
    if (!(*it)->active()) {
3183
      continue;
3184
    }
3185

81,537✔
3186
    auto bin = get_bin_from_element(*it);
3187

81,537✔
3188
    // set value
81,537✔
3189
    vector<libMesh::dof_id_type> value_dof_indices;
81,537✔
3190
    dof_map.dof_indices(*it, value_dof_indices, value_num);
3191
    Ensures(value_dof_indices.size() == 1);
3192
    eqn_sys.solution->set(value_dof_indices[0], values.at(bin));
3193

163,074✔
3194
    // set std dev
3195
    vector<libMesh::dof_id_type> std_dev_dof_indices;
3196
    dof_map.dof_indices(*it, std_dev_dof_indices, std_dev_num);
191,856✔
3197
    Ensures(std_dev_dof_indices.size() == 1);
3198
    eqn_sys.solution->set(std_dev_dof_indices[0], std_dev.at(bin));
3199
  }
3200
}
191,856✔
3201

3202
void LibMesh::write(const std::string& filename) const
3203
{
191,856✔
3204
  if (adaptive_) {
191,856✔
3205
    warning(fmt::format(
191,856✔
3206
      "Exodus output cannot be provided as unstructured mesh {} is adaptive.",
3207
      this->id_));
3208

3209
    return;
3210
  }
191,856✔
3211

959,280✔
3212
  write_message(fmt::format(
767,424✔
3213
    "Writing file: {}.e for unstructured mesh {}", filename, this->id_));
3214
  libMesh::ExodusII_IO exo(*m_);
3215
  std::set<std::string> systems_out = {eq_system_name_};
191,856✔
3216
  exo.write_discontinuous_exodusII(
191,856✔
3217
    filename + ".e", *equation_systems_, &systems_out);
3218
}
3219

3220
void LibMesh::bins_crossed(Position r0, Position r1, const Direction& u,
3221
  vector<int>& bins, vector<double>& lengths) const
3222
{
3223
  // TODO: Implement triangle crossings here
3224
  fatal_error("Tracklength tallies on libMesh instances are not implemented.");
3225
}
3226

3227
int LibMesh::get_bin(Position r) const
3228
{
3229
  // look-up a tet using the point locator
3230
  libMesh::Point p(r.x, r.y, r.z);
3231

3232
  // quick rejection check
3233
  if (!bbox_.contains_point(p)) {
3234
    return -1;
3235
  }
3236

3237
  const auto& point_locator = pl_.at(thread_num());
3238

3239
  const auto elem_ptr = (*point_locator)(p);
3240
  return elem_ptr ? get_bin_from_element(elem_ptr) : -1;
3241
}
3242

3243
int LibMesh::get_bin_from_element(const libMesh::Elem* elem) const
3244
{
3245
  int bin =
3246
    adaptive_ ? elem_to_bin_map_[elem->id()] : elem->id() - first_element_id_;
3247
  if (bin >= n_bins() || bin < 0) {
3248
    fatal_error(fmt::format("Invalid bin: {}", bin));
3249
  }
3250
  return bin;
3251
}
3252

3253
std::pair<vector<double>, vector<double>> LibMesh::plot(
3254
  Position plot_ll, Position plot_ur) const
3255
{
3256
  return {};
3257
}
3258

3259
const libMesh::Elem& LibMesh::get_element_from_bin(int bin) const
3260
{
3261
  return adaptive_ ? m_->elem_ref(bin_to_elem_map_.at(bin)) : m_->elem_ref(bin);
3262
}
3263

3264
double LibMesh::volume(int bin) const
3265
{
3266
  return this->get_element_from_bin(bin).volume();
3267
}
3268

3269
#endif // LIBMESH
3270

3271
//==============================================================================
3272
// Non-member functions
3273
//==============================================================================
3274

3275
void read_meshes(pugi::xml_node root)
3276
{
3277
  std::unordered_set<int> mesh_ids;
3278

3279
  for (auto node : root.children("mesh")) {
3280
    // Check to make sure multiple meshes in the same file don't share IDs
3281
    int id = std::stoi(get_node_value(node, "id"));
3282
    if (contains(mesh_ids, id)) {
3283
      fatal_error(fmt::format(
3284
        "Two or more meshes use the same unique ID '{}' in the same input file",
3285
        id));
3286
    }
3287
    mesh_ids.insert(id);
3288

3289
    // If we've already read a mesh with the same ID in a *different* file,
3290
    // assume it is the same here
3291
    if (model::mesh_map.find(id) != model::mesh_map.end()) {
3292
      warning(fmt::format("Mesh with ID={} appears in multiple files.", id));
3293
      continue;
3294
    }
3295

3296
    std::string mesh_type;
3297
    if (check_for_node(node, "type")) {
3298
      mesh_type = get_node_value(node, "type", true, true);
3299
    } else {
3300
      mesh_type = "regular";
3301
    }
3302

3303
    // determine the mesh library to use
3304
    std::string mesh_lib;
3305
    if (check_for_node(node, "library")) {
3306
      mesh_lib = get_node_value(node, "library", true, true);
3307
    }
3308

3309
    // Read mesh and add to vector
3310
    if (mesh_type == RegularMesh::mesh_type) {
3311
      model::meshes.push_back(make_unique<RegularMesh>(node));
3312
    } else if (mesh_type == RectilinearMesh::mesh_type) {
3313
      model::meshes.push_back(make_unique<RectilinearMesh>(node));
3314
    } else if (mesh_type == CylindricalMesh::mesh_type) {
3315
      model::meshes.push_back(make_unique<CylindricalMesh>(node));
3316
    } else if (mesh_type == SphericalMesh::mesh_type) {
3317
      model::meshes.push_back(make_unique<SphericalMesh>(node));
3318
#ifdef DAGMC
3319
    } else if (mesh_type == UnstructuredMesh::mesh_type &&
3320
               mesh_lib == MOABMesh::mesh_lib_type) {
3321
      model::meshes.push_back(make_unique<MOABMesh>(node));
3322
#endif
3323
#ifdef LIBMESH
3324
    } else if (mesh_type == UnstructuredMesh::mesh_type &&
3325
               mesh_lib == LibMesh::mesh_lib_type) {
3326
      model::meshes.push_back(make_unique<LibMesh>(node));
3327
#endif
3328
    } else if (mesh_type == UnstructuredMesh::mesh_type) {
3329
      fatal_error("Unstructured mesh support is not enabled or the mesh "
3330
                  "library is invalid.");
3331
    } else {
3332
      fatal_error("Invalid mesh type: " + mesh_type);
3333
    }
3334

3335
    // Map ID to position in vector
3336
    model::mesh_map[model::meshes.back()->id_] = model::meshes.size() - 1;
3337
  }
3338
}
3339

3340
void meshes_to_hdf5(hid_t group)
3341
{
3342
  // Write number of meshes
3343
  hid_t meshes_group = create_group(group, "meshes");
3344
  int32_t n_meshes = model::meshes.size();
3345
  write_attribute(meshes_group, "n_meshes", n_meshes);
3346

3347
  if (n_meshes > 0) {
3348
    // Write IDs of meshes
3349
    vector<int> ids;
23✔
3350
    for (const auto& m : model::meshes) {
3351
      m->to_hdf5(meshes_group);
3352
      ids.push_back(m->id_);
3353
    }
23✔
3354
    write_attribute(meshes_group, "ids", ids);
23✔
3355
  }
23✔
3356

23✔
3357
  close_group(meshes_group);
3358
}
3359

3360
void free_memory_mesh()
3361
{
3362
  model::meshes.clear();
3363
  model::mesh_map.clear();
3364
}
3365

3366
extern "C" int n_meshes()
3367
{
3368
  return model::meshes.size();
3369
}
3370

3371
} // namespace openmc
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