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

openmc-dev / openmc / 7071649772

02 Dec 2023 05:35PM UTC coverage: 84.541% (+0.02%) from 84.524%
7071649772

push

github

web-flow
Mesh Source Class (#2759)

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

220 of 231 new or added lines in 12 files covered. (95.24%)

53 existing lines in 2 files now uncovered.

47205 of 55837 relevant lines covered (84.54%)

34203579.09 hits per line

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

85.25
/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
#ifdef _OPENMP
12
#include <omp.h>
13
#endif
14
#include "xtensor/xbuilder.hpp"
15
#include "xtensor/xeval.hpp"
16
#include "xtensor/xmath.hpp"
17
#include "xtensor/xsort.hpp"
18
#include "xtensor/xtensor.hpp"
19
#include "xtensor/xview.hpp"
20
#include <fmt/core.h> // for fmt
21

22
#include "openmc/capi.h"
23
#include "openmc/constants.h"
24
#include "openmc/container_util.h"
25
#include "openmc/error.h"
26
#include "openmc/file_utils.h"
27
#include "openmc/hdf5_interface.h"
28
#include "openmc/memory.h"
29
#include "openmc/message_passing.h"
30
#include "openmc/random_dist.h"
31
#include "openmc/search.h"
32
#include "openmc/settings.h"
33
#include "openmc/tallies/filter.h"
34
#include "openmc/tallies/tally.h"
35
#include "openmc/xml_interface.h"
36

37
#ifdef LIBMESH
38
#include "libmesh/mesh_modification.h"
39
#include "libmesh/mesh_tools.h"
40
#include "libmesh/numeric_vector.h"
41
#endif
42

43
namespace openmc {
44

45
//==============================================================================
46
// Global variables
47
//==============================================================================
48

49
#ifdef LIBMESH
50
const bool LIBMESH_ENABLED = true;
51
#else
52
const bool LIBMESH_ENABLED = false;
53
#endif
54

55
namespace model {
56

57
std::unordered_map<int32_t, int32_t> mesh_map;
58
vector<unique_ptr<Mesh>> meshes;
59

60
} // namespace model
61

62
#ifdef LIBMESH
63
namespace settings {
64
unique_ptr<libMesh::LibMeshInit> libmesh_init;
65
const libMesh::Parallel::Communicator* libmesh_comm {nullptr};
66
} // namespace settings
67
#endif
68

69
//==============================================================================
70
// Helper functions
71
//==============================================================================
72

73
//! Update an intersection point if the given candidate is closer.
74
//
75
//! The first 6 arguments are coordinates for the starting point of a particle
76
//! and its intersection with a mesh surface.  If the distance between these
77
//! two points is shorter than the given `min_distance`, then the `r` argument
78
//! will be updated to match the intersection point, and `min_distance` will
79
//! also be updated.
80

81
inline bool check_intersection_point(double x1, double x0, double y1, double y0,
82
  double z1, double z0, Position& r, double& min_distance)
83
{
84
  double dist =
85
    std::pow(x1 - x0, 2) + std::pow(y1 - y0, 2) + std::pow(z1 - z0, 2);
86
  if (dist < min_distance) {
87
    r.x = x1;
88
    r.y = y1;
89
    r.z = z1;
90
    min_distance = dist;
91
    return true;
92
  }
93
  return false;
94
}
95

96
//==============================================================================
97
// Mesh implementation
98
//==============================================================================
99

100
Mesh::Mesh(pugi::xml_node node)
2,877✔
101
{
102
  // Read mesh id
103
  id_ = std::stoi(get_node_value(node, "id"));
2,877✔
104
}
2,877✔
105

106
void Mesh::set_id(int32_t id)
1✔
107
{
108
  Expects(id >= 0 || id == C_NONE);
1✔
109

110
  // Clear entry in mesh map in case one was already assigned
111
  if (id_ != C_NONE) {
1✔
112
    model::mesh_map.erase(id_);
×
113
    id_ = C_NONE;
×
114
  }
115

116
  // Ensure no other mesh has the same ID
117
  if (model::mesh_map.find(id) != model::mesh_map.end()) {
1✔
118
    throw std::runtime_error {
×
119
      fmt::format("Two meshes have the same ID: {}", id)};
×
120
  }
121

122
  // If no ID is specified, auto-assign the next ID in the sequence
123
  if (id == C_NONE) {
1✔
124
    id = 0;
1✔
125
    for (const auto& m : model::meshes) {
3✔
126
      id = std::max(id, m->id_);
2✔
127
    }
128
    ++id;
1✔
129
  }
130

131
  // Update ID and entry in the mesh map
132
  id_ = id;
1✔
133
  model::mesh_map[id] = model::meshes.size() - 1;
1✔
134
}
1✔
135

136
vector<double> Mesh::volumes() const
182✔
137
{
138
  vector<double> volumes(n_bins());
182✔
139
  for (int i = 0; i < n_bins(); i++) {
1,146,222✔
140
    volumes[i] = this->volume(i);
1,146,040✔
141
  }
142
  return volumes;
182✔
143
}
×
144

145
//==============================================================================
146
// Structured Mesh implementation
147
//==============================================================================
148

149
std::string StructuredMesh::bin_label(int bin) const
6,331,650✔
150
{
151
  MeshIndex ijk = get_indices_from_bin(bin);
6,331,650✔
152

153
  if (n_dimension_ > 2) {
6,331,650✔
154
    return fmt::format("Mesh Index ({}, {}, {})", ijk[0], ijk[1], ijk[2]);
12,626,340✔
155
  } else if (n_dimension_ > 1) {
18,480✔
156
    return fmt::format("Mesh Index ({}, {})", ijk[0], ijk[1]);
36,260✔
157
  } else {
158
    return fmt::format("Mesh Index ({})", ijk[0]);
700✔
159
  }
160
}
161

162
xt::xtensor<int, 1> StructuredMesh::get_x_shape() const
2,644✔
163
{
164
  // because method is const, shape_ is const as well and can't be adapted
165
  auto tmp_shape = shape_;
2,644✔
166
  return xt::adapt(tmp_shape, {n_dimension_});
5,288✔
167
}
168

169
Position StructuredMesh::sample_element(
112,140✔
170
  const MeshIndex& ijk, uint64_t* seed) const
171
{
172
  // lookup the lower/upper bounds for the mesh element
173
  double x_min = negative_grid_boundary(ijk, 0);
112,140✔
174
  double x_max = positive_grid_boundary(ijk, 0);
112,140✔
175

176
  double y_min = (n_dimension_ >= 2) ? negative_grid_boundary(ijk, 1) : 0.0;
112,140✔
177
  double y_max = (n_dimension_ >= 2) ? positive_grid_boundary(ijk, 1) : 0.0;
112,140✔
178

179
  double z_min = (n_dimension_ == 3) ? negative_grid_boundary(ijk, 2) : 0.0;
112,140✔
180
  double z_max = (n_dimension_ == 3) ? positive_grid_boundary(ijk, 2) : 0.0;
112,140✔
181

182
  return {x_min + (x_max - x_min) * prn(seed),
112,140✔
183
    y_min + (y_max - y_min) * prn(seed), z_min + (z_max - z_min) * prn(seed)};
112,140✔
184
}
185

186
//==============================================================================
187
// Unstructured Mesh implementation
188
//==============================================================================
189

190
UnstructuredMesh::UnstructuredMesh(pugi::xml_node node) : Mesh(node)
39✔
191
{
192

193
  // check the mesh type
194
  if (check_for_node(node, "type")) {
39✔
195
    auto temp = get_node_value(node, "type", true, true);
39✔
196
    if (temp != mesh_type) {
39✔
197
      fatal_error(fmt::format("Invalid mesh type: {}", temp));
×
198
    }
199
  }
39✔
200

201
  // check if a length unit multiplier was specified
202
  if (check_for_node(node, "length_multiplier")) {
39✔
203
    length_multiplier_ = std::stod(get_node_value(node, "length_multiplier"));
×
204
    specified_length_multiplier_ = true;
×
205
  }
206

207
  // get the filename of the unstructured mesh to load
208
  if (check_for_node(node, "filename")) {
39✔
209
    filename_ = get_node_value(node, "filename");
39✔
210
    if (!file_exists(filename_)) {
39✔
211
      fatal_error("Mesh file '" + filename_ + "' does not exist!");
×
212
    }
213
  } else {
214
    fatal_error(fmt::format(
×
215
      "No filename supplied for unstructured mesh with ID: {}", id_));
×
216
  }
217

218
  // check if mesh tally data should be written with
219
  // statepoint files
220
  if (check_for_node(node, "output")) {
39✔
221
    output_ = get_node_value_bool(node, "output");
×
222
  }
223
}
39✔
224

225
Position UnstructuredMesh::sample_tet(
601,200✔
226
  std::array<Position, 4> coords, uint64_t* seed) const
227
{
228
  // Uniform distribution
229
  double s = prn(seed);
601,200✔
230
  double t = prn(seed);
601,200✔
231
  double u = prn(seed);
601,200✔
232

233
  // From PyNE implementation of moab tet sampling C. Rocchini & P. Cignoni
234
  // (2000) Generating Random Points in a Tetrahedron, Journal of Graphics
235
  // Tools, 5:4, 9-12, DOI: 10.1080/10867651.2000.10487528
236
  if (s + t > 1) {
601,200✔
237
    s = 1.0 - s;
301,239✔
238
    t = 1.0 - t;
301,239✔
239
  }
240
  if (s + t + u > 1) {
601,200✔
241
    if (t + u > 1) {
400,869✔
242
      double old_t = t;
200,070✔
243
      t = 1.0 - u;
200,070✔
244
      u = 1.0 - s - old_t;
200,070✔
245
    } else if (t + u <= 1) {
200,799✔
246
      double old_s = s;
200,799✔
247
      s = 1.0 - t - u;
200,799✔
248
      u = old_s + t + u - 1;
200,799✔
249
    }
250
  }
251
  return s * (coords[1] - coords[0]) + t * (coords[2] - coords[0]) +
1,202,400✔
252
         u * (coords[3] - coords[0]) + coords[0];
1,803,600✔
253
}
254

255
const std::string UnstructuredMesh::mesh_type = "unstructured";
256

257
std::string UnstructuredMesh::get_mesh_type() const
×
258
{
259
  return mesh_type;
×
260
}
261

262
void UnstructuredMesh::surface_bins_crossed(
×
263
  Position r0, Position r1, const Direction& u, vector<int>& bins) const
264
{
265
  fatal_error("Unstructured mesh surface tallies are not implemented.");
×
266
}
267

268
std::string UnstructuredMesh::bin_label(int bin) const
193,712✔
269
{
270
  return fmt::format("Mesh Index ({})", bin);
387,424✔
271
};
272

273
void UnstructuredMesh::to_hdf5(hid_t group) const
24✔
274
{
275
  hid_t mesh_group = create_group(group, fmt::format("mesh {}", id_));
48✔
276

277
  write_dataset(mesh_group, "type", mesh_type);
24✔
278
  write_dataset(mesh_group, "filename", filename_);
24✔
279
  write_dataset(mesh_group, "library", this->library());
24✔
280

281
  if (specified_length_multiplier_)
24✔
282
    write_dataset(mesh_group, "length_multiplier", length_multiplier_);
×
283

284
  // write vertex coordinates
285
  xt::xtensor<double, 2> vertices({static_cast<size_t>(this->n_vertices()), 3});
24✔
286
  for (int i = 0; i < this->n_vertices(); i++) {
53,936✔
287
    auto v = this->vertex(i);
53,912✔
288
    xt::view(vertices, i, xt::all()) = xt::xarray<double>({v.x, v.y, v.z});
53,912✔
289
  }
290
  write_dataset(mesh_group, "vertices", vertices);
24✔
291

292
  int num_elem_skipped = 0;
24✔
293

294
  // write element types and connectivity
295
  vector<double> volumes;
24✔
296
  xt::xtensor<int, 2> connectivity({static_cast<size_t>(this->n_bins()), 8});
24✔
297
  xt::xtensor<int, 2> elem_types({static_cast<size_t>(this->n_bins()), 1});
24✔
298
  for (int i = 0; i < this->n_bins(); i++) {
265,736✔
299
    auto conn = this->connectivity(i);
265,712✔
300

301
    volumes.emplace_back(this->volume(i));
265,712✔
302

303
    // write linear tet element
304
    if (conn.size() == 4) {
265,712✔
305
      xt::view(elem_types, i, xt::all()) =
527,424✔
306
        static_cast<int>(ElementType::LINEAR_TET);
527,424✔
307
      xt::view(connectivity, i, xt::all()) =
527,424✔
308
        xt::xarray<int>({conn[0], conn[1], conn[2], conn[3], -1, -1, -1, -1});
791,136✔
309
      // write linear hex element
310
    } else if (conn.size() == 8) {
2,000✔
311
      xt::view(elem_types, i, xt::all()) =
4,000✔
312
        static_cast<int>(ElementType::LINEAR_HEX);
4,000✔
313
      xt::view(connectivity, i, xt::all()) = xt::xarray<int>({conn[0], conn[1],
8,000✔
314
        conn[2], conn[3], conn[4], conn[5], conn[6], conn[7]});
6,000✔
315
    } else {
316
      num_elem_skipped++;
×
317
      xt::view(elem_types, i, xt::all()) =
×
318
        static_cast<int>(ElementType::UNSUPPORTED);
319
      xt::view(connectivity, i, xt::all()) = -1;
×
320
    }
321
  }
265,712✔
322

323
  // warn users that some elements were skipped
324
  if (num_elem_skipped > 0) {
24✔
325
    warning(fmt::format("The connectivity of {} elements "
×
326
                        "on mesh {} were not written "
327
                        "because they are not of type linear tet/hex.",
328
      num_elem_skipped, this->id_));
×
329
  }
330

331
  write_dataset(mesh_group, "volumes", volumes);
24✔
332
  write_dataset(mesh_group, "connectivity", connectivity);
24✔
333
  write_dataset(mesh_group, "element_types", elem_types);
24✔
334

335
  close_group(mesh_group);
24✔
336
}
24✔
337

338
void UnstructuredMesh::set_length_multiplier(double length_multiplier)
19✔
339
{
340
  length_multiplier_ = length_multiplier;
19✔
341

342
  if (length_multiplier_ != 1.0)
19✔
343
    specified_length_multiplier_ = true;
×
344
}
19✔
345

346
ElementType UnstructuredMesh::element_type(int bin) const
84,000✔
347
{
348
  auto conn = connectivity(bin);
84,000✔
349

350
  if (conn.size() == 4)
84,000✔
351
    return ElementType::LINEAR_TET;
84,000✔
352
  else if (conn.size() == 8)
×
353
    return ElementType::LINEAR_HEX;
×
354
  else
355
    return ElementType::UNSUPPORTED;
×
356
}
84,000✔
357

358
StructuredMesh::MeshIndex StructuredMesh::get_indices(
948,252,278✔
359
  Position r, bool& in_mesh) const
360
{
361
  MeshIndex ijk;
362
  in_mesh = true;
948,252,278✔
363
  for (int i = 0; i < n_dimension_; ++i) {
2,147,483,647✔
364
    ijk[i] = get_index_in_direction(r[i], i);
2,147,483,647✔
365

366
    if (ijk[i] < 1 || ijk[i] > shape_[i])
2,147,483,647✔
367
      in_mesh = false;
106,109,744✔
368
  }
369
  return ijk;
948,252,278✔
370
}
371

372
int StructuredMesh::get_bin_from_indices(const MeshIndex& ijk) const
1,020,459,883✔
373
{
374
  switch (n_dimension_) {
1,020,459,883✔
375
  case 1:
1,116,192✔
376
    return ijk[0] - 1;
1,116,192✔
377
  case 2:
25,014,612✔
378
    return (ijk[1] - 1) * shape_[0] + ijk[0] - 1;
25,014,612✔
379
  case 3:
994,329,079✔
380
    return ((ijk[2] - 1) * shape_[1] + (ijk[1] - 1)) * shape_[0] + ijk[0] - 1;
994,329,079✔
381
  default:
×
382
    throw std::runtime_error {"Invalid number of mesh dimensions"};
×
383
  }
384
}
385

386
StructuredMesh::MeshIndex StructuredMesh::get_indices_from_bin(int bin) const
7,701,830✔
387
{
388
  MeshIndex ijk;
389
  if (n_dimension_ == 1) {
7,701,830✔
390
    ijk[0] = bin + 1;
350✔
391
  } else if (n_dimension_ == 2) {
7,701,480✔
392
    ijk[0] = bin % shape_[0] + 1;
18,130✔
393
    ijk[1] = bin / shape_[0] + 1;
18,130✔
394
  } else if (n_dimension_ == 3) {
7,683,350✔
395
    ijk[0] = bin % shape_[0] + 1;
7,683,350✔
396
    ijk[1] = (bin % (shape_[0] * shape_[1])) / shape_[0] + 1;
7,683,350✔
397
    ijk[2] = bin / (shape_[0] * shape_[1]) + 1;
7,683,350✔
398
  }
399
  return ijk;
7,701,830✔
400
}
401

402
int StructuredMesh::get_bin(Position r) const
281,295,169✔
403
{
404
  // Determine indices
405
  bool in_mesh;
406
  MeshIndex ijk = get_indices(r, in_mesh);
281,295,169✔
407
  if (!in_mesh)
281,295,169✔
408
    return -1;
25,621,112✔
409

410
  // Convert indices to bin
411
  return get_bin_from_indices(ijk);
255,674,057✔
412
}
413

414
int StructuredMesh::n_bins() const
1,163,946✔
415
{
416
  return std::accumulate(
1,163,946✔
417
    shape_.begin(), shape_.begin() + n_dimension_, 1, std::multiplies<>());
2,327,892✔
418
}
419

420
int StructuredMesh::n_surface_bins() const
636✔
421
{
422
  return 4 * n_dimension_ * n_bins();
636✔
423
}
424

425
xt::xtensor<double, 1> StructuredMesh::count_sites(
×
426
  const SourceSite* bank, int64_t length, bool* outside) const
427
{
428
  // Determine shape of array for counts
429
  std::size_t m = this->n_bins();
×
430
  vector<std::size_t> shape = {m};
×
431

432
  // Create array of zeros
433
  xt::xarray<double> cnt {shape, 0.0};
×
434
  bool outside_ = false;
×
435

436
  for (int64_t i = 0; i < length; i++) {
×
437
    const auto& site = bank[i];
×
438

439
    // determine scoring bin for entropy mesh
440
    int mesh_bin = get_bin(site.r);
×
441

442
    // if outside mesh, skip particle
443
    if (mesh_bin < 0) {
×
444
      outside_ = true;
×
445
      continue;
×
446
    }
447

448
    // Add to appropriate bin
449
    cnt(mesh_bin) += site.wgt;
×
450
  }
451

452
  // Create copy of count data. Since ownership will be acquired by xtensor,
453
  // std::allocator must be used to avoid Valgrind mismatched free() / delete
454
  // warnings.
455
  int total = cnt.size();
×
456
  double* cnt_reduced = std::allocator<double> {}.allocate(total);
×
457

458
#ifdef OPENMC_MPI
459
  // collect values from all processors
460
  MPI_Reduce(
461
    cnt.data(), cnt_reduced, total, MPI_DOUBLE, MPI_SUM, 0, mpi::intracomm);
462

463
  // Check if there were sites outside the mesh for any processor
464
  if (outside) {
465
    MPI_Reduce(&outside_, outside, 1, MPI_C_BOOL, MPI_LOR, 0, mpi::intracomm);
466
  }
467
#else
468
  std::copy(cnt.data(), cnt.data() + total, cnt_reduced);
469
  if (outside)
470
    *outside = outside_;
471
#endif
472

473
  // Adapt reduced values in array back into an xarray
474
  auto arr = xt::adapt(cnt_reduced, total, xt::acquire_ownership(), shape);
×
475
  xt::xarray<double> counts = arr;
×
476

477
  return counts;
×
478
}
479

480
// raytrace through the mesh. The template class T will do the tallying.
481
// A modern optimizing compiler can recognize the noop method of T and eleminate
482
// that call entirely.
483
template<class T>
484
void StructuredMesh::raytrace_mesh(
667,401,707✔
485
  Position r0, Position r1, const Direction& u, T tally) const
486
{
487
  // TODO: when c++-17 is available, use "if constexpr ()" to compile-time
488
  // enable/disable tally calls for now, T template type needs to provide both
489
  // surface and track methods, which might be empty. modern optimizing
490
  // compilers will (hopefully) eliminate the complete code (including
491
  // calculation of parameters) but for the future: be explicit
492

493
  // Compute the length of the entire track.
494
  double total_distance = (r1 - r0).norm();
667,401,707✔
495
  if (total_distance == 0.0)
667,401,707✔
496
    return;
6,663,832✔
497

498
  const int n = n_dimension_;
660,737,875✔
499

500
  // Flag if position is inside the mesh
501
  bool in_mesh;
502

503
  // Position is r = r0 + u * traveled_distance, start at r0
504
  double traveled_distance {0.0};
660,737,875✔
505

506
  // Calculate index of current cell. Offset the position a tiny bit in
507
  // direction of flight
508
  MeshIndex ijk = get_indices(r0 + TINY_BIT * u, in_mesh);
660,737,875✔
509

510
  // if track is very short, assume that it is completely inside one cell.
511
  // Only the current cell will score and no surfaces
512
  if (total_distance < 2 * TINY_BIT) {
660,737,875✔
513
    if (in_mesh) {
14✔
514
      tally.track(ijk, 1.0);
×
515
    }
516
    return;
14✔
517
  }
518

519
  // translate start and end positions,
520
  // this needs to come after the get_indices call because it does its own
521
  // translation
522
  local_coords(r0);
660,737,861✔
523
  local_coords(r1);
660,737,861✔
524

525
  // Calculate initial distances to next surfaces in all three dimensions
526
  std::array<MeshDistance, 3> distances;
1,321,475,722✔
527
  for (int k = 0; k < n; ++k) {
2,147,483,647✔
528
    distances[k] = distance_to_grid_boundary(ijk, k, r0, u, 0.0);
1,961,176,203✔
529
  }
530

531
  // Loop until r = r1 is eventually reached
532
  while (true) {
403,347,171✔
533

534
    if (in_mesh) {
1,064,085,032✔
535

536
      // find surface with minimal distance to current position
537
      const auto k = std::min_element(distances.begin(), distances.end()) -
960,308,842✔
538
                     distances.begin();
960,308,842✔
539

540
      // Tally track length delta since last step
541
      tally.track(ijk,
960,308,842✔
542
        (std::min(distances[k].distance, total_distance) - traveled_distance) /
960,308,842✔
543
          total_distance);
544

545
      // update position and leave, if we have reached end position
546
      traveled_distance = distances[k].distance;
960,308,842✔
547
      if (traveled_distance >= total_distance)
960,308,842✔
548
        return;
563,180,905✔
549

550
      // If we have not reached r1, we have hit a surface. Tally outward current
551
      tally.surface(ijk, k, distances[k].max_surface, false);
397,127,937✔
552

553
      // Update cell and calculate distance to next surface in k-direction.
554
      // The two other directions are still valid!
555
      ijk[k] = distances[k].next_index;
397,127,937✔
556
      distances[k] =
397,127,937✔
557
        distance_to_grid_boundary(ijk, k, r0, u, traveled_distance);
397,127,937✔
558

559
      // Check if we have left the interior of the mesh
560
      in_mesh = ((ijk[k] >= 1) && (ijk[k] <= shape_[k]));
397,127,937✔
561

562
      // If we are still inside the mesh, tally inward current for the next cell
563
      if (in_mesh)
397,127,937✔
564
        tally.surface(ijk, k, !distances[k].max_surface, true);
371,396,277✔
565

566
    } else { // not inside mesh
567

568
      // For all directions outside the mesh, find the distance that we need to
569
      // travel to reach the next surface. Use the largest distance, as only
570
      // this will cross all outer surfaces.
571
      int k_max {0};
103,776,190✔
572
      for (int k = 0; k < n; ++k) {
412,372,940✔
573
        if ((ijk[k] < 1 || ijk[k] > shape_[k]) &&
413,923,998✔
574
            (distances[k].distance > traveled_distance)) {
105,327,248✔
575
          traveled_distance = distances[k].distance;
104,420,796✔
576
          k_max = k;
104,420,796✔
577
        }
578
      }
579

580
      // If r1 is not inside the mesh, exit here
581
      if (traveled_distance >= total_distance)
103,776,190✔
582
        return;
97,556,956✔
583

584
      // Calculate the new cell index and update all distances to next surfaces.
585
      ijk = get_indices(r0 + (traveled_distance + TINY_BIT) * u, in_mesh);
6,219,234✔
586
      for (int k = 0; k < n; ++k) {
24,609,466✔
587
        distances[k] =
18,390,232✔
588
          distance_to_grid_boundary(ijk, k, r0, u, traveled_distance);
18,390,232✔
589
      }
590

591
      // If inside the mesh, Tally inward current
592
      if (in_mesh)
6,219,234✔
593
        tally.surface(ijk, k_max, !distances[k_max].max_surface, true);
4,615,722✔
594
    }
595
  }
596
}
597

261,103,324✔
598
void StructuredMesh::bins_crossed(Position r0, Position r1, const Direction& u,
599
  vector<int>& bins, vector<double>& lengths) const
600
{
601

602
  // Helper tally class.
603
  // stores a pointer to the mesh class and references to bins and lengths
604
  // parameters. Performs the actual tally through the track method.
605
  struct TrackAggregator {
606
    TrackAggregator(
607
      const StructuredMesh* _mesh, vector<int>& _bins, vector<double>& _lengths)
261,103,324✔
608
      : mesh(_mesh), bins(_bins), lengths(_lengths)
261,103,324✔
609
    {}
×
610
    void surface(const MeshIndex& ijk, int k, bool max, bool inward) const {}
611
    void track(const MeshIndex& ijk, double l) const
261,103,324✔
612
    {
613
      bins.push_back(mesh->get_bin_from_indices(ijk));
614
      lengths.push_back(l);
615
    }
616

617
    const StructuredMesh* mesh;
261,103,324✔
618
    vector<int>& bins;
619
    vector<double>& lengths;
620
  };
621

261,103,324✔
622
  // Perform the mesh raytrace with the helper class.
623
  raytrace_mesh(r0, r1, u, TrackAggregator(this, bins, lengths));
624
}
625

261,103,324✔
626
void StructuredMesh::surface_bins_crossed(
×
627
  Position r0, Position r1, const Direction& u, vector<int>& bins) const
×
628
{
629

×
630
  // Helper tally class.
631
  // stores a pointer to the mesh class and a reference to the bins parameter.
632
  // Performs the actual tally through the surface method.
633
  struct SurfaceAggregator {
634
    SurfaceAggregator(const StructuredMesh* _mesh, vector<int>& _bins)
635
      : mesh(_mesh), bins(_bins)
261,103,324✔
636
    {}
261,103,324✔
637
    void surface(const MeshIndex& ijk, int k, bool max, bool inward) const
638
    {
639
      int i_bin =
522,206,648✔
640
        4 * mesh->n_dimension_ * mesh->get_bin_from_indices(ijk) + 4 * k;
1,042,271,128✔
641
      if (max)
781,167,804✔
642
        i_bin += 2;
643
      if (inward)
644
        i_bin += 1;
645
      bins.push_back(i_bin);
65,019,482✔
646
    }
647
    void track(const MeshIndex& idx, double l) const {}
326,122,806✔
648

649
    const StructuredMesh* mesh;
650
    vector<int>& bins;
322,807,546✔
651
  };
322,807,546✔
652

653
  // Perform the mesh raytrace with the helper class.
654
  raytrace_mesh(r0, r1, u, SurfaceAggregator(this, bins));
322,807,546✔
655
}
322,807,546✔
656

657
//==============================================================================
658
// RegularMesh implementation
659
//==============================================================================
322,807,546✔
660

322,807,546✔
661
RegularMesh::RegularMesh(pugi::xml_node node) : StructuredMesh {node}
258,072,250✔
662
{
663
  // Determine number of dimensions for mesh
664
  if (!check_for_node(node, "dimension")) {
64,735,296✔
665
    fatal_error("Must specify <dimension> on a regular mesh.");
666
  }
667

668
  xt::xtensor<int, 1> shape = get_node_xarray<int>(node, "dimension");
64,735,296✔
669
  int n = n_dimension_ = shape.size();
64,735,296✔
670
  if (n != 1 && n != 2 && n != 3) {
64,735,296✔
671
    fatal_error("Mesh must be one, two, or three dimensions.");
672
  }
673
  std::copy(shape.begin(), shape.end(), shape_.begin());
64,735,296✔
674

675
  // Check that dimensions are all greater than zero
676
  if (xt::any(shape <= 0)) {
64,735,296✔
677
    fatal_error("All entries on the <dimension> element for a tally "
62,294,210✔
678
                "mesh must be positive.");
679
  }
680

681
  // Check for lower-left coordinates
682
  if (check_for_node(node, "lower_left")) {
683
    // Read mesh lower-left corner location
684
    lower_left_ = get_node_xarray<double>(node, "lower_left");
3,315,260✔
685
  } else {
12,851,190✔
686
    fatal_error("Must specify <lower_left> on a mesh.");
13,033,834✔
687
  }
3,497,904✔
688

3,422,696✔
689
  // Make sure lower_left and dimension match
3,422,696✔
690
  if (shape.size() != lower_left_.size()) {
691
    fatal_error("Number of entries on <lower_left> must be the same "
692
                "as the number of entries on <dimension>.");
693
  }
694

3,315,260✔
695
  if (check_for_node(node, "width")) {
3,031,074✔
696
    // Make sure one of upper-right or width were specified
697
    if (check_for_node(node, "upper_right")) {
698
      fatal_error("Cannot specify both <upper_right> and <width> on a mesh.");
284,186✔
699
    }
1,003,632✔
700

719,446✔
701
    width_ = get_node_xarray<double>(node, "width");
719,446✔
702

703
    // Check to ensure width has same dimensions
704
    auto n = width_.size();
705
    if (n != lower_left_.size()) {
284,186✔
706
      fatal_error("Number of entries on <width> must be the same as "
255,024✔
707
                  "the number of entries on <lower_left>.");
708
    }
709

710
    // Check for negative widths
406,298,383✔
711
    if (xt::any(width_ < 0.0)) {
712
      fatal_error("Cannot have a negative <width> on a tally mesh.");
713
    }
714

715
    // Set width and upper right coordinate
716
    upper_right_ = xt::eval(lower_left_ + shape * width_);
717

718
  } else if (check_for_node(node, "upper_right")) {
719
    upper_right_ = get_node_xarray<double>(node, "upper_right");
720

406,298,383✔
721
    // Check to ensure width has same dimensions
406,298,383✔
722
    auto n = upper_right_.size();
6,663,832✔
723
    if (n != lower_left_.size()) {
724
      fatal_error("Number of entries on <upper_right> must be the "
399,634,551✔
725
                  "same as the number of entries on <lower_left>.");
726
    }
727

728
    // Check that upper-right is above lower-left
729
    if (xt::any(upper_right_ < lower_left_)) {
730
      fatal_error("The <upper_right> coordinates must be greater than "
399,634,551✔
731
                  "the <lower_left> coordinates on a tally mesh.");
732
    }
733

734
    // Set width
399,634,551✔
735
    width_ = xt::eval((upper_right_ - lower_left_) / shape);
736
  } else {
737
    fatal_error("Must specify either <upper_right> or <width> on a mesh.");
738
  }
399,634,551✔
739

14✔
740
  // Set volume fraction
×
741
  volume_frac_ = 1.0 / xt::prod(shape)();
742

14✔
743
  element_volume_ = 1.0;
744
  for (int i = 0; i < n_dimension_; i++) {
745
    element_volume_ *= width_[i];
746
  }
747
}
748

399,634,537✔
749
int RegularMesh::get_index_in_direction(double r, int i) const
399,634,537✔
750
{
751
  return std::ceil((r - lower_left_[i]) / width_[i]);
752
}
799,269,074✔
753

1,579,642,936✔
754
const std::string RegularMesh::mesh_type = "regular";
1,180,008,399✔
755

756
std::string RegularMesh::get_mesh_type() const
757
{
758
  return mesh_type;
338,327,689✔
759
}
760

737,962,226✔
761
double RegularMesh::positive_grid_boundary(const MeshIndex& ijk, int i) const
762
{
763
  return lower_left_[i] + ijk[i] * width_[i];
637,501,296✔
764
}
637,501,296✔
765

766
double RegularMesh::negative_grid_boundary(const MeshIndex& ijk, int i) const
767
{
637,501,296✔
768
  return lower_left_[i] + (ijk[i] - 1) * width_[i];
637,501,296✔
769
}
770

771
StructuredMesh::MeshDistance RegularMesh::distance_to_grid_boundary(
772
  const MeshIndex& ijk, int i, const Position& r0, const Direction& u,
637,501,296✔
773
  double l) const
637,501,296✔
774
{
305,108,655✔
775
  MeshDistance d;
776
  d.next_index = ijk[i];
777
  if (std::abs(u[i]) < FP_PRECISION)
332,392,641✔
778
    return d;
779

780
  d.max_surface = (u[i] > 0);
781
  if (d.max_surface && (ijk[i] <= shape_[i])) {
332,392,641✔
782
    d.next_index++;
332,392,641✔
783
    d.distance = (positive_grid_boundary(ijk, i) - r0[i]) / u[i];
332,392,641✔
784
  } else if (!d.max_surface && (ijk[i] >= 1)) {
785
    d.next_index--;
786
    d.distance = (negative_grid_boundary(ijk, i) - r0[i]) / u[i];
332,392,641✔
787
  }
788
  return d;
789
}
332,392,641✔
790

309,102,067✔
791
std::pair<vector<double>, vector<double>> RegularMesh::plot(
792
  Position plot_ll, Position plot_ur) const
793
{
794
  // Figure out which axes lie in the plane of the plot.
795
  array<int, 2> axes {-1, -1};
796
  if (plot_ur.z == plot_ll.z) {
797
    axes[0] = 0;
100,460,930✔
798
    if (n_dimension_ > 1)
399,521,750✔
799
      axes[1] = 1;
400,890,164✔
800
  } else if (plot_ur.y == plot_ll.y) {
101,829,344✔
801
    axes[0] = 0;
100,998,100✔
802
    if (n_dimension_ > 2)
100,998,100✔
803
      axes[1] = 2;
804
  } else if (plot_ur.x == plot_ll.x) {
805
    if (n_dimension_ > 1)
806
      axes[0] = 1;
807
    if (n_dimension_ > 2)
100,460,930✔
808
      axes[1] = 2;
94,525,882✔
809
  } else {
810
    fatal_error("Can only plot mesh lines on an axis-aligned plot");
811
  }
5,935,048✔
812

23,605,834✔
813
  // Get the coordinates of the mesh lines along both of the axes.
17,670,786✔
814
  array<vector<double>, 2> axis_lines;
17,670,786✔
815
  for (int i_ax = 0; i_ax < 2; ++i_ax) {
816
    int axis = axes[i_ax];
817
    if (axis == -1)
818
      continue;
5,935,048✔
819
    auto& lines {axis_lines[i_ax]};
4,360,698✔
820

821
    double coord = lower_left_[axis];
822
    for (int i = 0; i < shape_[axis] + 1; ++i) {
823
      if (coord >= plot_ll[axis] && coord <= plot_ur[axis])
824
        lines.push_back(coord);
406,298,383✔
825
      coord += width_[axis];
826
    }
827
  }
828

829
  return {axis_lines[0], axis_lines[1]};
830
}
831

832
void RegularMesh::to_hdf5(hid_t group) const
406,298,383✔
833
{
834
  hid_t mesh_group = create_group(group, "mesh " + std::to_string(id_));
406,298,383✔
835

406,298,383✔
836
  write_dataset(mesh_group, "type", "regular");
645,855,406✔
837
  write_dataset(mesh_group, "dimension", get_x_shape());
637,501,296✔
838
  write_dataset(mesh_group, "lower_left", lower_left_);
839
  write_dataset(mesh_group, "upper_right", upper_right_);
637,501,296✔
840
  write_dataset(mesh_group, "width", width_);
637,501,296✔
841

637,501,296✔
842
  close_group(mesh_group);
843
}
844

845
xt::xtensor<double, 1> RegularMesh::count_sites(
846
  const SourceSite* bank, int64_t length, bool* outside) const
847
{
848
  // Determine shape of array for counts
849
  std::size_t m = this->n_bins();
406,298,383✔
850
  vector<std::size_t> shape = {m};
406,298,383✔
851

852
  // Create array of zeros
261,103,324✔
853
  xt::xarray<double> cnt {shape, 0.0};
854
  bool outside_ = false;
855

856
  for (int64_t i = 0; i < length; i++) {
857
    const auto& site = bank[i];
858

859
    // determine scoring bin for entropy mesh
860
    int mesh_bin = get_bin(site.r);
261,103,324✔
861

261,103,324✔
862
    // if outside mesh, skip particle
261,103,324✔
863
    if (mesh_bin < 0) {
127,284,530✔
864
      outside_ = true;
865
      continue;
866
    }
127,284,530✔
867

127,284,530✔
868
    // Add to appropriate bin
63,591,786✔
869
    cnt(mesh_bin) += site.wgt;
127,284,530✔
870
  }
62,549,234✔
871

127,284,530✔
872
  // Create copy of count data. Since ownership will be acquired by xtensor,
127,284,530✔
873
  // std::allocator must be used to avoid Valgrind mismatched free() / delete
322,807,546✔
874
  // warnings.
875
  int total = cnt.size();
876
  double* cnt_reduced = std::allocator<double> {}.allocate(total);
877

878
#ifdef OPENMC_MPI
879
  // collect values from all processors
880
  MPI_Reduce(
261,103,324✔
881
    cnt.data(), cnt_reduced, total, MPI_DOUBLE, MPI_SUM, 0, mpi::intracomm);
261,103,324✔
882

883
  // Check if there were sites outside the mesh for any processor
884
  if (outside) {
885
    MPI_Reduce(&outside_, outside, 1, MPI_C_BOOL, MPI_LOR, 0, mpi::intracomm);
886
  }
887
#else
1,875✔
888
  std::copy(cnt.data(), cnt.data() + total, cnt_reduced);
889
  if (outside)
890
    *outside = outside_;
1,875✔
891
#endif
×
892

893
  // Adapt reduced values in array back into an xarray
894
  auto arr = xt::adapt(cnt_reduced, total, xt::acquire_ownership(), shape);
1,875✔
895
  xt::xarray<double> counts = arr;
1,875✔
896

1,875✔
897
  return counts;
×
898
}
899

1,875✔
900
double RegularMesh::volume(const MeshIndex& ijk) const
901
{
902
  return element_volume_;
1,875✔
903
}
×
904

905
//==============================================================================
906
// RectilinearMesh implementation
907
//==============================================================================
908

1,875✔
909
RectilinearMesh::RectilinearMesh(pugi::xml_node node) : StructuredMesh {node}
910
{
1,875✔
911
  n_dimension_ = 3;
912

×
913
  grid_[0] = get_node_array<double>(node, "x_grid");
914
  grid_[1] = get_node_array<double>(node, "y_grid");
915
  grid_[2] = get_node_array<double>(node, "z_grid");
916

1,875✔
917
  if (int err = set_grid()) {
×
918
    fatal_error(openmc_err_msg);
919
  }
920
}
921

1,875✔
922
const std::string RectilinearMesh::mesh_type = "rectilinear";
923

57✔
924
std::string RectilinearMesh::get_mesh_type() const
×
925
{
926
  return mesh_type;
927
}
57✔
928

929
double RectilinearMesh::positive_grid_boundary(
930
  const MeshIndex& ijk, int i) const
57✔
931
{
57✔
932
  return grid_[i][ijk[i]];
×
933
}
934

935
double RectilinearMesh::negative_grid_boundary(
936
  const MeshIndex& ijk, int i) const
937
{
57✔
938
  return grid_[i][ijk[i] - 1];
×
939
}
940

941
StructuredMesh::MeshDistance RectilinearMesh::distance_to_grid_boundary(
942
  const MeshIndex& ijk, int i, const Position& r0, const Direction& u,
57✔
943
  double l) const
944
{
1,818✔
945
  MeshDistance d;
1,818✔
946
  d.next_index = ijk[i];
947
  if (std::abs(u[i]) < FP_PRECISION)
948
    return d;
1,818✔
949

1,818✔
950
  d.max_surface = (u[i] > 0);
×
951
  if (d.max_surface && (ijk[i] <= shape_[i])) {
952
    d.next_index++;
953
    d.distance = (positive_grid_boundary(ijk, i) - r0[i]) / u[i];
954
  } else if (!d.max_surface && (ijk[i] > 0)) {
955
    d.next_index--;
1,818✔
956
    d.distance = (negative_grid_boundary(ijk, i) - r0[i]) / u[i];
×
957
  }
958
  return d;
959
}
960

961
int RectilinearMesh::set_grid()
1,818✔
962
{
963
  shape_ = {static_cast<int>(grid_[0].size()) - 1,
×
964
    static_cast<int>(grid_[1].size()) - 1,
965
    static_cast<int>(grid_[2].size()) - 1};
966

967
  for (const auto& g : grid_) {
1,875✔
968
    if (g.size() < 2) {
969
      set_errmsg("x-, y-, and z- grids for rectilinear meshes "
1,875✔
970
                 "must each have at least 2 points");
7,315✔
971
      return OPENMC_E_INVALID_ARGUMENT;
5,440✔
972
    }
973
    if (std::adjacent_find(g.begin(), g.end(), std::greater_equal<>()) !=
1,875✔
974
        g.end()) {
975
      set_errmsg("Values in for x-, y-, and z- grids for "
2,147,483,647✔
976
                 "rectilinear meshes must be sorted and unique.");
977
      return OPENMC_E_INVALID_ARGUMENT;
2,147,483,647✔
978
    }
979
  }
980

981
  lower_left_ = {grid_[0].front(), grid_[1].front(), grid_[2].front()};
982
  upper_right_ = {grid_[0].back(), grid_[1].back(), grid_[2].back()};
1,650✔
983

984
  return 0;
1,650✔
985
}
986

987
int RectilinearMesh::get_index_in_direction(double r, int i) const
822,717,480✔
988
{
989
  return lower_bound_index(grid_[i].begin(), grid_[i].end(), r) + 1;
822,717,480✔
990
}
991

992
std::pair<vector<double>, vector<double>> RectilinearMesh::plot(
821,955,008✔
993
  Position plot_ll, Position plot_ur) const
994
{
821,955,008✔
995
  // Figure out which axes lie in the plane of the plot.
996
  array<int, 2> axes {-1, -1};
997
  if (plot_ur.z == plot_ll.z) {
1,668,395,390✔
998
    axes = {0, 1};
999
  } else if (plot_ur.y == plot_ll.y) {
1000
    axes = {0, 2};
1001
  } else if (plot_ur.x == plot_ll.x) {
1,668,395,390✔
1002
    axes = {1, 2};
1,668,395,390✔
1003
  } else {
1,668,395,390✔
1004
    fatal_error("Can only plot mesh lines on an axis-aligned plot");
×
1005
  }
1006

1,668,395,390✔
1007
  // Get the coordinates of the mesh lines along both of the axes.
1,668,395,390✔
1008
  array<vector<double>, 2> axis_lines;
822,381,060✔
1009
  for (int i_ax = 0; i_ax < 2; ++i_ax) {
822,381,060✔
1010
    int axis = axes[i_ax];
846,014,330✔
1011
    vector<double>& lines {axis_lines[i_ax]};
821,618,588✔
1012

821,618,588✔
1013
    for (auto coord : grid_[axis]) {
1014
      if (coord >= plot_ll[axis] && coord <= plot_ur[axis])
1,668,395,390✔
1015
        lines.push_back(coord);
1016
    }
1017
  }
28✔
1018

1019
  return {axis_lines[0], axis_lines[1]};
1020
}
1021

28✔
1022
void RectilinearMesh::to_hdf5(hid_t group) const
28✔
1023
{
28✔
1024
  hid_t mesh_group = create_group(group, "mesh " + std::to_string(id_));
28✔
1025

28✔
1026
  write_dataset(mesh_group, "type", "rectilinear");
×
1027
  write_dataset(mesh_group, "x_grid", grid_[0]);
×
1028
  write_dataset(mesh_group, "y_grid", grid_[1]);
×
1029
  write_dataset(mesh_group, "z_grid", grid_[2]);
×
1030

×
1031
  close_group(mesh_group);
×
1032
}
×
1033

×
1034
double RectilinearMesh::volume(const MeshIndex& ijk) const
×
1035
{
1036
  double vol {1.0};
×
1037

1038
  for (int i = 0; i < n_dimension_; i++) {
1039
    vol *= grid_[i][ijk[i] + 1] - grid_[i][ijk[i]];
1040
  }
28✔
1041
  return vol;
84✔
1042
}
56✔
1043

56✔
1044
//==============================================================================
×
1045
// CylindricalMesh implementation
56✔
1046
//==============================================================================
1047

56✔
1048
CylindricalMesh::CylindricalMesh(pugi::xml_node node)
364✔
1049
  : PeriodicStructuredMesh {node}
308✔
1050
{
308✔
1051
  n_dimension_ = 3;
308✔
1052
  grid_[0] = get_node_array<double>(node, "r_grid");
1053
  grid_[1] = get_node_array<double>(node, "phi_grid");
1054
  grid_[2] = get_node_array<double>(node, "z_grid");
1055
  origin_ = get_node_position(node, "origin");
56✔
1056

28✔
1057
  if (int err = set_grid()) {
1058
    fatal_error(openmc_err_msg);
2,264✔
1059
  }
1060
}
2,264✔
1061

1062
const std::string CylindricalMesh::mesh_type = "cylindrical";
2,264✔
1063

2,264✔
1064
std::string CylindricalMesh::get_mesh_type() const
2,264✔
1065
{
2,264✔
1066
  return mesh_type;
2,264✔
1067
}
1068

2,264✔
1069
StructuredMesh::MeshIndex CylindricalMesh::get_indices(
2,264✔
1070
  Position r, bool& in_mesh) const
1071
{
13,371✔
1072
  local_coords(r);
1073

1074
  Position mapped_r;
1075
  mapped_r[0] = std::hypot(r.x, r.y);
13,371✔
1076
  mapped_r[2] = r[2];
13,371✔
1077

1078
  if (mapped_r[0] < FP_PRECISION) {
1079
    mapped_r[1] = 0.0;
13,371✔
1080
  } else {
13,371✔
1081
    mapped_r[1] = std::atan2(r.y, r.x);
1082
    if (mapped_r[1] < 0)
13,137,855✔
1083
      mapped_r[1] += 2 * M_PI;
13,124,484✔
1084
  }
1085

1086
  MeshIndex idx = StructuredMesh::get_indices(mapped_r, in_mesh);
13,124,484✔
1087

1088
  idx[1] = sanitize_phi(idx[1]);
1089

13,124,484✔
1090
  return idx;
×
1091
}
×
1092

1093
Position CylindricalMesh::sample_element(
1094
  const MeshIndex& ijk, uint64_t* seed) const
1095
{
13,124,484✔
1096
  double r_min = this->r(ijk[0] - 1);
1097
  double r_max = this->r(ijk[0]);
1098

1099
  double phi_min = this->phi(ijk[1] - 1);
1100
  double phi_max = this->phi(ijk[1]);
1101

13,371✔
1102
  double z_min = this->z(ijk[2] - 1);
13,371✔
1103
  double z_max = this->z(ijk[2]);
1104

1105
  double r_min_sq = r_min * r_min;
1106
  double r_max_sq = r_max * r_max;
7,035✔
1107
  double r = std::sqrt(uniform_distribution(r_min_sq, r_max_sq, seed));
7,035✔
1108
  double phi = uniform_distribution(phi_min, phi_max, seed);
1109
  double z = uniform_distribution(z_min, z_max, seed);
1110

7,035✔
1111
  double x = r * std::cos(phi);
7,035✔
1112
  double y = r * std::sin(phi);
1113

1114
  return origin_ + Position(x, y, z);
6,336✔
1115
}
6,336✔
1116

6,336✔
1117
double CylindricalMesh::find_r_crossing(
1118
  const Position& r, const Direction& u, double l, int shell) const
1119
{
1120

13,371✔
1121
  if ((shell < 0) || (shell > shape_[0]))
13,371✔
1122
    return INFTY;
1123

26,742✔
1124
  // solve r.x^2 + r.y^2 == r0^2
13,371✔
1125
  // x^2 + 2*s*u*x + s^2*u^2 + s^2*v^2+2*s*v*y + y^2 -r0^2 = 0
1126
  // s^2 * (u^2 + v^2) + 2*s*(u*x+v*y) + x^2+y^2-r0^2 = 0
1,146,040✔
1127

1128
  const double r0 = grid_[0][shell];
1,146,040✔
1129
  if (r0 == 0.0)
1130
    return INFTY;
1131

1132
  const double denominator = u.x * u.x + u.y * u.y;
1133

1134
  // Direction of flight is in z-direction. Will never intersect r.
1135
  if (std::abs(denominator) < FP_PRECISION)
127✔
1136
    return INFTY;
1137

127✔
1138
  // inverse of dominator to help the compiler to speed things up
1139
  const double inv_denominator = 1.0 / denominator;
127✔
1140

127✔
1141
  const double p = (u.x * r.x + u.y * r.y) * inv_denominator;
127✔
1142
  double c = r.x * r.x + r.y * r.y - r0 * r0;
1143
  double D = p * p - c * inv_denominator;
127✔
UNCOV
1144

×
1145
  if (D < 0.0)
1146
    return INFTY;
127✔
1147

1148
  D = std::sqrt(D);
1149

1150
  // the solution -p - D is always smaller as -p + D : Check this one first
136✔
1151
  if (std::abs(c) <= RADIAL_MESH_TOL)
1152
    return INFTY;
136✔
1153

1154
  if (-p - D > l)
1155
    return -p - D;
58,189,642✔
1156
  if (-p + D > l)
1157
    return -p + D;
1158

58,189,642✔
1159
  return INFTY;
1160
}
1161

57,658,942✔
1162
double CylindricalMesh::find_phi_crossing(
1163
  const Position& r, const Direction& u, double l, int shell) const
1164
{
57,658,942✔
1165
  // Phi grid is [0, 2Ï€], thus there is no real surface to cross
1166
  if (full_phi_ && (shape_[1] == 1))
1167
    return INFTY;
117,000,428✔
1168

1169
  shell = sanitize_phi(shell);
1170

1171
  const double p0 = grid_[1][shell];
117,000,428✔
1172

117,000,428✔
1173
  // solve y(s)/x(s) = tan(p0) = sin(p0)/cos(p0)
117,000,428✔
UNCOV
1174
  // => x(s) * cos(p0) = y(s) * sin(p0)
×
1175
  // => (y + s * v) * cos(p0) = (x + s * u) * sin(p0)
1176
  // = s * (v * cos(p0) - u * sin(p0)) = - (y * cos(p0) - x * sin(p0))
117,000,428✔
1177

117,000,428✔
1178
  const double c0 = std::cos(p0);
58,189,642✔
1179
  const double s0 = std::sin(p0);
58,189,642✔
1180

58,810,786✔
1181
  const double denominator = (u.x * s0 - u.y * c0);
57,658,942✔
1182

57,658,942✔
1183
  // Check if direction of flight is not parallel to phi surface
1184
  if (std::abs(denominator) > FP_PRECISION) {
117,000,428✔
1185
    const double s = -(r.x * s0 - r.y * c0) / denominator;
1186
    // Check if solution is in positive direction of flight and crosses the
1187
    // correct phi surface (not -phi)
193✔
1188
    if ((s > l) && ((c0 * (r.x + s * u.x) + s0 * (r.y + s * u.y)) > 0.0))
1189
      return s;
193✔
1190
  }
193✔
1191

193✔
1192
  return INFTY;
1193
}
772✔
1194

579✔
UNCOV
1195
StructuredMesh::MeshDistance CylindricalMesh::find_z_crossing(
×
1196
  const Position& r, const Direction& u, double l, int shell) const
UNCOV
1197
{
×
1198
  MeshDistance d;
1199
  d.next_index = shell;
579✔
1200

1,158✔
UNCOV
1201
  // Direction of flight is within xy-plane. Will never intersect z.
×
1202
  if (std::abs(u.z) < FP_PRECISION)
UNCOV
1203
    return d;
×
1204

1205
  d.max_surface = (u.z > 0.0);
1206
  if (d.max_surface && (shell <= shape_[2])) {
1207
    d.next_index += 1;
193✔
1208
    d.distance = (grid_[2][shell] - r.z) / u.z;
193✔
1209
  } else if (!d.max_surface && (shell > 0)) {
1210
    d.next_index -= 1;
193✔
1211
    d.distance = (grid_[2][shell - 1] - r.z) / u.z;
1212
  }
1213
  return d;
167,458,062✔
1214
}
1215

167,458,062✔
1216
StructuredMesh::MeshDistance CylindricalMesh::distance_to_grid_boundary(
1217
  const MeshIndex& ijk, int i, const Position& r0, const Direction& u,
1218
  double l) const
14✔
1219
{
1220
  Position r = r0 - origin_;
1221

1222
  if (i == 0) {
14✔
1223

14✔
UNCOV
1224
    return std::min(
×
1225
      MeshDistance(ijk[i] + 1, true, find_r_crossing(r, u, l, ijk[i])),
14✔
1226
      MeshDistance(ijk[i] - 1, false, find_r_crossing(r, u, l, ijk[i] - 1)));
14✔
1227

×
UNCOV
1228
  } else if (i == 1) {
×
1229

UNCOV
1230
    return std::min(MeshDistance(sanitize_phi(ijk[i] + 1), true,
×
1231
                      find_phi_crossing(r, u, l, ijk[i])),
1232
      MeshDistance(sanitize_phi(ijk[i] - 1), false,
1233
        find_phi_crossing(r, u, l, ijk[i] - 1)));
1234

14✔
1235
  } else {
42✔
1236
    return find_z_crossing(r, u, l, ijk[i]);
28✔
1237
  }
28✔
1238
}
1239

140✔
1240
int CylindricalMesh::set_grid()
112✔
1241
{
112✔
1242
  shape_ = {static_cast<int>(grid_[0].size()) - 1,
1243
    static_cast<int>(grid_[1].size()) - 1,
1244
    static_cast<int>(grid_[2].size()) - 1};
1245

28✔
1246
  for (const auto& g : grid_) {
14✔
1247
    if (g.size() < 2) {
1248
      set_errmsg("r-, phi-, and z- grids for cylindrical meshes "
136✔
1249
                 "must each have at least 2 points");
1250
      return OPENMC_E_INVALID_ARGUMENT;
136✔
1251
    }
1252
    if (std::adjacent_find(g.begin(), g.end(), std::greater_equal<>()) !=
136✔
1253
        g.end()) {
136✔
1254
      set_errmsg("Values in for r-, phi-, and z- grids for "
136✔
1255
                 "cylindrical meshes must be sorted and unique.");
136✔
1256
      return OPENMC_E_INVALID_ARGUMENT;
1257
    }
136✔
1258
  }
136✔
1259
  if (grid_[0].front() < 0.0) {
UNCOV
1260
    set_errmsg("r-grid for "
×
1261
               "cylindrical meshes must start at r >= 0.");
UNCOV
1262
    return OPENMC_E_INVALID_ARGUMENT;
×
1263
  }
UNCOV
1264
  if (grid_[1].front() < 0.0) {
×
UNCOV
1265
    set_errmsg("phi-grid for "
×
1266
               "cylindrical meshes must start at phi >= 0.");
UNCOV
1267
    return OPENMC_E_INVALID_ARGUMENT;
×
1268
  }
1269
  if (grid_[1].back() > 2.0 * PI) {
1270
    set_errmsg("phi-grids for "
1271
               "cylindrical meshes must end with theta <= 2*pi.");
1272

1273
    return OPENMC_E_INVALID_ARGUMENT;
1274
  }
467✔
1275

467✔
1276
  full_phi_ = (grid_[1].front() == 0.0) && (grid_[1].back() == 2.0 * PI);
1277

467✔
1278
  lower_left_ = {grid_[0].front(), grid_[1].front(), grid_[2].front()};
467✔
1279
  upper_right_ = {grid_[0].back(), grid_[1].back(), grid_[2].back()};
467✔
1280

467✔
1281
  return 0;
467✔
1282
}
1283

467✔
1284
int CylindricalMesh::get_index_in_direction(double r, int i) const
×
1285
{
1286
  return lower_bound_index(grid_[i].begin(), grid_[i].end(), r) + 1;
467✔
1287
}
1288

1289
std::pair<vector<double>, vector<double>> CylindricalMesh::plot(
1290
  Position plot_ll, Position plot_ur) const
70✔
1291
{
1292
  fatal_error("Plot of cylindrical Mesh not implemented");
70✔
1293

1294
  // Figure out which axes lie in the plane of the plot.
1295
  array<vector<double>, 2> axis_lines;
58,086,224✔
1296
  return {axis_lines[0], axis_lines[1]};
1297
}
1298

58,086,224✔
1299
void CylindricalMesh::to_hdf5(hid_t group) const
1300
{
58,086,224✔
1301
  hid_t mesh_group = create_group(group, "mesh " + std::to_string(id_));
58,086,224✔
1302

58,086,224✔
1303
  write_dataset(mesh_group, "type", "cylindrical");
1304
  write_dataset(mesh_group, "r_grid", grid_[0]);
58,086,224✔
UNCOV
1305
  write_dataset(mesh_group, "phi_grid", grid_[1]);
×
1306
  write_dataset(mesh_group, "z_grid", grid_[2]);
1307
  write_dataset(mesh_group, "origin", origin_);
58,086,224✔
1308

58,086,224✔
1309
  close_group(mesh_group);
29,014,846✔
1310
}
1311

1312
double CylindricalMesh::volume(const MeshIndex& ijk) const
58,086,224✔
1313
{
1314
  double r_i = grid_[0][ijk[0] - 1];
58,086,224✔
1315
  double r_o = grid_[0][ijk[0]];
1316

58,086,224✔
1317
  double phi_i = grid_[1][ijk[1] - 1];
1318
  double phi_o = grid_[1][ijk[1]];
1319

112,000✔
1320
  double z_i = grid_[2][ijk[2] - 1];
1321
  double z_o = grid_[2][ijk[2]];
1322

112,000✔
1323
  return 0.5 * (r_o * r_o - r_i * r_i) * (phi_o - phi_i) * (z_o - z_i);
112,000✔
1324
}
1325

112,000✔
1326
//==============================================================================
112,000✔
1327
// SphericalMesh implementation
1328
//==============================================================================
112,000✔
1329

112,000✔
1330
SphericalMesh::SphericalMesh(pugi::xml_node node)
1331
  : PeriodicStructuredMesh {node}
112,000✔
1332
{
112,000✔
1333
  n_dimension_ = 3;
112,000✔
1334

112,000✔
1335
  grid_[0] = get_node_array<double>(node, "r_grid");
112,000✔
1336
  grid_[1] = get_node_array<double>(node, "theta_grid");
1337
  grid_[2] = get_node_array<double>(node, "phi_grid");
112,000✔
1338
  origin_ = get_node_position(node, "origin");
112,000✔
1339

1340
  if (int err = set_grid()) {
112,000✔
1341
    fatal_error(openmc_err_msg);
1342
  }
1343
}
171,066,700✔
1344

1345
const std::string SphericalMesh::mesh_type = "spherical";
1346

1347
std::string SphericalMesh::get_mesh_type() const
171,066,700✔
1348
{
20,825,084✔
1349
  return mesh_type;
1350
}
1351

1352
StructuredMesh::MeshIndex SphericalMesh::get_indices(
1353
  Position r, bool& in_mesh) const
1354
{
150,241,616✔
1355
  local_coords(r);
150,241,616✔
1356

8,218,098✔
1357
  Position mapped_r;
1358
  mapped_r[0] = r.norm();
142,023,518✔
1359

1360
  if (mapped_r[0] < FP_PRECISION) {
1361
    mapped_r[1] = 0.0;
142,023,518✔
1362
    mapped_r[2] = 0.0;
×
1363
  } else {
1364
    mapped_r[1] = std::acos(r.z / mapped_r.x);
1365
    mapped_r[2] = std::atan2(r.y, r.x);
142,023,518✔
1366
    if (mapped_r[2] < 0)
1367
      mapped_r[2] += 2 * M_PI;
142,023,518✔
1368
  }
142,023,518✔
1369

142,023,518✔
1370
  MeshIndex idx = StructuredMesh::get_indices(mapped_r, in_mesh);
1371

142,023,518✔
1372
  idx[1] = sanitize_theta(idx[1]);
19,669,482✔
1373
  idx[2] = sanitize_phi(idx[2]);
1374

122,354,036✔
1375
  return idx;
1376
}
1377

122,354,036✔
1378
Position SphericalMesh::sample_element(
8,233,792✔
1379
  const MeshIndex& ijk, uint64_t* seed) const
1380
{
114,120,244✔
1381
  double r_min = this->r(ijk[0] - 1);
23,241,092✔
1382
  double r_max = this->r(ijk[0]);
90,879,152✔
1383

55,112,708✔
1384
  double theta_min = this->theta(ijk[1] - 1);
1385
  double theta_max = this->theta(ijk[1]);
35,766,444✔
1386

1387
  double phi_min = this->phi(ijk[2] - 1);
1388
  double phi_max = this->phi(ijk[2]);
84,275,884✔
1389

1390
  double cos_theta = uniform_distribution(theta_min, theta_max, seed);
1391
  double sin_theta = std::sin(std::acos(cos_theta));
1392
  double phi = uniform_distribution(phi_min, phi_max, seed);
84,275,884✔
1393
  double r_min_cub = std::pow(r_min, 3);
37,877,504✔
1394
  double r_max_cub = std::pow(r_max, 3);
1395
  // might be faster to do rejection here?
46,398,380✔
1396
  double r = std::cbrt(uniform_distribution(r_min_cub, r_max_cub, seed));
1397

46,398,380✔
1398
  double x = r * std::cos(phi) * sin_theta;
1399
  double y = r * std::sin(phi) * sin_theta;
1400
  double z = r * cos_theta;
1401

1402
  return origin_ + Position(x, y, z);
1403
}
1404

46,398,380✔
1405
double SphericalMesh::find_r_crossing(
46,398,380✔
1406
  const Position& r, const Direction& u, double l, int shell) const
1407
{
46,398,380✔
1408
  if ((shell < 0) || (shell > shape_[0]))
1409
    return INFTY;
1410

46,398,380✔
1411
  // solve |r+s*u| = r0
46,398,380✔
1412
  // |r+s*u| = |r| + 2*s*r*u + s^2 (|u|==1 !)
1413
  const double r0 = grid_[0][shell];
1414
  if (r0 == 0.0)
46,398,380✔
1415
    return INFTY;
21,862,932✔
1416
  const double p = r.dot(u);
1417
  double c = r.dot(r) - r0 * r0;
1418
  double D = p * p - c;
24,535,448✔
1419

1420
  if (std::abs(c) <= RADIAL_MESH_TOL)
1421
    return INFTY;
45,747,576✔
1422

1423
  if (D >= 0.0) {
1424
    D = std::sqrt(D);
45,747,576✔
1425
    // the solution -p - D is always smaller as -p + D : Check this one first
45,747,576✔
1426
    if (-p - D > l)
1427
      return -p - D;
1428
    if (-p + D > l)
45,747,576✔
1429
      return -p + D;
560,000✔
1430
  }
1431

45,187,576✔
1432
  return INFTY;
45,187,576✔
1433
}
20,683,936✔
1434

20,683,936✔
1435
double SphericalMesh::find_theta_crossing(
24,503,640✔
1436
  const Position& r, const Direction& u, double l, int shell) const
21,043,806✔
1437
{
21,043,806✔
1438
  // Theta grid is [0, π], thus there is no real surface to cross
1439
  if (full_theta_ && (shape_[1] == 1))
45,187,576✔
1440
    return INFTY;
1441

1442
  shell = sanitize_theta(shell);
173,418,868✔
1443

1444
  // solving z(s) = cos/theta) * r(s) with r(s) = r+s*u
1445
  // yields
1446
  // a*s^2 + 2*b*s + c == 0 with
173,418,868✔
1447
  // a = cos(theta)^2 - u.z * u.z
1448
  // b = r*u * cos(theta)^2 - u.z * r.z
173,418,868✔
1449
  // c = r*r * cos(theta)^2 - r.z^2
1450

85,533,350✔
1451
  const double cos_t = std::cos(grid_[1][shell]);
85,533,350✔
1452
  const bool sgn = std::signbit(cos_t);
171,066,700✔
1453
  const double cos_t_2 = cos_t * cos_t;
1454

87,885,518✔
1455
  const double a = cos_t_2 - u.z * u.z;
1456
  const double b = r.dot(u) * cos_t_2 - r.z * u.z;
42,137,942✔
1457
  const double c = r.dot(r) * cos_t_2 - r.z * r.z;
42,137,942✔
1458

42,137,942✔
1459
  // if factor of s^2 is zero, direction of flight is parallel to theta surface
84,275,884✔
1460
  if (std::abs(a) < FP_PRECISION) {
1461
    // if b vanishes, direction of flight is within theta surface and crossing
1462
    // is not possible
45,747,576✔
1463
    if (std::abs(b) < FP_PRECISION)
1464
      return INFTY;
1465

1466
    const double s = -0.5 * c / b;
481✔
1467
    // Check if solution is in positive direction of flight and has correct sign
1468
    if ((s > l) && (std::signbit(r.z + s * u.z) == sgn))
481✔
1469
      return s;
481✔
1470

481✔
1471
    // no crossing is possible
1472
    return INFTY;
1,924✔
1473
  }
1,443✔
UNCOV
1474

×
1475
  const double p = b / a;
UNCOV
1476
  double D = p * p - c / a;
×
1477

1478
  if (D < 0.0)
1,443✔
1479
    return INFTY;
2,886✔
UNCOV
1480

×
1481
  D = std::sqrt(D);
UNCOV
1482

×
1483
  // the solution -p-D is always smaller as -p+D : Check this one first
1484
  double s = -p - D;
1485
  // Check if solution is in positive direction of flight and has correct sign
481✔
UNCOV
1486
  if ((s > l) && (std::signbit(r.z + s * u.z) == sgn))
×
1487
    return s;
UNCOV
1488

×
1489
  s = -p + D;
1490
  // Check if solution is in positive direction of flight and has correct sign
481✔
UNCOV
1491
  if ((s > l) && (std::signbit(r.z + s * u.z) == sgn))
×
1492
    return s;
UNCOV
1493

×
1494
  return INFTY;
1495
}
481✔
UNCOV
1496

×
1497
double SphericalMesh::find_phi_crossing(
1498
  const Position& r, const Direction& u, double l, int shell) const
UNCOV
1499
{
×
1500
  // Phi grid is [0, 2Ï€], thus there is no real surface to cross
1501
  if (full_phi_ && (shape_[2] == 1))
1502
    return INFTY;
481✔
1503

1504
  shell = sanitize_phi(shell);
481✔
1505

481✔
1506
  const double p0 = grid_[2][shell];
1507

481✔
1508
  // solve y(s)/x(s) = tan(p0) = sin(p0)/cos(p0)
1509
  // => x(s) * cos(p0) = y(s) * sin(p0)
1510
  // => (y + s * v) * cos(p0) = (x + s * u) * sin(p0)
174,258,672✔
1511
  // = s * (v * cos(p0) - u * sin(p0)) = - (y * cos(p0) - x * sin(p0))
1512

174,258,672✔
1513
  const double c0 = std::cos(p0);
1514
  const double s0 = std::sin(p0);
1515

×
1516
  const double denominator = (u.x * s0 - u.y * c0);
1517

1518
  // Check if direction of flight is not parallel to phi surface
×
1519
  if (std::abs(denominator) > FP_PRECISION) {
1520
    const double s = -(r.x * s0 - r.y * c0) / denominator;
1521
    // Check if solution is in positive direction of flight and crosses the
1522
    // correct phi surface (not -phi)
1523
    if ((s > l) && ((c0 * (r.x + s * u.x) + s0 * (r.y + s * u.y)) > 0.0))
1524
      return s;
1525
  }
462✔
1526

1527
  return INFTY;
462✔
1528
}
1529

462✔
1530
StructuredMesh::MeshDistance SphericalMesh::distance_to_grid_boundary(
462✔
1531
  const MeshIndex& ijk, int i, const Position& r0, const Direction& u,
462✔
1532
  double l) const
462✔
1533
{
462✔
1534

1535
  if (i == 0) {
462✔
1536
    return std::min(
462✔
1537
      MeshDistance(ijk[i] + 1, true, find_r_crossing(r0, u, l, ijk[i])),
UNCOV
1538
      MeshDistance(ijk[i] - 1, false, find_r_crossing(r0, u, l, ijk[i] - 1)));
×
1539

UNCOV
1540
  } else if (i == 1) {
×
UNCOV
1541
    return std::min(MeshDistance(sanitize_theta(ijk[i] + 1), true,
×
1542
                      find_theta_crossing(r0, u, l, ijk[i])),
UNCOV
1543
      MeshDistance(sanitize_theta(ijk[i] - 1), false,
×
UNCOV
1544
        find_theta_crossing(r0, u, l, ijk[i] - 1)));
×
1545

UNCOV
1546
  } else {
×
UNCOV
1547
    return std::min(MeshDistance(sanitize_phi(ijk[i] + 1), true,
×
1548
                      find_phi_crossing(r0, u, l, ijk[i])),
UNCOV
1549
      MeshDistance(sanitize_phi(ijk[i] - 1), false,
×
1550
        find_phi_crossing(r0, u, l, ijk[i] - 1)));
1551
  }
1552
}
1553

1554
int SphericalMesh::set_grid()
1555
{
1556
  shape_ = {static_cast<int>(grid_[0].size()) - 1,
369✔
1557
    static_cast<int>(grid_[1].size()) - 1,
369✔
1558
    static_cast<int>(grid_[2].size()) - 1};
1559

369✔
1560
  for (const auto& g : grid_) {
1561
    if (g.size() < 2) {
369✔
1562
      set_errmsg("x-, y-, and z- grids for spherical meshes "
369✔
1563
                 "must each have at least 2 points");
369✔
1564
      return OPENMC_E_INVALID_ARGUMENT;
369✔
1565
    }
1566
    if (std::adjacent_find(g.begin(), g.end(), std::greater_equal<>()) !=
369✔
1567
        g.end()) {
×
1568
      set_errmsg("Values in for r-, theta-, and phi- grids for "
1569
                 "spherical meshes must be sorted and unique.");
369✔
1570
      return OPENMC_E_INVALID_ARGUMENT;
1571
    }
1572
    if (g.front() < 0.0) {
1573
      set_errmsg("r-, theta-, and phi- grids for "
56✔
1574
                 "spherical meshes must start at v >= 0.");
1575
      return OPENMC_E_INVALID_ARGUMENT;
56✔
1576
    }
1577
  }
1578
  if (grid_[1].back() > PI) {
85,467,718✔
1579
    set_errmsg("theta-grids for "
1580
               "spherical meshes must end with theta <= pi.");
1581

85,467,718✔
1582
    return OPENMC_E_INVALID_ARGUMENT;
1583
  }
85,467,718✔
1584
  if (grid_[2].back() > 2 * PI) {
85,467,718✔
1585
    set_errmsg("phi-grids for "
1586
               "spherical meshes must end with phi <= 2*pi.");
85,467,718✔
UNCOV
1587
    return OPENMC_E_INVALID_ARGUMENT;
×
UNCOV
1588
  }
×
1589

1590
  full_theta_ = (grid_[1].front() == 0.0) && (grid_[1].back() == PI);
85,467,718✔
1591
  full_phi_ = (grid_[2].front() == 0.0) && (grid_[2].back() == 2 * PI);
85,467,718✔
1592

85,467,718✔
1593
  lower_left_ = {grid_[0].front(), grid_[1].front(), grid_[2].front()};
42,698,768✔
1594
  upper_right_ = {grid_[0].back(), grid_[1].back(), grid_[2].back()};
1595

1596
  return 0;
85,467,718✔
1597
}
1598

85,467,718✔
1599
int SphericalMesh::get_index_in_direction(double r, int i) const
85,467,718✔
1600
{
1601
  return lower_bound_index(grid_[i].begin(), grid_[i].end(), r) + 1;
85,467,718✔
1602
}
1603

UNCOV
1604
std::pair<vector<double>, vector<double>> SphericalMesh::plot(
×
1605
  Position plot_ll, Position plot_ur) const
1606
{
UNCOV
1607
  fatal_error("Plot of spherical Mesh not implemented");
×
UNCOV
1608

×
1609
  // Figure out which axes lie in the plane of the plot.
UNCOV
1610
  array<vector<double>, 2> axis_lines;
×
UNCOV
1611
  return {axis_lines[0], axis_lines[1]};
×
1612
}
UNCOV
1613

×
1614
void SphericalMesh::to_hdf5(hid_t group) const
×
1615
{
UNCOV
1616
  hid_t mesh_group = create_group(group, "mesh " + std::to_string(id_));
×
UNCOV
1617

×
UNCOV
1618
  write_dataset(mesh_group, "type", SphericalMesh::mesh_type);
×
UNCOV
1619
  write_dataset(mesh_group, "r_grid", grid_[0]);
×
UNCOV
1620
  write_dataset(mesh_group, "theta_grid", grid_[1]);
×
1621
  write_dataset(mesh_group, "phi_grid", grid_[2]);
UNCOV
1622
  write_dataset(mesh_group, "origin", origin_);
×
1623

UNCOV
1624
  close_group(mesh_group);
×
UNCOV
1625
}
×
UNCOV
1626

×
1627
double SphericalMesh::volume(const MeshIndex& ijk) const
UNCOV
1628
{
×
1629
  double r_i = grid_[0][ijk[0] - 1];
1630
  double r_o = grid_[0][ijk[0]];
1631

560,399,056✔
1632
  double theta_i = grid_[1][ijk[1] - 1];
1633
  double theta_o = grid_[1][ijk[1]];
1634

560,399,056✔
1635
  double phi_i = grid_[2][ijk[2] - 1];
50,957,466✔
1636
  double phi_o = grid_[2][ijk[2]];
1637

1638
  return (1.0 / 3.0) * (r_o * r_o * r_o - r_i * r_i * r_i) *
1639
         (std::cos(theta_i) - std::cos(theta_o)) * (phi_o - phi_i);
509,441,590✔
1640
}
509,441,590✔
1641

8,840,832✔
1642
//==============================================================================
500,600,758✔
1643
// Helper functions for the C API
500,600,758✔
1644
//==============================================================================
500,600,758✔
1645

1646
int check_mesh(int32_t index)
500,600,758✔
1647
{
13,473,320✔
1648
  if (index < 0 || index >= model::meshes.size()) {
1649
    set_errmsg("Index in meshes array is out of bounds.");
487,127,438✔
1650
    return OPENMC_E_OUT_OF_BOUNDS;
452,853,744✔
1651
  }
1652
  return 0;
452,853,744✔
1653
}
80,868,858✔
1654

371,984,886✔
1655
template<class T>
224,102,774✔
1656
int check_mesh_type(int32_t index)
1657
{
1658
  if (int err = check_mesh(index))
182,155,806✔
1659
    return err;
1660

1661
  T* mesh = dynamic_cast<T*>(model::meshes[index].get());
136,692,136✔
1662
  if (!mesh) {
1663
    set_errmsg("This function is not valid for input mesh.");
1664
    return OPENMC_E_INVALID_TYPE;
1665
  }
136,692,136✔
1666
  return 0;
89,247,984✔
1667
}
1668

47,444,152✔
1669
template<class T>
1670
bool is_mesh_type(int32_t index)
1671
{
1672
  T* mesh = dynamic_cast<T*>(model::meshes[index].get());
1673
  return mesh;
1674
}
1675

1676
//==============================================================================
1677
// C API functions
47,444,152✔
1678
//==============================================================================
47,444,152✔
1679

47,444,152✔
1680
// Return the type of mesh as a C string
1681
extern "C" int openmc_mesh_get_type(int32_t index, char* type)
47,444,152✔
1682
{
47,444,152✔
1683
  if (int err = check_mesh(index))
47,444,152✔
1684
    return err;
1685

1686
  std::strcpy(type, model::meshes[index].get()->get_mesh_type().c_str());
47,444,152✔
1687

1688
  return 0;
1689
}
×
1690

×
1691
//! Extend the meshes array by n elements
1692
extern "C" int openmc_extend_meshes(
×
1693
  int32_t n, const char* type, int32_t* index_start, int32_t* index_end)
1694
{
×
1695
  if (index_start)
×
1696
    *index_start = model::meshes.size();
1697
  std::string mesh_type;
1698

×
1699
  for (int i = 0; i < n; ++i) {
1700
    if (RegularMesh::mesh_type == type) {
1701
      model::meshes.push_back(make_unique<RegularMesh>());
47,444,152✔
1702
    } else if (RectilinearMesh::mesh_type == type) {
47,444,152✔
1703
      model::meshes.push_back(make_unique<RectilinearMesh>());
1704
    } else if (CylindricalMesh::mesh_type == type) {
47,444,152✔
1705
      model::meshes.push_back(make_unique<CylindricalMesh>());
13,455,008✔
1706
    } else if (SphericalMesh::mesh_type == type) {
1707
      model::meshes.push_back(make_unique<SphericalMesh>());
33,989,144✔
1708
    } else {
1709
      throw std::runtime_error {"Unknown mesh type: " + std::string(type)};
1710
    }
33,989,144✔
1711
  }
1712
  if (index_end)
33,989,144✔
1713
    *index_end = model::meshes.size() - 1;
6,448,820✔
1714

1715
  return 0;
27,540,324✔
1716
}
1717

27,540,324✔
1718
//! Adds a new unstructured mesh to OpenMC
12,908,588✔
1719
extern "C" int openmc_add_unstructured_mesh(
1720
  const char filename[], const char library[], int* id)
14,631,736✔
1721
{
1722
  std::string lib_name(library);
1723
  std::string mesh_file(filename);
138,668,180✔
1724
  bool valid_lib = false;
1725

1726
#ifdef DAGMC
1727
  if (lib_name == MOABMesh::mesh_lib_type) {
138,668,180✔
1728
    model::meshes.push_back(std::move(make_unique<MOABMesh>(mesh_file)));
89,247,984✔
1729
    valid_lib = true;
1730
  }
49,420,196✔
1731
#endif
1732

49,420,196✔
1733
#ifdef LIBMESH
1734
  if (lib_name == LibMesh::mesh_lib_type) {
1735
    model::meshes.push_back(std::move(make_unique<LibMesh>(mesh_file)));
1736
    valid_lib = true;
1737
  }
1738
#endif
1739

49,420,196✔
1740
  if (!valid_lib) {
49,420,196✔
1741
    set_errmsg(fmt::format("Mesh library {} is not supported "
1742
                           "by this build of OpenMC",
49,420,196✔
1743
      lib_name));
1744
    return OPENMC_E_INVALID_ARGUMENT;
1745
  }
49,420,196✔
1746

49,420,196✔
1747
  // auto-assign new ID
1748
  model::meshes.back()->set_id(-1);
1749
  *id = model::meshes.back()->id_;
49,420,196✔
1750

21,825,664✔
1751
  return 0;
1752
}
1753

27,594,532✔
1754
//! Return the index in the meshes array of a mesh with a given ID
1755
extern "C" int openmc_get_mesh_index(int32_t id, int32_t* index)
1756
{
417,879,686✔
1757
  auto pair = model::mesh_map.find(id);
1758
  if (pair == model::mesh_map.end()) {
1759
    set_errmsg("No mesh exists with ID=" + std::to_string(id) + ".");
1760
    return OPENMC_E_INVALID_ID;
1761
  }
417,879,686✔
1762
  *index = pair->second;
280,199,528✔
1763
  return 0;
280,199,528✔
1764
}
560,399,056✔
1765

1766
//! Return the ID of a mesh
137,680,158✔
1767
extern "C" int openmc_mesh_get_id(int32_t index, int32_t* id)
68,346,068✔
1768
{
68,346,068✔
1769
  if (int err = check_mesh(index))
68,346,068✔
1770
    return err;
136,692,136✔
1771
  *id = model::meshes[index]->id_;
1772
  return 0;
1773
}
69,334,090✔
1774

69,334,090✔
1775
//! Set the ID of a mesh
69,334,090✔
1776
extern "C" int openmc_mesh_set_id(int32_t index, int32_t id)
138,668,180✔
1777
{
1778
  if (int err = check_mesh(index))
1779
    return err;
1780
  model::meshes[index]->id_ = id;
383✔
1781
  model::mesh_map[id] = index;
1782
  return 0;
383✔
1783
}
383✔
1784

383✔
1785
//! Get the dimension of a regular mesh
1786
extern "C" int openmc_regular_mesh_get_dimension(
1,532✔
1787
  int32_t index, int** dims, int* n)
1,149✔
1788
{
×
1789
  if (int err = check_mesh_type<RegularMesh>(index))
1790
    return err;
×
1791
  RegularMesh* mesh = dynamic_cast<RegularMesh*>(model::meshes[index].get());
1792
  *dims = mesh->shape_.data();
1,149✔
1793
  *n = mesh->n_dimension_;
2,298✔
1794
  return 0;
×
1795
}
1796

×
1797
//! Set the dimension of a regular mesh
1798
extern "C" int openmc_regular_mesh_set_dimension(
1,149✔
1799
  int32_t index, int n, const int* dims)
×
1800
{
1801
  if (int err = check_mesh_type<RegularMesh>(index))
×
1802
    return err;
1803
  RegularMesh* mesh = dynamic_cast<RegularMesh*>(model::meshes[index].get());
1804

383✔
1805
  // Copy dimension
×
1806
  mesh->n_dimension_ = n;
1807
  std::copy(dims, dims + n, mesh->shape_.begin());
1808
  return 0;
×
1809
}
1810

383✔
1811
//! Get the regular mesh parameters
×
1812
extern "C" int openmc_regular_mesh_get_params(
1813
  int32_t index, double** ll, double** ur, double** width, int* n)
×
1814
{
1815
  if (int err = check_mesh_type<RegularMesh>(index))
1816
    return err;
383✔
1817
  RegularMesh* m = dynamic_cast<RegularMesh*>(model::meshes[index].get());
383✔
1818

1819
  if (m->lower_left_.dimension() == 0) {
383✔
1820
    set_errmsg("Mesh parameters have not been set.");
383✔
1821
    return OPENMC_E_ALLOCATE;
1822
  }
383✔
1823

1824
  *ll = m->lower_left_.data();
1825
  *ur = m->upper_right_.data();
256,403,154✔
1826
  *width = m->width_.data();
1827
  *n = m->n_dimension_;
256,403,154✔
1828
  return 0;
1829
}
1830

×
1831
//! Set the regular mesh parameters
1832
extern "C" int openmc_regular_mesh_set_params(
1833
  int32_t index, int n, const double* ll, const double* ur, const double* width)
×
1834
{
1835
  if (int err = check_mesh_type<RegularMesh>(index))
1836
    return err;
1837
  RegularMesh* m = dynamic_cast<RegularMesh*>(model::meshes[index].get());
1838

1839
  vector<std::size_t> shape = {static_cast<std::size_t>(n)};
1840
  if (ll && ur) {
364✔
1841
    m->lower_left_ = xt::adapt(ll, n, xt::no_ownership(), shape);
1842
    m->upper_right_ = xt::adapt(ur, n, xt::no_ownership(), shape);
364✔
1843
    m->width_ = (m->upper_right_ - m->lower_left_) / m->get_x_shape();
1844
  } else if (ll && width) {
364✔
1845
    m->lower_left_ = xt::adapt(ll, n, xt::no_ownership(), shape);
364✔
1846
    m->width_ = xt::adapt(width, n, xt::no_ownership(), shape);
364✔
1847
    m->upper_right_ = m->lower_left_ + m->get_x_shape() * m->width_;
364✔
1848
  } else if (ur && width) {
364✔
1849
    m->upper_right_ = xt::adapt(ur, n, xt::no_ownership(), shape);
1850
    m->width_ = xt::adapt(width, n, xt::no_ownership(), shape);
364✔
1851
    m->lower_left_ = m->upper_right_ - m->get_x_shape() * m->width_;
364✔
1852
  } else {
1853
    set_errmsg("At least two parameters must be specified.");
×
1854
    return OPENMC_E_INVALID_ARGUMENT;
1855
  }
×
1856

×
1857
  return 0;
1858
}
×
1859

×
1860
//! Set the mesh parameters for rectilinear, cylindrical and spharical meshes
1861
template<class C>
×
1862
int openmc_structured_mesh_set_grid_impl(int32_t index, const double* grid_x,
×
1863
  const int nx, const double* grid_y, const int ny, const double* grid_z,
1864
  const int nz)
×
1865
{
×
1866
  if (int err = check_mesh_type<C>(index))
1867
    return err;
1868

1869
  C* m = dynamic_cast<C*>(model::meshes[index].get());
1870

1871
  m->n_dimension_ = 3;
1872

8,096✔
1873
  m->grid_[0].reserve(nx);
1874
  m->grid_[1].reserve(ny);
8,096✔
1875
  m->grid_[2].reserve(nz);
×
1876

×
1877
  for (int i = 0; i < nx; i++) {
1878
    m->grid_[0].push_back(grid_x[i]);
8,096✔
1879
  }
1880
  for (int i = 0; i < ny; i++) {
1881
    m->grid_[1].push_back(grid_y[i]);
1882
  }
1,776✔
1883
  for (int i = 0; i < nz; i++) {
1884
    m->grid_[2].push_back(grid_z[i]);
1,776✔
1885
  }
×
1886

1887
  int err = m->set_grid();
1,776✔
1888
  return err;
1,776✔
1889
}
×
1890

×
1891
//! Get the mesh parameters for rectilinear, cylindrical and spherical meshes
1892
template<class C>
1,776✔
1893
int openmc_structured_mesh_get_grid_impl(int32_t index, double** grid_x,
1894
  int* nx, double** grid_y, int* ny, double** grid_z, int* nz)
168✔
1895
{
1896
  if (int err = check_mesh_type<C>(index))
168✔
1897
    return err;
×
1898
  C* m = dynamic_cast<C*>(model::meshes[index].get());
1899

168✔
1900
  if (m->lower_left_.dimension() == 0) {
168✔
1901
    set_errmsg("Mesh parameters have not been set.");
×
1902
    return OPENMC_E_ALLOCATE;
×
1903
  }
1904

168✔
1905
  *grid_x = m->grid_[0].data();
1906
  *nx = m->grid_[0].size();
168✔
1907
  *grid_y = m->grid_[1].data();
1908
  *ny = m->grid_[1].size();
168✔
1909
  *grid_z = m->grid_[2].data();
×
1910
  *nz = m->grid_[2].size();
1911

168✔
1912
  return 0;
168✔
1913
}
×
1914

×
1915
//! Get the rectilinear mesh grid
1916
extern "C" int openmc_rectilinear_mesh_get_grid(int32_t index, double** grid_x,
168✔
1917
  int* nx, double** grid_y, int* ny, double** grid_z, int* nz)
1918
{
272✔
1919
  return openmc_structured_mesh_get_grid_impl<RectilinearMesh>(
1920
    index, grid_x, nx, grid_y, ny, grid_z, nz);
272✔
1921
}
×
1922

1923
//! Set the rectilienar mesh parameters
272✔
1924
extern "C" int openmc_rectilinear_mesh_set_grid(int32_t index,
272✔
1925
  const double* grid_x, const int nx, const double* grid_y, const int ny,
×
1926
  const double* grid_z, const int nz)
×
1927
{
1928
  return openmc_structured_mesh_set_grid_impl<RectilinearMesh>(
272✔
1929
    index, grid_x, nx, grid_y, ny, grid_z, nz);
1930
}
1,168✔
1931

1932
//! Get the cylindrical mesh grid
1,168✔
1933
extern "C" int openmc_cylindrical_mesh_get_grid(int32_t index, double** grid_x,
×
1934
  int* nx, double** grid_y, int* ny, double** grid_z, int* nz)
1935
{
1,168✔
1936
  return openmc_structured_mesh_get_grid_impl<CylindricalMesh>(
1,168✔
1937
    index, grid_x, nx, grid_y, ny, grid_z, nz);
×
1938
}
×
1939

1940
//! Set the cylindrical mesh parameters
1,168✔
1941
extern "C" int openmc_cylindrical_mesh_set_grid(int32_t index,
1942
  const double* grid_x, const int nx, const double* grid_y, const int ny,
1943
  const double* grid_z, const int nz)
1944
{
1945
  return openmc_structured_mesh_set_grid_impl<CylindricalMesh>(
1946
    index, grid_x, nx, grid_y, ny, grid_z, nz);
1947
}
1948

1949
//! Get the spherical mesh grid
1950
extern "C" int openmc_spherical_mesh_get_grid(int32_t index, double** grid_x,
1951
  int* nx, double** grid_y, int* ny, double** grid_z, int* nz)
1952
{
1953

1954
  return openmc_structured_mesh_get_grid_impl<SphericalMesh>(
1955
    index, grid_x, nx, grid_y, ny, grid_z, nz);
1,912✔
1956
  ;
1957
}
1,912✔
1958

×
1959
//! Set the spherical mesh parameters
1960
extern "C" int openmc_spherical_mesh_set_grid(int32_t index,
1,912✔
1961
  const double* grid_x, const int nx, const double* grid_y, const int ny,
1962
  const double* grid_z, const int nz)
1,912✔
1963
{
1964
  return openmc_structured_mesh_set_grid_impl<SphericalMesh>(
1965
    index, grid_x, nx, grid_y, ny, grid_z, nz);
1966
}
446✔
1967

1968
#ifdef DAGMC
1969

446✔
1970
const std::string MOABMesh::mesh_lib_type = "moab";
446✔
1971

446✔
1972
MOABMesh::MOABMesh(pugi::xml_node node) : UnstructuredMesh(node)
1973
{
892✔
1974
  initialize();
446✔
1975
}
352✔
1976

94✔
1977
MOABMesh::MOABMesh(const std::string& filename, double length_multiplier)
66✔
1978
{
28✔
1979
  filename_ = filename;
14✔
1980
  set_length_multiplier(length_multiplier);
14✔
1981
  initialize();
14✔
1982
}
1983

×
1984
MOABMesh::MOABMesh(std::shared_ptr<moab::Interface> external_mbi)
1985
{
1986
  mbi_ = external_mbi;
446✔
1987
  filename_ = "unknown (external file)";
×
1988
  this->initialize();
1989
}
446✔
1990

446✔
1991
void MOABMesh::initialize()
1992
{
1993

×
1994
  // Create the MOAB interface and load data from file
1995
  this->create_interface();
1996

×
1997
  // Initialise MOAB error code
×
1998
  moab::ErrorCode rval = moab::MB_SUCCESS;
×
1999

2000
  // Set the dimension
2001
  n_dimension_ = 3;
2002

2003
  // set member range of tetrahedral entities
2004
  rval = mbi_->get_entities_by_dimension(0, n_dimension_, ehs_);
2005
  if (rval != moab::MB_SUCCESS) {
2006
    fatal_error("Failed to get all tetrahedral elements");
2007
  }
2008

2009
  if (!ehs_.all_of_type(moab::MBTET)) {
2010
    warning("Non-tetrahedral elements found in unstructured "
2011
            "mesh file: " +
2012
            filename_);
2013
  }
2014

×
2015
  // set member range of vertices
×
2016
  int vertex_dim = 0;
2017
  rval = mbi_->get_entities_by_dimension(0, vertex_dim, verts_);
2018
  if (rval != moab::MB_SUCCESS) {
×
2019
    fatal_error("Failed to get all vertex handles");
2020
  }
2021

2022
  // make an entity set for all tetrahedra
×
2023
  // this is used for convenience later in output
×
2024
  rval = mbi_->create_meshset(moab::MESHSET_SET, tetset_);
2025
  if (rval != moab::MB_SUCCESS) {
×
2026
    fatal_error("Failed to create an entity set for the tetrahedral elements");
2027
  }
2028

2029
  rval = mbi_->add_entities(tetset_, ehs_);
712✔
2030
  if (rval != moab::MB_SUCCESS) {
2031
    fatal_error("Failed to add tetrahedra to an entity set.");
712✔
2032
  }
712✔
2033

×
2034
  if (specified_length_multiplier_) {
×
2035
    // get the connectivity of all tets
2036
    moab::Range adj;
712✔
2037
    rval = mbi_->get_adjacencies(ehs_, 0, true, adj, moab::Interface::UNION);
712✔
2038
    if (rval != moab::MB_SUCCESS) {
2039
      fatal_error("Failed to get adjacent vertices of tetrahedra.");
2040
    }
2041
    // scale all vertex coords by multiplier (done individually so not all
3,962✔
2042
    // coordinates are in memory twice at once)
2043
    for (auto vert : adj) {
3,962✔
2044
      // retrieve coords
×
2045
      std::array<double, 3> coord;
3,962✔
2046
      rval = mbi_->get_coords(&vert, 1, coord.data());
3,962✔
2047
      if (rval != moab::MB_SUCCESS) {
2048
        fatal_error("Could not get coordinates of vertex.");
2049
      }
2050
      // scale coords
446✔
2051
      for (auto& c : coord) {
2052
        c *= length_multiplier_;
446✔
2053
      }
×
2054
      // set new coords
446✔
2055
      rval = mbi_->set_coords(&vert, 1, coord.data());
446✔
2056
      if (rval != moab::MB_SUCCESS) {
446✔
2057
        fatal_error("Failed to set new vertex coordinates");
2058
      }
2059
    }
2060
  }
14✔
2061

2062
  // build acceleration data structures
2063
  compute_barycentric_data(ehs_);
14✔
2064
  build_kdtree(ehs_);
×
2065
}
14✔
2066

14✔
2067
void MOABMesh::create_interface()
14✔
2068
{
14✔
2069
  // Do not create a MOAB instance if one is already in memory
2070
  if (mbi_)
2071
    return;
2072

352✔
2073
  // create MOAB instance
2074
  mbi_ = std::make_shared<moab::Core>();
2075

352✔
2076
  // load unstructured mesh file
×
2077
  moab::ErrorCode rval = mbi_->load_file(filename_.c_str());
352✔
2078
  if (rval != moab::MB_SUCCESS) {
2079
    fatal_error("Failed to load the unstructured mesh file: " + filename_);
2080
  }
352✔
2081
}
352✔
2082

352✔
2083
void MOABMesh::build_kdtree(const moab::Range& all_tets)
2084
{
2085
  moab::Range all_tris;
2086
  int adj_dim = 2;
422✔
2087
  moab::ErrorCode rval = mbi_->get_adjacencies(
2088
    all_tets, adj_dim, true, all_tris, moab::Interface::UNION);
2089
  if (rval != moab::MB_SUCCESS) {
422✔
2090
    fatal_error("Failed to get adjacent triangles for tets");
×
2091
  }
422✔
2092

2093
  if (!all_tris.all_of_type(moab::MBTRI)) {
422✔
2094
    warning("Non-triangle elements found in tet adjacencies in "
×
2095
            "unstructured mesh file: " +
×
2096
            filename_);
2097
  }
2098

422✔
2099
  // combine into one range
422✔
2100
  moab::Range all_tets_and_tris;
422✔
2101
  all_tets_and_tris.merge(all_tets);
422✔
2102
  all_tets_and_tris.merge(all_tris);
422✔
2103

2104
  // create a kd-tree instance
2105
  kdtree_ = make_unique<moab::AdaptiveKDTree>(mbi_.get());
2106

380✔
2107
  // build the tree
2108
  rval = kdtree_->build_tree(all_tets_and_tris, &kdtree_root_);
2109
  if (rval != moab::MB_SUCCESS) {
380✔
2110
    fatal_error("Failed to construct KDTree for the "
×
2111
                "unstructured mesh file: " +
380✔
2112
                filename_);
2113
  }
380✔
2114
}
380✔
2115

352✔
2116
void MOABMesh::intersect_track(const moab::CartVect& start,
352✔
2117
  const moab::CartVect& dir, double track_len, vector<double>& hits) const
352✔
2118
{
28✔
2119
  hits.clear();
14✔
2120

14✔
2121
  moab::ErrorCode rval;
14✔
2122
  vector<moab::EntityHandle> tris;
14✔
2123
  // get all intersections with triangles in the tet mesh
14✔
2124
  // (distances are relative to the start point, not the previous intersection)
14✔
2125
  rval = kdtree_->ray_intersect_triangles(kdtree_root_, FP_COINCIDENT,
14✔
2126
    dir.array(), start.array(), tris, hits, 0, track_len);
2127
  if (rval != moab::MB_SUCCESS) {
×
2128
    fatal_error(
×
2129
      "Failed to compute intersections on unstructured mesh: " + filename_);
2130
  }
2131

380✔
2132
  // remove duplicate intersection distances
380✔
2133
  std::unique(hits.begin(), hits.end());
2134

2135
  // sorts by first component of std::pair by default
2136
  std::sort(hits.begin(), hits.end());
94✔
2137
}
2138

2139
void MOABMesh::bins_crossed(Position r0, Position r1, const Direction& u,
2140
  vector<int>& bins, vector<double>& lengths) const
94✔
2141
{
×
2142
  moab::CartVect start(r0.x, r0.y, r0.z);
2143
  moab::CartVect end(r1.x, r1.y, r1.z);
94✔
2144
  moab::CartVect dir(u.x, u.y, u.z);
2145
  dir.normalize();
94✔
2146

2147
  double track_len = (end - start).length();
94✔
2148
  if (track_len == 0.0)
94✔
2149
    return;
94✔
2150

2151
  start -= TINY_BIT * dir;
896✔
2152
  end += TINY_BIT * dir;
802✔
2153

2154
  vector<double> hits;
324✔
2155
  intersect_track(start, dir, track_len, hits);
230✔
2156

2157
  bins.clear();
324✔
2158
  lengths.clear();
230✔
2159

2160
  // if there are no intersections the track may lie entirely
2161
  // within a single tet. If this is the case, apply entire
94✔
2162
  // score to that tet and return.
94✔
2163
  if (hits.size() == 0) {
2164
    Position midpoint = r0 + u * (track_len * 0.5);
14✔
2165
    int bin = this->get_bin(midpoint);
2166
    if (bin != -1) {
2167
      bins.push_back(bin);
2168
      lengths.push_back(1.0);
14✔
2169
    }
×
2170
    return;
2171
  }
14✔
2172

2173
  // for each segment in the set of tracks, try to look up a tet
14✔
2174
  // at the midpoint of the segment
2175
  Position current = r0;
14✔
2176
  double last_dist = 0.0;
14✔
2177
  for (const auto& hit : hits) {
14✔
2178
    // get the segment length
2179
    double segment_length = hit - last_dist;
56✔
2180
    last_dist = hit;
42✔
2181
    // find the midpoint of this segment
2182
    Position midpoint = current + u * (segment_length * 0.5);
56✔
2183
    // try to find a tet for this position
42✔
2184
    int bin = this->get_bin(midpoint);
2185

56✔
2186
    // determine the start point for this segment
42✔
2187
    current = r0 + u * hit;
2188

2189
    if (bin == -1) {
14✔
2190
      continue;
14✔
2191
    }
2192

14✔
2193
    bins.push_back(bin);
2194
    lengths.push_back(segment_length / track_len);
2195
  }
2196

14✔
2197
  // tally remaining portion of track after last hit if
×
2198
  // the last segment of the track is in the mesh but doesn't
2199
  // reach the other side of the tet
14✔
2200
  if (hits.back() < track_len) {
2201
    Position segment_start = r0 + u * hits.back();
14✔
2202
    double segment_length = track_len - hits.back();
2203
    Position midpoint = segment_start + u * (segment_length * 0.5);
14✔
2204
    int bin = this->get_bin(midpoint);
14✔
2205
    if (bin != -1) {
14✔
2206
      bins.push_back(bin);
2207
      lengths.push_back(segment_length / track_len);
56✔
2208
    }
42✔
2209
  }
2210
};
56✔
2211

42✔
2212
moab::EntityHandle MOABMesh::get_tet(const Position& r) const
2213
{
56✔
2214
  moab::CartVect pos(r.x, r.y, r.z);
42✔
2215
  // find the leaf of the kd-tree for this position
2216
  moab::AdaptiveKDTreeIter kdtree_iter;
2217
  moab::ErrorCode rval = kdtree_->point_search(pos.array(), kdtree_iter);
14✔
2218
  if (rval != moab::MB_SUCCESS) {
14✔
2219
    return 0;
2220
  }
66✔
2221

2222
  // retrieve the tet elements of this leaf
2223
  moab::EntityHandle leaf = kdtree_iter.handle();
2224
  moab::Range tets;
66✔
2225
  rval = mbi_->get_entities_by_dimension(leaf, 3, tets, false);
×
2226
  if (rval != moab::MB_SUCCESS) {
2227
    warning("MOAB error finding tets.");
66✔
2228
  }
2229

66✔
2230
  // loop over the tets in this leaf, returning the containing tet if found
2231
  for (const auto& tet : tets) {
66✔
2232
    if (point_in_tet(pos, tet)) {
66✔
2233
      return tet;
66✔
2234
    }
2235
  }
784✔
2236

718✔
2237
  // if no tet is found, return an invalid handle
2238
  return 0;
212✔
2239
}
146✔
2240

2241
double MOABMesh::volume(int bin) const
212✔
2242
{
146✔
2243
  return tet_volume(get_ent_handle_from_bin(bin));
2244
}
2245

66✔
2246
std::string MOABMesh::library() const
66✔
2247
{
2248
  return mesh_lib_type;
2249
}
2250

2251
// Sample position within a tet for MOAB type tets
514✔
2252
Position MOABMesh::sample_element(int32_t bin, uint64_t* seed) const
2253
{
2254

514✔
2255
  moab::EntityHandle tet_ent = get_ent_handle_from_bin(bin);
×
2256

514✔
2257
  // Get vertex coordinates for MOAB tet
2258
  const moab::EntityHandle* conn1;
514✔
2259
  int conn1_size;
×
2260
  moab::ErrorCode rval = mbi_->get_connectivity(tet_ent, conn1, conn1_size);
×
2261
  if (rval != moab::MB_SUCCESS || conn1_size != 4) {
2262
    fatal_error(fmt::format(
2263
      "Failed to get tet connectivity or connectivity size ({}) is invalid.",
514✔
2264
      conn1_size));
514✔
2265
  }
514✔
2266
  moab::CartVect p[4];
514✔
2267
  rval = mbi_->get_coords(conn1, conn1_size, p[0].array());
514✔
2268
  if (rval != moab::MB_SUCCESS) {
514✔
2269
    fatal_error("Failed to get tet coords");
2270
  }
514✔
2271

2272
  std::array<Position, 4> tet_verts;
154✔
2273
  for (int i = 0; i < 4; i++) {
2274
    tet_verts[i] = {p[i][0], p[i][1], p[i][2]};
2275
  }
154✔
2276
  // Samples position within tet using Barycentric stuff
×
2277
  return this->sample_tet(tet_verts, seed);
154✔
2278
}
2279

154✔
2280
double MOABMesh::tet_volume(moab::EntityHandle tet) const
×
2281
{
×
2282
  vector<moab::EntityHandle> conn;
2283
  moab::ErrorCode rval = mbi_->get_connectivity(&tet, 1, conn);
2284
  if (rval != moab::MB_SUCCESS) {
154✔
2285
    fatal_error("Failed to get tet connectivity");
154✔
2286
  }
154✔
2287

154✔
2288
  moab::CartVect p[4];
154✔
2289
  rval = mbi_->get_coords(conn.data(), conn.size(), p[0].array());
154✔
2290
  if (rval != moab::MB_SUCCESS) {
2291
    fatal_error("Failed to get tet coords");
154✔
2292
  }
2293

154✔
2294
  return 1.0 / 6.0 * (((p[1] - p[0]) * (p[2] - p[0])) % (p[3] - p[0]));
2295
}
2296

154✔
2297
int MOABMesh::get_bin(Position r) const
×
2298
{
154✔
2299
  moab::EntityHandle tet = get_tet(r);
2300
  if (tet == 0) {
154✔
2301
    return -1;
×
2302
  } else {
×
2303
    return get_bin_from_ent_handle(tet);
2304
  }
2305
}
154✔
2306

154✔
2307
void MOABMesh::compute_barycentric_data(const moab::Range& tets)
154✔
2308
{
154✔
2309
  moab::ErrorCode rval;
154✔
2310

154✔
2311
  baryc_data_.clear();
2312
  baryc_data_.resize(tets.size());
154✔
2313

2314
  // compute the barycentric data for each tet element
206✔
2315
  // and store it as a 3x3 matrix
2316
  for (auto& tet : tets) {
2317
    vector<moab::EntityHandle> verts;
206✔
2318
    rval = mbi_->get_connectivity(&tet, 1, verts);
×
2319
    if (rval != moab::MB_SUCCESS) {
206✔
2320
      fatal_error("Failed to get connectivity of tet on umesh: " + filename_);
2321
    }
206✔
2322

×
2323
    moab::CartVect p[4];
×
2324
    rval = mbi_->get_coords(verts.data(), verts.size(), p[0].array());
2325
    if (rval != moab::MB_SUCCESS) {
2326
      fatal_error("Failed to get coordinates of a tet in umesh: " + filename_);
206✔
2327
    }
206✔
2328

206✔
2329
    moab::Matrix3 a(p[1] - p[0], p[2] - p[0], p[3] - p[0], true);
206✔
2330

206✔
2331
    // invert now to avoid this cost later
206✔
2332
    a = a.transpose().inverse();
2333
    baryc_data_.at(get_bin_from_ent_handle(tet)) = a;
206✔
2334
  }
2335
}
2336

2337
bool MOABMesh::point_in_tet(
206✔
2338
  const moab::CartVect& r, moab::EntityHandle tet) const
2339
{
2340

206✔
2341
  moab::ErrorCode rval;
206✔
2342

2343
  // get tet vertices
2344
  vector<moab::EntityHandle> verts;
2345
  rval = mbi_->get_connectivity(&tet, 1, verts);
66✔
2346
  if (rval != moab::MB_SUCCESS) {
2347
    warning("Failed to get vertices of tet in umesh: " + filename_);
2348
    return false;
2349
  }
66✔
2350

66✔
2351
  // first vertex is used as a reference point for the barycentric data -
2352
  // retrieve its coordinates
2353
  moab::CartVect p_zero;
2354
  rval = mbi_->get_coords(verts.data(), 1, p_zero.array());
154✔
2355
  if (rval != moab::MB_SUCCESS) {
2356
    warning("Failed to get coordinates of a vertex in "
2357
            "unstructured mesh: " +
154✔
2358
            filename_);
154✔
2359
    return false;
2360
  }
2361

2362
  // look up barycentric data
14✔
2363
  int idx = get_bin_from_ent_handle(tet);
2364
  const moab::Matrix3& a_inv = baryc_data_[idx];
2365

2366
  moab::CartVect bary_coords = a_inv * (r - p_zero);
14✔
2367

14✔
2368
  return (bary_coords[0] >= 0.0 && bary_coords[1] >= 0.0 &&
2369
          bary_coords[2] >= 0.0 &&
2370
          bary_coords[0] + bary_coords[1] + bary_coords[2] <= 1.0);
2371
}
154✔
2372

2373
int MOABMesh::get_bin_from_index(int idx) const
2374
{
2375
  if (idx >= n_bins()) {
154✔
2376
    fatal_error(fmt::format("Invalid bin index: {}", idx));
154✔
2377
  }
2378
  return ehs_[idx] - ehs_[0];
2379
}
2380

2381
int MOABMesh::get_index(const Position& r, bool* in_mesh) const
14✔
2382
{
2383
  int bin = get_bin(r);
2384
  *in_mesh = bin != -1;
2385
  return bin;
14✔
2386
}
14✔
2387

2388
int MOABMesh::get_index_from_bin(int bin) const
2389
{
2390
  return bin;
2391
}
2392

2393
std::pair<vector<double>, vector<double>> MOABMesh::plot(
20✔
2394
  Position plot_ll, Position plot_ur) const
2395
{
20✔
2396
  // TODO: Implement mesh lines
20✔
2397
  return {};
2398
}
2399

2400
int MOABMesh::get_vert_idx_from_handle(moab::EntityHandle vert) const
2401
{
2402
  int idx = vert - verts_[0];
2403
  if (idx >= n_vertices()) {
2404
    fatal_error(
2405
      fmt::format("Invalid vertex idx {} (# vertices {})", idx, n_vertices()));
1✔
2406
  }
2407
  return idx;
1✔
2408
}
1✔
2409

1✔
2410
int MOABMesh::get_bin_from_ent_handle(moab::EntityHandle eh) const
1✔
2411
{
2412
  int bin = eh - ehs_[0];
21✔
2413
  if (bin >= n_bins()) {
2414
    fatal_error(fmt::format("Invalid bin: {}", bin));
2415
  }
2416
  return bin;
21✔
2417
}
2418

2419
moab::EntityHandle MOABMesh::get_ent_handle_from_bin(int bin) const
21✔
2420
{
2421
  if (bin >= n_bins()) {
2422
    fatal_error(fmt::format("Invalid bin index: ", bin));
21✔
2423
  }
2424
  return ehs_[0] + bin;
2425
}
21✔
2426

21✔
2427
int MOABMesh::n_bins() const
2428
{
2429
  return ehs_.size();
2430
}
21✔
2431

2432
int MOABMesh::n_surface_bins() const
2433
{
2434
  // collect all triangles in the set of tets for this mesh
2435
  moab::Range tris;
2436
  moab::ErrorCode rval;
2437
  rval = mbi_->get_entities_by_type(0, moab::MBTRI, tris);
21✔
2438
  if (rval != moab::MB_SUCCESS) {
21✔
2439
    warning("Failed to get all triangles in the mesh instance");
21✔
2440
    return -1;
2441
  }
2442
  return 2 * tris.size();
2443
}
2444

2445
Position MOABMesh::centroid(int bin) const
21✔
2446
{
21✔
2447
  moab::ErrorCode rval;
2448

2449
  auto tet = this->get_ent_handle_from_bin(bin);
2450

21✔
2451
  // look up the tet connectivity
21✔
2452
  vector<moab::EntityHandle> conn;
2453
  rval = mbi_->get_connectivity(&tet, 1, conn);
2454
  if (rval != moab::MB_SUCCESS) {
2455
    warning("Failed to get connectivity of a mesh element.");
21✔
2456
    return {};
2457
  }
2458

2459
  // get the coordinates
2460
  vector<moab::CartVect> coords(conn.size());
2461
  rval = mbi_->get_coords(conn.data(), conn.size(), coords[0].array());
2462
  if (rval != moab::MB_SUCCESS) {
2463
    warning("Failed to get the coordinates of a mesh element.");
2464
    return {};
2465
  }
2466

2467
  // compute the centroid of the element vertices
2468
  moab::CartVect centroid(0.0, 0.0, 0.0);
2469
  for (const auto& coord : coords) {
2470
    centroid += coord;
2471
  }
2472
  centroid /= double(coords.size());
2473

2474
  return {centroid[0], centroid[1], centroid[2]};
2475
}
2476

2477
int MOABMesh::n_vertices() const
2478
{
2479
  return verts_.size();
2480
}
2481

2482
Position MOABMesh::vertex(int id) const
2483
{
2484

21✔
2485
  moab::ErrorCode rval;
21✔
2486

21✔
2487
  moab::EntityHandle vert = verts_[id];
2488

21✔
2489
  moab::CartVect coords;
2490
  rval = mbi_->get_coords(&vert, 1, coords.array());
2491
  if (rval != moab::MB_SUCCESS) {
21✔
2492
    fatal_error("Failed to get the coordinates of a vertex.");
1✔
2493
  }
2494

2495
  return {coords[0], coords[1], coords[2]};
20✔
2496
}
2497

2498
std::vector<int> MOABMesh::connectivity(int bin) const
20✔
2499
{
20✔
2500
  moab::ErrorCode rval;
2501

2502
  auto tet = get_ent_handle_from_bin(bin);
2503

2504
  // look up the tet connectivity
21✔
2505
  vector<moab::EntityHandle> conn;
2506
  rval = mbi_->get_connectivity(&tet, 1, conn);
21✔
2507
  if (rval != moab::MB_SUCCESS) {
21✔
2508
    fatal_error("Failed to get connectivity of a mesh element.");
21✔
2509
    return {};
2510
  }
21✔
2511

2512
  std::vector<int> verts(4);
2513
  for (int i = 0; i < verts.size(); i++) {
2514
    verts[i] = get_vert_idx_from_handle(conn[i]);
21✔
2515
  }
2516

2517
  return verts;
2518
}
2519

2520
std::pair<moab::Tag, moab::Tag> MOABMesh::get_score_tags(
2521
  std::string score) const
21✔
2522
{
21✔
2523
  moab::ErrorCode rval;
21✔
2524
  // add a tag to the mesh
2525
  // all scores are treated as a single value
2526
  // with an uncertainty
21✔
2527
  moab::Tag value_tag;
2528

2529
  // create the value tag if not present and get handle
21✔
2530
  double default_val = 0.0;
21✔
2531
  auto val_string = score + "_mean";
2532
  rval = mbi_->tag_get_handle(val_string.c_str(), 1, moab::MB_TYPE_DOUBLE,
2533
    value_tag, moab::MB_TAG_DENSE | moab::MB_TAG_CREAT, &default_val);
2534
  if (rval != moab::MB_SUCCESS) {
2535
    auto msg =
21✔
2536
      fmt::format("Could not create or retrieve the value tag for the score {}"
2537
                  " on unstructured mesh {}",
1,519,602✔
2538
        score, id_);
2539
    fatal_error(msg);
2540
  }
1,519,602✔
2541

2542
  // create the std dev tag if not present and get handle
2543
  moab::Tag error_tag;
1,519,602✔
2544
  std::string err_string = score + "_std_dev";
2545
  rval = mbi_->tag_get_handle(err_string.c_str(), 1, moab::MB_TYPE_DOUBLE,
2546
    error_tag, moab::MB_TAG_DENSE | moab::MB_TAG_CREAT, &default_val);
1,519,602✔
2547
  if (rval != moab::MB_SUCCESS) {
2548
    auto msg =
1,519,602✔
2549
      fmt::format("Could not create or retrieve the error tag for the score {}"
2550
                  " on unstructured mesh {}",
2551
        score, id_);
2552
    fatal_error(msg);
2553
  }
2554

1,519,602✔
2555
  // return the populated tag handles
2556
  return {value_tag, error_tag};
2557
}
1,519,602✔
2558

1,519,602✔
2559
void MOABMesh::add_score(const std::string& score)
2560
{
1,519,602✔
2561
  auto score_tags = get_score_tags(score);
2562
  tag_names_.push_back(score);
2563
}
1,519,602✔
2564

1,519,602✔
2565
void MOABMesh::remove_scores()
1,519,602✔
2566
{
1,519,602✔
2567
  for (const auto& name : tag_names_) {
2568
    auto value_name = name + "_mean";
1,519,602✔
2569
    moab::Tag tag;
1,519,602✔
2570
    moab::ErrorCode rval = mbi_->tag_get_handle(value_name.c_str(), tag);
701,483✔
2571
    if (rval != moab::MB_SUCCESS)
2572
      return;
1,519,602✔
2573

1,519,602✔
2574
    rval = mbi_->tag_delete(tag);
2575
    if (rval != moab::MB_SUCCESS) {
1,519,602✔
2576
      auto msg = fmt::format("Failed to delete mesh tag for the score {}"
1,519,602✔
2577
                             " on unstructured mesh {}",
2578
        name, id_);
1,519,602✔
2579
      fatal_error(msg);
1,519,602✔
2580
    }
2581

2582
    auto std_dev_name = name + "_std_dev";
2583
    rval = mbi_->tag_get_handle(std_dev_name.c_str(), tag);
2584
    if (rval != moab::MB_SUCCESS) {
1,519,602✔
2585
      auto msg =
701,483✔
2586
        fmt::format("Std. Dev. mesh tag does not exist for the score {}"
701,483✔
2587
                    " on unstructured mesh {}",
701,483✔
2588
          name, id_);
223,515✔
2589
    }
223,515✔
2590

2591
    rval = mbi_->tag_delete(tag);
701,483✔
2592
    if (rval != moab::MB_SUCCESS) {
2593
      auto msg = fmt::format("Failed to delete mesh tag for the score {}"
2594
                             " on unstructured mesh {}",
2595
        name, id_);
2596
      fatal_error(msg);
818,119✔
2597
    }
818,119✔
2598
  }
5,506,248✔
2599
  tag_names_.clear();
2600
}
4,688,129✔
2601

4,688,129✔
2602
void MOABMesh::set_score_data(const std::string& score,
2603
  const vector<double>& values, const vector<double>& std_dev)
4,688,129✔
2604
{
2605
  auto score_tags = this->get_score_tags(score);
4,688,129✔
2606

2607
  moab::ErrorCode rval;
2608
  // set the score value
4,688,129✔
2609
  rval = mbi_->tag_set_data(score_tags.first, ehs_, values.data());
2610
  if (rval != moab::MB_SUCCESS) {
4,688,129✔
2611
    auto msg = fmt::format("Failed to set the tally value for score '{}' "
20,480✔
2612
                           "on unstructured mesh {}",
2613
      score, id_);
2614
    warning(msg);
4,667,649✔
2615
  }
4,667,649✔
2616

2617
  // set the error value
2618
  rval = mbi_->tag_set_data(score_tags.second, ehs_, std_dev.data());
2619
  if (rval != moab::MB_SUCCESS) {
2620
    auto msg = fmt::format("Failed to set the tally error for score '{}' "
2621
                           "on unstructured mesh {}",
818,119✔
2622
      score, id_);
818,119✔
2623
    warning(msg);
818,119✔
2624
  }
818,119✔
2625
}
818,119✔
2626

818,119✔
2627
void MOABMesh::write(const std::string& base_filename) const
762,819✔
2628
{
762,819✔
2629
  // add extension to the base name
2630
  auto filename = base_filename + ".vtk";
2631
  write_message(5, "Writing unstructured mesh {}...", filename);
1,519,602✔
2632
  filename = settings::path_output + filename;
2633

7,276,559✔
2634
  // write the tetrahedral elements of the mesh only
2635
  // to avoid clutter from zero-value data on other
7,276,559✔
2636
  // elements during visualization
2637
  moab::ErrorCode rval;
7,276,559✔
2638
  rval = mbi_->write_mesh(filename.c_str(), &tetset_, 1);
7,276,559✔
2639
  if (rval != moab::MB_SUCCESS) {
7,276,559✔
2640
    auto msg = fmt::format("Failed to write unstructured mesh {}", id_);
1,010,077✔
2641
    warning(msg);
2642
  }
2643
}
2644

6,266,482✔
2645
#endif
6,266,482✔
2646

6,266,482✔
2647
#ifdef LIBMESH
6,266,482✔
2648

2649
const std::string LibMesh::mesh_lib_type = "libmesh";
2650

2651
LibMesh::LibMesh(pugi::xml_node node) : UnstructuredMesh(node)
2652
{
212,714,013✔
2653
  // filename_ and length_multiplier_ will already be set by the
212,711,167✔
2654
  // UnstructuredMesh constructor
6,263,636✔
2655
  set_mesh_pointer_from_filename(filename_);
2656
  set_length_multiplier(length_multiplier_);
2657
  initialize();
2658
}
2659

2,846✔
2660
// create the mesh from a pointer to a libMesh Mesh
7,276,559✔
2661
LibMesh::LibMesh(libMesh::MeshBase& input_mesh, double length_multiplier)
2662
{
131,856✔
2663
  m_ = &input_mesh;
2664
  set_length_multiplier(length_multiplier);
131,856✔
2665
  initialize();
2666
}
2667

28✔
2668
// create the mesh from an input file
2669
LibMesh::LibMesh(const std::string& filename, double length_multiplier)
28✔
2670
{
2671
  set_mesh_pointer_from_filename(filename);
2672
  set_length_multiplier(length_multiplier);
2673
  initialize();
200,400✔
2674
}
2675

2676
void LibMesh::set_mesh_pointer_from_filename(const std::string& filename)
200,400✔
2677
{
2678
  filename_ = filename;
2679
  unique_m_ = make_unique<libMesh::Mesh>(*settings::libmesh_comm, n_dimension_);
2680
  m_ = unique_m_.get();
2681
  m_->read(filename_);
200,400✔
2682
}
200,400✔
2683

2684
// intialize from mesh file
2685
void LibMesh::initialize()
2686
{
2687
  if (!settings::libmesh_comm) {
1,002,000✔
2688
    fatal_error(
200,400✔
2689
      "Attempting to use an unstructured mesh without a libMesh communicator.");
200,400✔
2690
  }
2691

2692
  // assuming that unstructured meshes used in OpenMC are 3D
2693
  n_dimension_ = 3;
200,400✔
2694

1,002,000✔
2695
  if (specified_length_multiplier_) {
801,600✔
2696
    libMesh::MeshTools::Modification::scale(*m_, length_multiplier_);
2697
  }
2698
  // if OpenMC is managing the libMesh::MeshBase instance, prepare the mesh.
400,800✔
2699
  // Otherwise assume that it is prepared by its owning application
2700
  if (unique_m_) {
2701
    m_->prepare_for_use();
131,856✔
2702
  }
2703

131,856✔
2704
  // ensure that the loaded mesh is 3 dimensional
131,856✔
2705
  if (m_->mesh_dimension() != n_dimension_) {
131,856✔
2706
    fatal_error(fmt::format("Mesh file {} specified for use in an unstructured "
2707
                            "mesh is not a 3D mesh.",
2708
      filename_));
2709
  }
659,280✔
2710

131,856✔
2711
  // create an equation system for storing values
131,856✔
2712
  eq_system_name_ = fmt::format("mesh_{}_system", id_);
2713

2714
  equation_systems_ = make_unique<libMesh::EquationSystems>(*m_);
2715
  libMesh::ExplicitSystem& eq_sys =
263,712✔
2716
    equation_systems_->add_system<libMesh::ExplicitSystem>(eq_system_name_);
131,856✔
2717

2718
#ifdef _OPENMP
7,276,559✔
2719
  int n_threads = omp_get_max_threads();
2720
#else
7,276,559✔
2721
  int n_threads = 1;
7,276,559✔
2722
#endif
1,012,923✔
2723

2724
  for (int i = 0; i < n_threads; i++) {
6,263,636✔
2725
    pl_.emplace_back(m_->sub_point_locator());
2726
    pl_.back()->set_contains_point_tol(FP_COINCIDENT);
2727
    pl_.back()->enable_out_of_mesh_mode();
2728
  }
21✔
2729

2730
  // store first element in the mesh to use as an offset for bin indices
2731
  auto first_elem = *m_->elements_begin();
2732
  first_element_id_ = first_elem->id();
21✔
2733

21✔
2734
  // bounding box for the mesh for quick rejection checks
2735
  bbox_ = libMesh::MeshTools::create_bounding_box(*m_);
2736
}
2737

251,733✔
2738
// Sample position within a tet for LibMesh type tets
251,712✔
2739
Position LibMesh::sample_element(int32_t bin, uint64_t* seed) const
251,712✔
2740
{
251,712✔
2741
  const auto& elem = get_element_from_bin(bin);
2742
  // Get tet vertex coordinates from LibMesh
2743
  std::array<Position, 4> tet_verts;
2744
  for (int i = 0; i < elem.n_nodes(); i++) {
1,258,560✔
2745
    auto node_ref = elem.node_ref(i);
251,712✔
2746
    tet_verts[i] = {node_ref(0), node_ref(1), node_ref(2)};
251,712✔
2747
  }
2748
  // Samples position within tet using Barycentric coordinates
2749
  return this->sample_tet(tet_verts, seed);
2750
}
251,712✔
2751

2752
Position LibMesh::centroid(int bin) const
2753
{
251,712✔
2754
  const auto& elem = this->get_element_from_bin(bin);
251,712✔
2755
  auto centroid = elem.vertex_average();
251,712✔
2756
  return {centroid(0), centroid(1), centroid(2)};
21✔
2757
}
2758

212,711,167✔
2759
int LibMesh::n_vertices() const
2760
{
2761
  return m_->n_nodes();
2762
}
2763

2764
Position LibMesh::vertex(int vertex_id) const
2765
{
212,711,167✔
2766
  const auto node_ref = m_->node_ref(vertex_id);
212,711,167✔
2767
  return {node_ref(0), node_ref(1), node_ref(2)};
212,711,167✔
2768
}
2769

2770
std::vector<int> LibMesh::connectivity(int elem_id) const
2771
{
2772
  std::vector<int> conn;
2773
  const auto* elem_ptr = m_->elem_ptr(elem_id);
2774
  for (int i = 0; i < elem_ptr->n_nodes(); i++) {
212,711,167✔
2775
    conn.push_back(elem_ptr->node_id(i));
212,711,167✔
2776
  }
212,711,167✔
2777
  return conn;
2778
}
2779

2780
std::string LibMesh::library() const
2781
{
2782
  return mesh_lib_type;
2783
}
2784

212,711,167✔
2785
int LibMesh::n_bins() const
212,711,167✔
2786
{
2787
  return m_->n_elem();
212,711,167✔
2788
}
2789

343,413,192✔
2790
int LibMesh::n_surface_bins() const
366,015,919✔
2791
{
235,313,894✔
2792
  int n_bins = 0;
212,711,167✔
2793
  for (int i = 0; i < this->n_bins(); i++) {
2794
    const libMesh::Elem& e = get_element_from_bin(i);
2795
    n_bins += e.n_faces();
2796
    // if this is a boundary element, it will only be visited once,
2797
    // the number of surface bins is incremented to
2798
    for (auto neighbor_ptr : e.neighbor_ptr_range()) {
2799
      // null neighbor pointer indicates a boundary face
2800
      if (!neighbor_ptr) {
2801
        n_bins++;
2802
      }
2803
    }
2804
  }
2805
  return n_bins;
2806
}
2807

2808
void LibMesh::add_score(const std::string& var_name)
2809
{
2810
  // check if this is a new variable
2811
  std::string value_name = var_name + "_mean";
2812
  if (!variable_map_.count(value_name)) {
2813
    auto& eqn_sys = equation_systems_->get_system(eq_system_name_);
2814
    auto var_num =
2815
      eqn_sys.add_variable(value_name, libMesh::CONSTANT, libMesh::MONOMIAL);
2816
    variable_map_[value_name] = var_num;
2817
  }
2818

2819
  std::string std_dev_name = var_name + "_std_dev";
2820
  // check if this is a new variable
2821
  if (!variable_map_.count(std_dev_name)) {
623,424✔
2822
    auto& eqn_sys = equation_systems_->get_system(eq_system_name_);
2823
    auto var_num =
623,424✔
2824
      eqn_sys.add_variable(std_dev_name, libMesh::CONSTANT, libMesh::MONOMIAL);
623,424✔
2825
    variable_map_[std_dev_name] = var_num;
2826
  }
2827
}
2828

623,424✔
2829
void LibMesh::remove_scores()
2830
{
2831
  auto& eqn_sys = equation_systems_->get_system(eq_system_name_);
219,226,515✔
2832
  eqn_sys.clear();
2833
  variable_map_.clear();
219,226,515✔
2834
}
219,226,515✔
2835

2836
void LibMesh::set_score_data(const std::string& var_name,
2837
  const vector<double>& values, const vector<double>& std_dev)
219,226,515✔
2838
{
2839
  auto& eqn_sys = equation_systems_->get_system(eq_system_name_);
2840

488,112✔
2841
  if (!eqn_sys.is_initialized()) {
2842
    equation_systems_->init();
488,112✔
2843
  }
2844

2845
  const libMesh::DofMap& dof_map = eqn_sys.get_dof_map();
488,112✔
2846

2847
  // look up the value variable
2848
  std::string value_name = var_name + "_mean";
219,870,537✔
2849
  unsigned int value_num = variable_map_.at(value_name);
2850
  // look up the std dev variable
219,870,537✔
2851
  std::string std_dev_name = var_name + "_std_dev";
2852
  unsigned int std_dev_num = variable_map_.at(std_dev_name);
2853

2854
  for (auto it = m_->local_elements_begin(); it != m_->local_elements_end();
2855
       it++) {
2856
    auto bin = get_bin_from_element(*it);
2857

2858
    // set value
2859
    vector<libMesh::dof_id_type> value_dof_indices;
2860
    dof_map.dof_indices(*it, value_dof_indices, value_num);
2861
    Ensures(value_dof_indices.size() == 1);
2862
    eqn_sys.solution->set(value_dof_indices[0], values.at(bin));
2863

2864
    // set std dev
2865
    vector<libMesh::dof_id_type> std_dev_dof_indices;
2866
    dof_map.dof_indices(*it, std_dev_dof_indices, std_dev_num);
2867
    Ensures(std_dev_dof_indices.size() == 1);
2868
    eqn_sys.solution->set(std_dev_dof_indices[0], std_dev.at(bin));
2869
  }
2870
}
2871

2872
void LibMesh::write(const std::string& filename) const
2873
{
2874
  write_message(fmt::format(
2875
    "Writing file: {}.e for unstructured mesh {}", filename, this->id_));
2876
  libMesh::ExodusII_IO exo(*m_);
2877
  std::set<std::string> systems_out = {eq_system_name_};
2878
  exo.write_discontinuous_exodusII(
2879
    filename + ".e", *equation_systems_, &systems_out);
2880
}
2881

2882
void LibMesh::bins_crossed(Position r0, Position r1, const Direction& u,
2883
  vector<int>& bins, vector<double>& lengths) const
2884
{
2885
  // TODO: Implement triangle crossings here
2886
  fatal_error("Tracklength tallies on libMesh instances are not implemented.");
2887
}
2888

2889
int LibMesh::get_bin(Position r) const
2890
{
2891
  // look-up a tet using the point locator
2892
  libMesh::Point p(r.x, r.y, r.z);
2893

2894
  // quick rejection check
2895
  if (!bbox_.contains_point(p)) {
2896
    return -1;
2897
  }
2898

646,738✔
2899
#ifdef _OPENMP
2900
  int thread_num = omp_get_thread_num();
646,738✔
2901
#else
2902
  int thread_num = 0;
2903
#endif
23,294✔
2904

2905
  const auto& point_locator = pl_.at(thread_num);
2906

2907
  const auto elem_ptr = (*point_locator)(p);
2908
  return elem_ptr ? get_bin_from_element(elem_ptr) : -1;
23,294✔
2909
}
2910

23,294✔
2911
int LibMesh::get_bin_from_element(const libMesh::Elem* elem) const
23,294✔
2912
{
23,294✔
2913
  int bin = elem->id() - first_element_id_;
2914
  if (bin >= n_bins() || bin < 0) {
2915
    fatal_error(fmt::format("Invalid bin: {}", bin));
2916
  }
46,588✔
2917
  return bin;
2918
}
2919

155,856✔
2920
std::pair<vector<double>, vector<double>> LibMesh::plot(
2921
  Position plot_ll, Position plot_ur) const
2922
{
2923
  return {};
155,856✔
2924
}
2925

2926
const libMesh::Elem& LibMesh::get_element_from_bin(int bin) const
155,856✔
2927
{
155,856✔
2928
  return m_->elem_ref(bin);
155,856✔
2929
}
2930

2931
double LibMesh::volume(int bin) const
2932
{
2933
  return this->get_element_from_bin(bin).volume();
155,856✔
2934
}
779,280✔
2935

623,424✔
2936
#endif // LIBMESH
2937

2938
//==============================================================================
155,856✔
2939
// Non-member functions
155,856✔
2940
//==============================================================================
2941

2942
void read_meshes(pugi::xml_node root)
2943
{
2944
  std::unordered_set<int> mesh_ids;
2945

2946
  for (auto node : root.children("mesh")) {
2947
    // Check to make sure multiple meshes in the same file don't share IDs
2948
    int id = std::stoi(get_node_value(node, "id"));
2949
    if (contains(mesh_ids, id)) {
2950
      fatal_error(fmt::format(
2951
        "Two or more meshes use the same unique ID '{}' in the same input file",
2952
        id));
2953
    }
2954
    mesh_ids.insert(id);
2955

2956
    // If we've already read a mesh with the same ID in a *different* file,
2957
    // assume it is the same here
2958
    if (model::mesh_map.find(id) != model::mesh_map.end()) {
2959
      warning(fmt::format("Mesh with ID={} appears in multiple files.", id));
2960
      continue;
2961
    }
2962

2963
    std::string mesh_type;
2964
    if (check_for_node(node, "type")) {
2965
      mesh_type = get_node_value(node, "type", true, true);
2966
    } else {
2967
      mesh_type = "regular";
2968
    }
2969

2970
    // determine the mesh library to use
2971
    std::string mesh_lib;
2972
    if (check_for_node(node, "library")) {
2973
      mesh_lib = get_node_value(node, "library", true, true);
2974
    }
2975

2976
    // Read mesh and add to vector
2977
    if (mesh_type == RegularMesh::mesh_type) {
2978
      model::meshes.push_back(make_unique<RegularMesh>(node));
2979
    } else if (mesh_type == RectilinearMesh::mesh_type) {
2980
      model::meshes.push_back(make_unique<RectilinearMesh>(node));
2981
    } else if (mesh_type == CylindricalMesh::mesh_type) {
2982
      model::meshes.push_back(make_unique<CylindricalMesh>(node));
2983
    } else if (mesh_type == SphericalMesh::mesh_type) {
2984
      model::meshes.push_back(make_unique<SphericalMesh>(node));
2985
#ifdef DAGMC
2986
    } else if (mesh_type == UnstructuredMesh::mesh_type &&
2987
               mesh_lib == MOABMesh::mesh_lib_type) {
2988
      model::meshes.push_back(make_unique<MOABMesh>(node));
2989
#endif
2990
#ifdef LIBMESH
2991
    } else if (mesh_type == UnstructuredMesh::mesh_type &&
2992
               mesh_lib == LibMesh::mesh_lib_type) {
2993
      model::meshes.push_back(make_unique<LibMesh>(node));
2994
#endif
2995
    } else if (mesh_type == UnstructuredMesh::mesh_type) {
2996
      fatal_error("Unstructured mesh support is not enabled or the mesh "
2997
                  "library is invalid.");
2998
    } else {
2999
      fatal_error("Invalid mesh type: " + mesh_type);
3000
    }
3001

3002
    // Map ID to position in vector
3003
    model::mesh_map[model::meshes.back()->id_] = model::meshes.size() - 1;
3004
  }
3005
}
3006

3007
void meshes_to_hdf5(hid_t group)
3008
{
3009
  // Write number of meshes
3010
  hid_t meshes_group = create_group(group, "meshes");
3011
  int32_t n_meshes = model::meshes.size();
3012
  write_attribute(meshes_group, "n_meshes", n_meshes);
3013

3014
  if (n_meshes > 0) {
3015
    // Write IDs of meshes
3016
    vector<int> ids;
3017
    for (const auto& m : model::meshes) {
3018
      m->to_hdf5(meshes_group);
3019
      ids.push_back(m->id_);
3020
    }
3021
    write_attribute(meshes_group, "ids", ids);
3022
  }
3023

3024
  close_group(meshes_group);
3025
}
3026

3027
void free_memory_mesh()
3028
{
3029
  model::meshes.clear();
3030
  model::mesh_map.clear();
3031
}
3032

3033
extern "C" int n_meshes()
3034
{
3035
  return model::meshes.size();
3036
}
3037

3038
} // 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

© 2026 Coveralls, Inc