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

openmc-dev / openmc / 19074870542

04 Nov 2025 04:02PM UTC coverage: 81.682% (-0.2%) from 81.872%
19074870542

Pull #3279

github

web-flow
Merge 26285d9ac into bd76fc056
Pull Request #3279: Hexagonal mesh model

16965 of 23709 branches covered (71.56%)

Branch coverage included in aggregate %.

560 of 856 new or added lines in 7 files covered. (65.42%)

125 existing lines in 2 files now uncovered.

54731 of 64066 relevant lines covered (85.43%)

42373202.59 hits per line

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

59.69
/src/hex_mesh.cpp
1
#include "openmc/hex_mesh.h"
2
#include <algorithm> // for copy, equal, min, min_element
3
#include <cmath>     // for ceil, sqrt
4
#include <cstddef>   // for size_t
5
#include <string>
6

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

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

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

41
namespace openmc {
42

43
//==============================================================================
44
// Global variables
45
//==============================================================================
46

47
int HexagonalMesh::set_grid()
16✔
48
{
49
  if (shape_[0] % 2 == 0) {
16!
NEW
50
    set_errmsg("Heaxgonal mesh cannot be an even number of hexes wide");
×
NEW
51
    return OPENMC_E_INVALID_ARGUMENT;
×
52
  }
53

54
  hex_radius_ = (shape_[0] - 1) / 2;
16✔
55

56
  if (hex_radius_ == 0)
16!
NEW
57
    hex_count_ = 1;
×
58
  else
59
    hex_count_ = 1 + 3 * (hex_radius_ + 1) * hex_radius_;
16✔
60

61
  // width[1] is the height of the full mesh block, width[0] is the width of
62
  // the hexagon from flat end to flat end
63
  element_volume_ = width_[1] * width_[0] * width_[0] * sqrt(3) * 0.5;
16✔
64

65
  // size of hex is defined as the radius of the circumscribed circle
66
  size_ = width_[0] / sqrt(3.0);
16✔
67

68
  // radius of enclosing cylinder
69
  r_encl_ = (hex_radius_ - 0.5) * sqrt(3) * size_ + (1 - sqrt(3) * 0.5) * size_;
16✔
70

71
  // set the plane normals or 3 principal directions in the hex mesh
72
  init_plane_normals();
16✔
73

74
  // scale grid vectors of hexagonal mesh
75
  scale_grid_vectors(size_);
16✔
76

77
  return 0;
16✔
78
}
79

80
HexagonalMesh::HexagonalMesh(pugi::xml_node node)
16✔
81
  : PeriodicStructuredMesh {node}
16✔
82
{
83
  // Determine number of dimensions for mesh
84
  if (!check_for_node(node, "dimension")) {
16!
NEW
85
    fatal_error("Must specify <dimension> on a hexagonal mesh.");
×
86
  }
87

88
  xt::xtensor<int, 1> shape = get_node_xarray<int>(node, "dimension");
16✔
89
  int n = n_dimension_ = shape.size();
16✔
90
  if (n != 1 && n != 2) {
16!
NEW
91
    fatal_error("Hexagonal mesh must be one or two dimensions.");
×
92
  }
93
  std::copy(shape.begin(), shape.end(), shape_.begin());
16✔
94

95
  // Make sure shape has two numbers
96
  if (n == 1) {
16!
NEW
97
    shape_[1] = 1;
×
NEW
98
    n = 2;
×
99
  }
100

101
  // Check that dimensions are all greater than zero
102
  if (xt::any(shape <= 0)) {
16!
NEW
103
    fatal_error("All entries on the <dimension> element for a tally "
×
104
                "mesh must be positive.");
105
  }
106
  if (shape_[0] % 2 == 0) {
16!
NEW
107
    fatal_error("First shape coordinate must be odd to avoid non-integer "
×
108
                "ring count.");
109
  }
110

111
  // Check for lower-left coordinates
112
  if (check_for_node(node, "lower_left")) {
16!
113
    // Read mesh lower-left corner location
114
    lower_left_ = get_node_xarray<double>(node, "lower_left");
16✔
115
  } else {
NEW
116
    fatal_error("Must specify <lower_left> on a hexagonal mesh.");
×
117
  }
118

119
  // Make sure lower_left and dimension match
120
  if (shape.size() != lower_left_.size()) {
16!
NEW
121
    fatal_error("Number of entries on <lower_left> must be the same "
×
122
                "as the number of entries on <dimension>.");
123
  }
124

125
  if (check_for_node(node, "width")) {
16!
NEW
126
    width_ = get_node_xarray<double>(node, "width");
×
127
    // Make sure one of upper-right or width were specified
NEW
128
    if (check_for_node(node, "upper_right")) {
×
NEW
129
      fatal_error(
×
130
        "Cannot specify both <upper_right> and <width> on a hexgonal mesh.");
131
    }
132

133
    // Check to ensure width has same dimensions
NEW
134
    auto n = width_.size();
×
NEW
135
    if (n != lower_left_.size()) {
×
NEW
136
      fatal_error("Number of entries on <width> must be the same as "
×
137
                  "the number of entries on <lower_left>.");
138
    }
139

140
    // Check for negative widths
NEW
141
    if (xt::any(width_ < 0.0)) {
×
NEW
142
      fatal_error("Cannot have a negative <width> on a tally hexgonal mesh.");
×
143
    }
144

145
    // Set width and upper right coordinate
NEW
146
    upper_right_ = xt::eval(lower_left_ + shape * width_);
×
147

148
  } else if (check_for_node(node, "upper_right")) {
16!
149
    upper_right_ = get_node_xarray<double>(node, "upper_right");
16✔
150

151
    // Check to ensure width has same dimensions
152
    auto n = upper_right_.size();
16✔
153
    if (n != lower_left_.size()) {
16!
NEW
154
      fatal_error("Number of entries on <upper_right> must be the "
×
155
                  "same as the number of entries on <lower_left>.");
156
    }
157

158
    // Check that upper-right is above lower-left
159
    if (xt::any(upper_right_ < lower_left_)) {
16!
NEW
160
      fatal_error("The <upper_right> coordinates must be greater than "
×
161
                  "the <lower_left> coordinates on a tally hexgonal mesh.");
162
    }
163

164
    // Set width
165
    width_ = xt::eval((upper_right_ - lower_left_) / shape);
16✔
166
  } else {
NEW
167
    fatal_error(
×
168
      "Must specify either <upper_right> or <width> on a hexagonal mesh.");
169
  }
170

171
  if (int err = set_grid()) {
16!
NEW
172
    fatal_error(openmc_err_msg);
×
173
  }
174
}
16✔
175

NEW
176
HexagonalMesh::HexagonalMesh(hid_t group) : PeriodicStructuredMesh {group}
×
177
{
178
  // Determine number of dimensions for mesh
NEW
179
  if (!object_exists(group, "dimension")) {
×
NEW
180
    fatal_error("Must specify <dimension> on a regular mesh.");
×
181
  }
182

NEW
183
  xt::xtensor<int, 1> shape;
×
NEW
184
  read_dataset(group, "dimension", shape);
×
NEW
185
  int n = n_dimension_ = shape.size();
×
NEW
186
  if (n != 1 && n != 2) {
×
NEW
187
    fatal_error("Hexagonal mesh must be one or two, or three dimensions.");
×
188
  }
NEW
189
  std::copy(shape.begin(), shape.end(), shape_.begin());
×
190

191
  // Check for lower-left coordinates
NEW
192
  if (object_exists(group, "lower_left")) {
×
193
    // Read mesh lower-left corner location
NEW
194
    read_dataset(group, "lower_left", lower_left_);
×
195
  } else {
NEW
196
    fatal_error("Must specify lower_left dataset on a mesh.");
×
197
  }
198

NEW
199
  if (object_exists(group, "upper_right")) {
×
NEW
200
    read_dataset(group, "upper_right", upper_right_);
×
201
    // Set width
NEW
202
    width_ = xt::eval((upper_right_ - lower_left_) / shape);
×
NEW
203
  } else if (object_exists(group, "width")) {
×
NEW
204
    read_dataset(group, "width", width_);
×
205
    // Set width and upper right coordinate
NEW
206
    upper_right_ = xt::eval(lower_left_ + shape * width_);
×
207
  } else {
NEW
208
    fatal_error(
×
209
      "Must specify either upper_right or width dataset on a hexagonal mesh.");
210
  }
211

212
  // Make sure shape has two numbers
NEW
213
  if (n == 1) {
×
NEW
214
    shape_[1] = 1;
×
NEW
215
    n = 2;
×
216
  }
217

NEW
218
  if (int err = set_grid()) {
×
NEW
219
    fatal_error(openmc_err_msg);
×
220
  }
NEW
221
}
×
222

223
const std::string HexagonalMesh::mesh_type = "hexagonal";
224

NEW
225
double HexagonalMesh::volume(const StructuredMesh::MeshIndex& ijk) const
×
226
{
NEW
227
  return element_volume_;
×
228
}
229

230
int HexagonalMesh::scale_grid_vectors(double s)
16✔
231
{
232
  // scale basis vectors of hexagonal mesh
233
  r_ = r_ * s;
16✔
234
  q_ = q_ * s;
16✔
235
  s_ = s_ * s;
16✔
236
  return 0;
16✔
237
}
238

239
int HexagonalMesh::init_plane_normals()
16✔
240
{
241
  n0_ = 0.5 * (r_ + q_ * 0.5);
16✔
242
  n1_ = 0.5 * (-s_ - r_ * 0.5);
16✔
243
  n2_ = 0.5 * (q_ + s_ * 0.5);
16✔
244

245
  n0_ /= n0_.norm();
16✔
246
  n1_ /= n1_.norm();
16✔
247
  n2_ /= n2_.norm();
16✔
248

249
  return 0;
16✔
250
}
251

252
std::string HexagonalMesh::get_mesh_type() const
3,355✔
253
{
254
  return mesh_type;
3,355✔
255
}
256

257
int HexagonalMesh::hex_distance(
637,879✔
258
  const HexMeshIndex& ijkl0, const HexMeshIndex& ijkl1) const
259
{
260
  // return the integer lateral hex-distance between two hexes (ignores z)
261
  return std::max(
637,879✔
262
    std::max(std::abs(ijkl0[0] - ijkl1[0]), std::abs(ijkl0[1] - ijkl1[1])),
637,879✔
263
    std::abs(ijkl0[2] - ijkl1[2]));
1,275,758✔
264
}
265

266
int HexagonalMesh::hex_radius(const HexMeshIndex& ijkl) const
710,358✔
267
{
268
  // return the integer hex-radius of a hex. (ignores z)
269
  return std::max(
710,358✔
270
    std::max(std::abs(ijkl[0]), std::abs(ijkl[1])), std::abs(ijkl[2]));
1,420,716✔
271
}
272

273
int32_t HexagonalMesh::get_bin_from_hexindices(const HexMeshIndex& ijkl) const
706,805✔
274
{
275
  // get linear index from the HexMeshIndex
276
  int32_t r_0 = hex_radius(ijkl);
706,805✔
277
  if (r_0 == 0)
706,805✔
278
    return (ijkl[3] - 1) * hex_count_;
75,658✔
279
  int32_t start_of_ring = (1 + 3 * r_0 * (r_0 - 1));
631,147✔
280
  int32_t bin_no = (ijkl[3] - 1) * hex_count_ + (1 + 3 * r_0 * (r_0 - 1)) +
631,147✔
281
                   offset_in_ring(ijkl, r_0);
631,147✔
282
  return bin_no;
631,147✔
283
}
284

285
int32_t HexagonalMesh::offset_in_ring(const HexMeshIndex& ijkl, int32_t r) const
638,253✔
286
{
287
  // find the offset within a ring
288
  if (r == 0)
638,253✔
289
    return 0;
374✔
290
  HexMeshIndex i {ijkl};
637,879✔
291
  HexMeshIndex corner {r, 0, -r, 0};
637,879✔
292

293
  int segment {0};
637,879✔
294
  if (abs(i[2]) >= abs(i[1]) && abs(i[2]) >= abs(i[0])) {
637,879✔
295
    if (i[2] < 0)
363,022✔
296
      segment = 0;
178,101✔
297
    else
298
      segment = 3;
184,921✔
299
  } else if (abs(i[1]) >= abs(i[0])) {
274,857✔
300
    if (i[1] > 0)
213,620✔
301
      segment = 1;
109,087✔
302
    else
303
      segment = 4;
104,533✔
304
  } else {
305
    if (i[0] < 0)
61,237✔
306
      segment = 2;
30,470✔
307
    else
308
      segment = 5;
30,767✔
309
  }
310

311
  for (int k = 0; k < segment; k++)
1,934,636✔
312
    corner = rotate_hexindex(corner);
1,296,757✔
313

314
  int32_t hexd = hex_distance(corner, i);
637,879✔
315
  int ii = r * segment + hexd;
637,879✔
316
  return ii;
637,879✔
317
}
318

319
HexagonalMesh::HexMeshIndex HexagonalMesh::rotate_hexindex(
1,305,172✔
320
  const HexMeshIndex& ijkl) const
321
{
322
  HexMeshIndex new_ijkl {ijkl};
1,305,172✔
323
  new_ijkl[0] = -ijkl[1];
1,305,172✔
324
  new_ijkl[1] = -ijkl[2];
1,305,172✔
325
  new_ijkl[2] = -ijkl[0];
1,305,172✔
326
  return new_ijkl;
1,305,172✔
327
}
328

329
HexagonalMesh::HexMeshIndex HexagonalMesh::get_hexindices_from_bin(
3,553✔
330
  const int32_t bin) const
331
{
332
  std::array<HexMeshIndex, 6> directions;
333
  directions[0] = {-1, 1, 0, 0};
3,553✔
334
  directions[1] = {-1, 0, 1, 0};
3,553✔
335
  directions[2] = {0, -1, 1, 0};
3,553✔
336
  directions[3] = {1, -1, 0, 0};
3,553✔
337
  directions[4] = {1, 0, -1, 0};
3,553✔
338
  directions[5] = {0, 1, -1, 0};
3,553✔
339

340
  HexMeshIndex ijkl = {0, 0, 0, 1};
3,553✔
341
  ijkl[3] = (int)floor(bin / hex_count_) + 1;
3,553✔
342
  int spiral_index = bin % hex_count_;
3,553✔
343
  if (spiral_index == 0) {
3,553✔
344
    return ijkl;
187✔
345
  }
346
  int ring = (int)floor((sqrt(12 * spiral_index - 3) + 3) / 6);
3,366✔
347
  int start_of_ring = (1 + 3 * ring * (ring - 1));
3,366✔
348

349
  if (ring > 0) {
3,366!
350
    int segment = (spiral_index - start_of_ring) / ring;
3,366✔
351

352
    ijkl[0] = ring;
3,366✔
353
    ijkl[2] = -ring;
3,366✔
354

355
    for (int k = 0; k < segment; k++)
11,781✔
356
      ijkl = rotate_hexindex(ijkl);
8,415✔
357

358
    for (int k = 0; k < spiral_index - start_of_ring - ring * segment; k++) {
4,488✔
359
      for (int l = 0; l < ijkl.size(); l++)
5,610✔
360
        ijkl[l] = ijkl[l] + directions[segment][l];
4,488✔
361
    }
362
  }
363
  return ijkl;
3,366✔
364
}
365

366
int HexagonalMesh::n_bins() const
32✔
367
{
368
  return hex_count_ * shape_[1];
32✔
369
}
370

371
int HexagonalMesh::n_surface_bins() const
16✔
372
{
373
  return 4 * 4 * n_bins();
16✔
374
}
375

376
xt::xtensor<int, 1> HexagonalMesh::get_x_shape() const
11✔
377
{
378
  // because method is const, shape_ is const as well and can't be adapted
379
  auto tmp_shape = shape_;
11✔
380
  return xt::adapt(tmp_shape, {2});
22✔
381
}
382

383
std::string HexagonalMesh::bin_label(int bin) const
3,553✔
384
{
385
  HexMeshIndex ijkl = get_hexindices_from_bin(bin);
3,553✔
386
  int hr = hex_radius(ijkl);
3,553✔
387
  int ofr = offset_in_ring(ijkl, hr);
3,553✔
388
  return fmt::format(
389
    "Mesh Index ({}, {}, {})", hr, offset_in_ring(ijkl, hr), ijkl[3]);
7,106✔
390
}
391

392
double HexagonalMesh::frac_hexindex_in_direction(const Position& r, int i) const
2,480,676✔
393
{
394
  switch (i) {
2,480,676!
395
  case 0:
620,169✔
396
    return (2.0 / 3.0 * -r.y) / this->size_;
620,169✔
397
  case 1:
620,169✔
398
    return (sqrt(3.0) / 3.0 * r.x + 1.0 / 3.0 * r.y) / this->size_;
620,169✔
399
  case 2:
620,169✔
400
    return -(2.0 / 3.0 * -r.y) / this->size_ -
620,169✔
401
           (sqrt(3.0) / 3.0 * r.x + 1.0 / 3.0 * r.y) / this->size_;
620,169✔
402
  case 3:
620,169✔
403
    // z is idx 1 in width_ and lower_left_ / upper_right_
404
    return (r.z - lower_left_[1]) / width_[1];
620,169✔
405
  }
NEW
406
  return -1;
×
407
}
408

NEW
409
int HexagonalMesh::get_index_in_direction(double r, int i) const
×
410
{
411
  // dummy placeholder
NEW
412
  return 0;
×
413
}
414

415
Position HexagonalMesh::get_position_from_hexindex(HexMeshIndex ijkl) const
3,598,397✔
416
{
417
  // return the cartesian position of center of a hexagon indexed by ijkl
418
  // Note thet we have to use the plane normals as basis vectors, as opposed to
419
  // the grid vectors (r, q, s)
420
  Position r;
3,598,397✔
421
  r.x = ijkl[0] * n0_[0] * size_ * sqrt(3) + ijkl[1] * n1_[0] * size_ * sqrt(3);
3,598,397✔
422
  r.y = ijkl[0] * n0_[1] * size_ * sqrt(3) + ijkl[1] * n1_[1] * size_ * sqrt(3);
3,598,397✔
423
  r.z = (ijkl[3] - 0.5) * width_[1] + lower_left_[1];
3,598,397✔
424

425
  return r;
3,598,397✔
426
}
427

428
HexagonalMesh::HexMeshIndex HexagonalMesh::get_hexindices(
620,169✔
429
  Position r, bool& in_mesh) const
430
{
431
  // return index of mesh element in hexes
432
  local_coords(r);
620,169✔
433
  vector<double> frac_cds {0, 0, 0, 0};
620,169✔
434

435
  // r coordinate
436
  frac_cds[0] = frac_hexindex_in_direction(r, 0);
620,169✔
437
  // q coordinate
438
  frac_cds[1] = frac_hexindex_in_direction(r, 1);
620,169✔
439
  // s coordinate
440
  frac_cds[2] = frac_hexindex_in_direction(r, 2);
620,169✔
441
  // z-coordinate
442
  frac_cds[3] = frac_hexindex_in_direction(r, 3);
620,169✔
443

444
  HexMeshIndex idx = round_frac_hexindex(frac_cds);
620,169✔
445
  // check if either index is out of bounds
446
  in_mesh = in_hexmesh(idx);
620,169✔
447
  return idx;
620,169✔
448
}
620,169✔
449

450
HexagonalMesh::HexMeshIndex HexagonalMesh::round_frac_hexindex(
620,169✔
451
  vector<double> frac_ijkl) const
452
{
453
  std::vector<double> diff(4);
620,169✔
454
  HexMeshIndex ijkl {0, 0, 0, 1};
620,169✔
455

456
  for (int i = 0; i < frac_ijkl.size(); ++i) {
3,100,845✔
457
    diff[i] = (std::abs(std::round(frac_ijkl[i]) - frac_ijkl[i]));
2,480,676✔
458
    ijkl[i] = std::round(frac_ijkl[i]);
2,480,676✔
459
  }
460
  if (diff[0] > diff[1] && diff[0] > diff[2]) {
620,169✔
461
    ijkl[0] = -ijkl[1] - ijkl[2];
165,814✔
462
  } else if (diff[1] > diff[2]) {
454,355✔
463
    ijkl[1] = -ijkl[0] - ijkl[2];
230,208✔
464
  } else {
465
    ijkl[2] = -ijkl[0] - ijkl[1];
224,147✔
466
  }
467
  // z-coordinate should be treated differently
468
  ijkl[3] = std::ceil(frac_ijkl[3]);
620,169✔
469

470
  return ijkl;
620,169✔
471
}
620,169✔
472

473
bool HexagonalMesh::in_hexmesh(HexMeshIndex& ijkl) const
1,215,082✔
474
{
475
  for (auto it = ijkl.begin(); it != std::prev(ijkl.end()); it++) {
3,781,305✔
476
    auto elem = *it;
3,027,816✔
477
    if (abs(elem) > hex_radius_)
3,027,816✔
478
      return false;
461,593✔
479
  }
480
  if (ijkl[3] > shape_[1] || ijkl[3] <= 0)
753,489✔
481
    return false;
123,123✔
482
  return true;
630,366✔
483
}
484

NEW
485
Position HexagonalMesh::sample_element(
×
486
  const HexMeshIndex& ijkl, uint64_t* seed) const
487
{
488
  // return random position inside mesh element
NEW
489
  Position rh = get_position_from_hexindex(ijkl);
×
490

NEW
491
  Position xy = sample_hexagon(seed) * size_;
×
492
  Position z(
NEW
493
    0, 0, uniform_distribution(-width_[1] / 2.0, width_[1] / 2.0, seed));
×
NEW
494
  return origin_ + rh + xy + z;
×
495
}
496

NEW
497
Position HexagonalMesh::sample_hexagon(uint64_t* seed) const
×
498
{
499
  // return a position inside hexagon at (0,0,0)
NEW
500
  double x = uniform_distribution(-sqrt(3) * 0.5, sqrt(3) * 0.5, seed);
×
NEW
501
  double y = uniform_distribution(-0.5, 1.0, seed);
×
502

NEW
503
  if (y > 0.5)
×
NEW
504
    if (y - 0.5 > 0.5 - std::abs(x) / sqrt(3)) { // reflect the point
×
NEW
505
      if (x > 0)
×
NEW
506
        x -= sqrt(3) * 0.5;
×
507
      else
NEW
508
        x += sqrt(3) * 0.5;
×
NEW
509
      y -= 1.5;
×
510
    }
NEW
511
  return Position(x, y, 0);
×
512
}
513

514
double HexagonalMesh::find_r_crossing(
620,169✔
515
  const Position& r, const Direction& u, double l) const
516
{
517
  // solve r.x^2 + r.y^2 == r0^2
518
  // x^2 + 2*s*u*x + s^2*u^2 + s^2*v^2+2*s*v*y + y^2 -r0^2 = 0
519
  // s^2 * (u^2 + v^2) + 2*s*(u*x+v*y) + x^2+y^2-r0^2 = 0
520

521
  const double r0 = r_encl_;
620,169✔
522
  if (r0 == 0.0)
620,169!
NEW
523
    return INFTY;
×
524

525
  const double denominator = u.x * u.x + u.y * u.y;
620,169✔
526

527
  // Direction of flight is in z-direction. Will never intersect r.
528
  if (std::abs(denominator) < FP_PRECISION)
620,169!
NEW
529
    return INFTY;
×
530

531
  // inverse of dominator to help the compiler to speed things up
532
  const double inv_denominator = 1.0 / denominator;
620,169✔
533

534
  const double p = (u.x * r.x + u.y * r.y) * inv_denominator;
620,169✔
535
  double c = r.x * r.x + r.y * r.y - r0 * r0;
620,169✔
536
  double D = p * p - c * inv_denominator;
620,169✔
537

538
  if (D < 0.0)
620,169✔
539
    return INFTY;
187,550✔
540

541
  D = std::sqrt(D);
432,619✔
542

543
  // the solution -p - D is always smaller as -p + D : Check this one first
544
  if (std::abs(c) <= RADIAL_MESH_TOL)
432,619!
NEW
545
    return INFTY;
×
546

547
  if (-p - D > l)
432,619✔
548
    return -p - D;
106,667✔
549
  if (-p + D > l)
325,952✔
550
    return -p + D;
227,436✔
551

552
  return INFTY;
98,516✔
553
}
554

555
template<class T>
556
void HexagonalMesh::raytrace_mesh(
620,169✔
557
  Position r0, Position r1, const Direction& u, T tally) const
558
{
559
  // TODO: when c++-17 is available, use "if constexpr ()" to compile-time
560
  // enable/disable tally calls for now, T template type needs to provide both
561
  // surface and track methods, which might be empty. modern optimizing
562
  // compilers will (hopefully) eliminate the complete code (including
563
  // calculation of parameters) but for the future: be explicit
564

565
  // very similar to to the structured mesh raytrace_mesh but we cannot rely on
566
  // simply recomputing only a single distance. Also the distance to the outside
567
  // of the mesh is somewhat more complicated
568
  int iteration {0};
620,169✔
569
  // Compute the length of the entire track.
570
  double total_distance = (r1 - r0).norm();
620,169✔
571
  if (total_distance == 0.0 && settings::solver_type != SolverType::RANDOM_RAY)
620,169!
NEW
572
    return;
×
573

574
  const int n = n_dimension_ * 2;
620,169✔
575

576
  // Flag if position is inside the mesh
577
  bool in_mesh;
578

579
  // Position is r = r0 + u * traveled_distance, start at r0
580
  double traveled_distance {0.0};
620,169✔
581

582
  // Calculate index of current cell. Offset the position a tiny bit in
583
  // direction of flight
584
  HexMeshIndex ijkl = get_hexindices(r0 + TINY_BIT * u, in_mesh);
620,169✔
585

586
  // If outside mesh and not on the way towards it (i.e. missing the
587
  // circumscribed cylinder) exit early.
588
  double dist_to_enclosing_cyl = find_r_crossing(r0, u, traveled_distance);
620,169✔
589

590
  if (!in_mesh) {
620,169✔
591
    if (dist_to_enclosing_cyl < INFTY &&
305,371✔
592
        dist_to_enclosing_cyl < total_distance) {
102,311✔
593
      traveled_distance = dist_to_enclosing_cyl;
29,084✔
594
    } else
595
      return;
276,287✔
596
  }
597

598
  // if track is very short, assume that it is completely inside one cell.
599
  // Only the current cell will score and no surfaces
600
  if (total_distance < 2 * TINY_BIT) {
343,882!
NEW
601
    if (in_mesh) {
×
NEW
602
      tally.track(ijkl, 1.0);
×
603
    }
NEW
604
    return;
×
605
  }
606

607
  // translate start and end positions,
608
  // this needs to come after the get_hexindices call because it does its own
609
  // translation
610
  local_coords(r0);
343,882✔
611
  local_coords(r1);
343,882✔
612

613
  // Calculate initial distances to next surfaces in all three dimensions
614
  std::array<HexMeshDistance, 4> distances;
1,031,646✔
615
  for (int k = 0; k < n; ++k) {
1,719,410✔
616
    distances[k] = distance_to_hex_boundary(ijkl, k, r0, u, traveled_distance);
1,375,528✔
617
  }
618

619
  // Loop until r = r1 is eventually reached
620
  while (true) {
594,913✔
621
    iteration++;
938,795✔
622
    // std::cout << iteration << std::endl;
623
    //  find surface with minimal distance to current position
624
    const auto k =
938,795✔
625
      std::min_element(distances.begin(), distances.end()) - distances.begin();
938,795✔
626

627
    if (in_mesh) {
938,795✔
628
      // Tally track length delta since last step
629
      tally.track(ijkl,
630,366✔
630
        (std::min(distances[k].distance, total_distance) - traveled_distance) /
630,366✔
631
          total_distance);
632
    }
633

634
    // update position and leave, if we have reached end position
635
    traveled_distance = distances[k].distance;
938,795✔
636
    if (traveled_distance >= total_distance)
938,795✔
637
      return;
343,882✔
638

639
    if (in_mesh) {
594,913✔
640
      // If we have not reached r1, we have hit a surface. Tally outward current
641
      tally.surface(ijkl, k, distances[k].max_surface, false);
413,369✔
642
    }
643

644
    // Update cell and calculate distance to next surface in k-direction.
645
    ijkl = distances[k].next_index;
594,913✔
646
    // now the index has been updated recompute the distances - unfortunately
647
    // we have to do more than one again (as opposed to for cartesian mesh)
648

649
    if (k < 3) {
594,913✔
650
      for (int j = 0; j < 4; ++j) {
2,713,260✔
651
        distances[j] =
2,170,608✔
652
          distance_to_hex_boundary(ijkl, j, r0, u, traveled_distance);
2,170,608✔
653
      }
654
    } else {
655
      distances[3] =
52,261✔
656
        distance_to_hex_boundary(ijkl, 3, r0, u, traveled_distance);
52,261✔
657
      for (int j = 0; j < 3; ++j) {
209,044✔
658
        distances[j].next_index[3] = ijkl[3];
156,783✔
659
      }
660
    }
661

662
    // Check if we have left the interior of the mesh
663
    // Do this by getting new index
664
    in_mesh = in_hexmesh(ijkl);
594,913✔
665

666
    // If we are still inside the mesh, tally inward current for the next cell
667
    if (in_mesh)
594,913✔
668
      tally.surface(ijkl, k, !distances[k].max_surface, true);
315,568✔
669
  }
670
}
671

672
HexagonalMesh::HexMeshDistance HexagonalMesh::distance_to_hex_boundary(
3,598,397✔
673
  const HexMeshIndex& ijkl, int i, const Position& r0, const Direction& u,
674
  double l) const
675
{
676
  // Compute the distance to the element boundary of index i \in {0, ..., 3}
677
  // i==3 means z
678

679
  Position r = r0 - origin_;
3,598,397✔
680
  // Given the hex index - we now find the distance from r0 to the 0:q, 1:r, 2:s
681
  // plane(s) successively, given that the hex-center is given by the hexindex
682
  // quadruplet and hence also the planes.
683
  Position rh = get_position_from_hexindex(ijkl);
3,598,397✔
684
  // local position relative to hex center
685
  Position rloc = r0 + l * u - rh;
3,598,397✔
686
  HexMeshDistance d;
3,598,397✔
687
  d.next_index = ijkl;
3,598,397✔
688

689
  double dh = 0;
3,598,397✔
690

691
  if (i < 3) {
3,598,397✔
692
    if (std::abs(u[0]) + std::abs(u[1]) < FP_PRECISION)
2,659,602!
NEW
693
      return d;
×
694
    switch (i) {
2,659,602!
695
    case 0:
886,534✔
696
      if (std::abs(u.dot(n0_)) < FP_PRECISION) {
886,534!
NEW
697
        return d;
×
698
      }
699
      dh = rh.dot(n0_) / u.dot(n0_);
886,534✔
700
      d.max_surface = n0_.dot(u) > 0;
886,534✔
701
      if (abs(ijkl[0]) > hex_radius_ + 1) {
886,534✔
702
        return d;
38,632✔
703
      }
704
      if (d.max_surface) {
847,902✔
705
        d.distance = dh + (this->r_ - r0).dot(this->n0_) / u.dot(this->n0_);
425,711✔
706
        d.next_index[0]++;
425,711✔
707
        d.next_index[2]--;
425,711✔
708
      } else {
709
        d.distance = dh + (-this->r_ - r0).dot(this->n0_) / u.dot(this->n0_);
422,191✔
710
        d.next_index[0]--;
422,191✔
711
        d.next_index[2]++;
422,191✔
712
      }
713
      break;
847,902✔
714
    case 1:
886,534✔
715
      if (std::abs(u.dot(n1_)) < FP_PRECISION) {
886,534!
NEW
716
        return d;
×
717
      }
718
      dh = rh.dot(n1_) / u.dot(n1_);
886,534✔
719
      d.max_surface = n1_.dot(u) > 0;
886,534✔
720
      if (abs(ijkl[1]) > hex_radius_ + 1) {
886,534✔
721
        return d;
27,181✔
722
      }
723
      if (d.max_surface) {
859,353✔
724
        d.distance = dh + (-this->s_ - r0).dot(this->n1_) / u.dot(this->n1_);
426,668✔
725
        d.next_index[1]++;
426,668✔
726
        d.next_index[2]--;
426,668✔
727
      } else {
728
        d.distance = dh + (this->s_ - r0).dot(this->n1_) / u.dot(this->n1_);
432,685✔
729
        d.next_index[1]--;
432,685✔
730
        d.next_index[2]++;
432,685✔
731
      }
732
      break;
859,353✔
733
    case 2:
886,534✔
734
      if (std::abs(u.dot(n2_)) < FP_PRECISION) {
886,534!
NEW
735
        return d;
×
736
      }
737
      dh = rh.dot(n2_) / u.dot(n2_);
886,534✔
738
      d.max_surface = n2_.dot(u) > 0;
886,534✔
739
      if (abs(ijkl[2]) > hex_radius_ + 1) {
886,534✔
740
        return d;
25,234✔
741
      }
742
      if (d.max_surface) {
861,300✔
743
        d.distance = dh + (this->q_ - r0).dot(this->n2_) / u.dot(this->n2_);
432,773✔
744
        d.next_index[0]--;
432,773✔
745
        d.next_index[1]++;
432,773✔
746
      } else {
747
        d.distance = dh + (-this->q_ - r0).dot(this->n2_) / u.dot(this->n2_);
428,527✔
748
        d.next_index[0]++;
428,527✔
749
        d.next_index[1]--;
428,527✔
750
      }
751
      break;
861,300✔
752
    }
753
  } else {
754
    if (std::abs(u[2]) < FP_PRECISION) {
938,795!
NEW
755
      return d;
×
756
    }
757
    // Z-planes z-index has index 3 (nr 4) in ijkl.
758
    d.max_surface = (u[2] > 0);
938,795✔
759
    if (d.max_surface) {
938,795✔
760
      d.next_index[3]++;
473,275✔
761
      d.distance = ((lower_left_[1] + ijkl[3] * width_[1]) - r0[2]) / u[2];
473,275✔
762
    } else {
763
      d.next_index[3]--;
465,520✔
764
      d.distance =
465,520✔
765
        ((lower_left_[1] + (ijkl[3] - 1) * width_[1]) - r0[2]) / u[2];
465,520✔
766
    }
767
  }
768
  return d;
3,507,350✔
769
}
770

NEW
771
StructuredMesh::MeshDistance HexagonalMesh::distance_to_grid_boundary(
×
772
  const MeshIndex& ijk, int i, const Position& r0, const Direction& u,
773
  double l) const
774
{
NEW
775
  MeshDistance d;
×
NEW
776
  d.distance = INFTY;
×
NEW
777
  return d;
×
778
}
779

NEW
780
std::pair<vector<double>, vector<double>> HexagonalMesh::plot(
×
781
  Position plot_ll, Position plot_ur) const
782
{
NEW
783
  fatal_error("Plot of hexagonal Mesh not implemented");
×
784

785
  // Figure out which axes lie in the plane of the plot.
786
  array<vector<double>, 2> axis_lines;
787
  return {axis_lines[0], axis_lines[1]};
788
}
789

790
void HexagonalMesh::to_hdf5_inner(hid_t mesh_group) const
11✔
791
{
792
  write_dataset(mesh_group, "dimension", get_x_shape());
11✔
793
  write_dataset(mesh_group, "lower_left", lower_left_);
11✔
794
  write_dataset(mesh_group, "upper_right", upper_right_);
11✔
795
  write_dataset(mesh_group, "width", width_);
11✔
796
  // hex-mesh specifics
797
  write_dataset(mesh_group, "hex_count", hex_count_);
11✔
798
  write_dataset(mesh_group, "hex_radius", hex_radius_);
11✔
799
}
11✔
800

NEW
801
double HexagonalMesh::volume(const HexMeshIndex& ijkl) const
×
802
{
NEW
803
  double zdiff = (upper_right_[2] - lower_left_[2]) / shape_[2];
×
NEW
804
  return 6 * sqrt(3.0) * 0.25 * size_ * size_ * zdiff;
×
805
}
806

807
void HexagonalMesh::bins_crossed(Position r0, Position r1, const Direction& u,
378,983✔
808
  vector<int>& bins, vector<double>& lengths) const
809
{
810

811
  // Helper tally class.
812
  // stores a pointer to the mesh class and references to bins and lengths
813
  // parameters. Performs the actual tally through the track method.
814
  struct TrackAggregator {
815
    TrackAggregator(
378,983✔
816
      const HexagonalMesh* _mesh, vector<int>& _bins, vector<double>& _lengths)
817
      : mesh(_mesh), bins(_bins), lengths(_lengths)
378,983✔
818
    {}
378,983✔
819
    void surface(const HexMeshIndex& ijkl, int k, bool max, bool inward) const
361,592✔
820
    {}
361,592✔
821
    void track(const HexMeshIndex& ijkl, double l) const
339,460✔
822
    {
823
      bins.push_back(mesh->get_bin_from_hexindices(ijkl));
339,460✔
824
      lengths.push_back(l);
339,460✔
825
    }
339,460✔
826

827
    const HexagonalMesh* mesh;
828
    vector<int>& bins;
829
    vector<double>& lengths;
830
  };
831

832
  // Perform the mesh raytrace with the helper class.
833
  raytrace_mesh(r0, r1, u, TrackAggregator(this, bins, lengths));
378,983✔
834
}
378,983✔
835

836
void HexagonalMesh::surface_bins_crossed(
241,186✔
837
  Position r0, Position r1, const Direction& u, vector<int>& bins) const
838
{
839

840
  // Helper tally class.
841
  // stores a pointer to the mesh class and a reference to the bins parameter.
842
  // Performs the actual tally through the surface method.
843
  struct SurfaceAggregator {
844
    SurfaceAggregator(const HexagonalMesh* _mesh, vector<int>& _bins)
241,186✔
845
      : mesh(_mesh), bins(_bins)
241,186✔
846
    {}
241,186✔
847
    void surface(const HexMeshIndex& ijkl, int k, bool max, bool inward) const
367,345✔
848
    {
849
      int i_bin = 4 * 4 * mesh->get_bin_from_hexindices(ijkl) + 4 * k;
367,345✔
850
      if (max)
367,345✔
851
        i_bin += 2;
183,216✔
852
      if (inward)
367,345✔
853
        i_bin += 1;
162,624✔
854
      bins.push_back(i_bin);
367,345✔
855
    }
367,345✔
856
    void track(const HexMeshIndex& idx, double l) const {}
290,906✔
857

858
    const HexagonalMesh* mesh;
859
    vector<int>& bins;
860
  };
861

862
  // Perform the mesh raytrace with the helper class.
863
  raytrace_mesh(r0, r1, u, SurfaceAggregator(this, bins));
241,186✔
864
}
241,186✔
865

866
//==============================================================================
867
// Helper functions for the C API
868
//==============================================================================
869

NEW
870
int check_hex_mesh(int32_t index)
×
871
{
NEW
872
  if (index < 0 || index >= model::meshes.size()) {
×
NEW
873
    set_errmsg("Index in meshes array is out of bounds.");
×
NEW
874
    return OPENMC_E_OUT_OF_BOUNDS;
×
875
  }
NEW
876
  return 0;
×
877
}
878
// This is identical to the one in mesh.cpp
879
template<class T>
NEW
880
int check_mesh_type(int32_t index)
×
881
{
NEW
882
  if (int err = check_hex_mesh(index))
×
NEW
883
    return err;
×
884

NEW
885
  T* mesh = dynamic_cast<T*>(model::meshes[index].get());
×
NEW
886
  if (!mesh) {
×
NEW
887
    set_errmsg("This function is not valid for input mesh.");
×
NEW
888
    return OPENMC_E_INVALID_TYPE;
×
889
  }
NEW
890
  return 0;
×
891
}
892

893
// This is identical to the one in mesh.cpp
894
template<class T>
895
bool is_mesh_type(int32_t index)
896
{
897
  T* mesh = dynamic_cast<T*>(model::meshes[index].get());
898
  return mesh;
899
}
900

901
//! Get the dimension of a hexagonal mesh
NEW
902
extern "C" int openmc_hexagonal_mesh_get_dimension(
×
903
  int32_t index, int** dims, int* n)
904
{
NEW
905
  if (int err = check_mesh_type<HexagonalMesh>(index))
×
NEW
906
    return err;
×
907
  HexagonalMesh* mesh =
NEW
908
    dynamic_cast<HexagonalMesh*>(model::meshes[index].get());
×
NEW
909
  *dims = mesh->shape_.data();
×
NEW
910
  *n = mesh->n_dimension_;
×
NEW
911
  return 0;
×
912
}
913

914
//! Set the dimension of a hexagonal mesh
NEW
915
extern "C" int openmc_hexagonal_mesh_set_dimension(
×
916
  int32_t index, int n, const int* dims)
917
{
NEW
918
  if (int err = check_mesh_type<HexagonalMesh>(index))
×
NEW
919
    return err;
×
920
  HexagonalMesh* mesh =
NEW
921
    dynamic_cast<HexagonalMesh*>(model::meshes[index].get());
×
922

923
  // Copy dimension
NEW
924
  mesh->n_dimension_ = n;
×
NEW
925
  std::copy(dims, dims + n, mesh->shape_.begin());
×
926

927
  // TODO: incorporate this bit into method in HexagonalMesh that can be called
928
  // from here and from constructor
NEW
929
  mesh->hex_radius_ = (mesh->shape_[0] - 1) / 2;
×
NEW
930
  if (mesh->hex_radius_ == 0)
×
NEW
931
    mesh->hex_count_ = 1;
×
932
  else
NEW
933
    mesh->hex_count_ = 1 + 3 * (mesh->hex_radius_ + 1) * mesh->hex_radius_;
×
934

NEW
935
  return 0;
×
936
}
937
//! Get the hexagonal mesh parameters
NEW
938
extern "C" int openmc_hexagonal_mesh_get_params(
×
939
  int32_t index, double** ll, double** ur, double** width, int* n)
940
{
NEW
941
  if (int err = check_mesh_type<HexagonalMesh>(index))
×
NEW
942
    return err;
×
NEW
943
  HexagonalMesh* m = dynamic_cast<HexagonalMesh*>(model::meshes[index].get());
×
944

NEW
945
  if (m->lower_left_.dimension() == 0) {
×
NEW
946
    set_errmsg("Mesh parameters have not been set.");
×
NEW
947
    return OPENMC_E_ALLOCATE;
×
948
  }
949

NEW
950
  *ll = m->lower_left_.data();
×
NEW
951
  *ur = m->upper_right_.data();
×
NEW
952
  *width = m->width_.data();
×
NEW
953
  *n = m->n_dimension_;
×
NEW
954
  return 0;
×
955
}
956

957
//! Set the hexagonal mesh parameters
NEW
958
extern "C" int openmc_hexagonal_mesh_set_params(
×
959
  int32_t index, int n, const double* ll, const double* ur, const double* width)
960
{
NEW
961
  if (int err = check_mesh_type<HexagonalMesh>(index))
×
NEW
962
    return err;
×
NEW
963
  HexagonalMesh* m = dynamic_cast<HexagonalMesh*>(model::meshes[index].get());
×
964

NEW
965
  if (m->n_dimension_ == -1) {
×
NEW
966
    set_errmsg("Need to set mesh dimension before setting parameters.");
×
NEW
967
    return OPENMC_E_UNASSIGNED;
×
968
  }
969

NEW
970
  vector<std::size_t> shape = {static_cast<std::size_t>(n)};
×
NEW
971
  if (ll && ur) {
×
NEW
972
    m->lower_left_ = xt::adapt(ll, n, xt::no_ownership(), shape);
×
NEW
973
    m->upper_right_ = xt::adapt(ur, n, xt::no_ownership(), shape);
×
NEW
974
    m->width_ = (m->upper_right_ - m->lower_left_) / m->get_x_shape();
×
NEW
975
  } else if (ll && width) {
×
NEW
976
    m->lower_left_ = xt::adapt(ll, n, xt::no_ownership(), shape);
×
NEW
977
    m->width_ = xt::adapt(width, n, xt::no_ownership(), shape);
×
NEW
978
    m->upper_right_ = m->lower_left_ + m->get_x_shape() * m->width_;
×
NEW
979
  } else if (ur && width) {
×
NEW
980
    m->upper_right_ = xt::adapt(ur, n, xt::no_ownership(), shape);
×
NEW
981
    m->width_ = xt::adapt(width, n, xt::no_ownership(), shape);
×
NEW
982
    m->lower_left_ = m->upper_right_ - m->get_x_shape() * m->width_;
×
983
  } else {
NEW
984
    set_errmsg("At least two parameters must be specified.");
×
NEW
985
    return OPENMC_E_INVALID_ARGUMENT;
×
986
  }
987

988
  // TODO: incorporate this into method in HexagonalMesh that can be called from
989
  // here and from constructor
990

991
  // Set material volumes etc.
NEW
992
  m->size_ = m->width_[0] / sqrt(3.0);
×
NEW
993
  m->init_plane_normals();
×
NEW
994
  m->scale_grid_vectors(m->size_);
×
995

NEW
996
  return 0;
×
NEW
997
}
×
998

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