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

openmc-dev / openmc / 16190841638

10 Jul 2025 09:03AM UTC coverage: 84.887% (-0.4%) from 85.251%
16190841638

Pull #3279

github

web-flow
Merge 5f15cb8bb into d700d395d
Pull Request #3279: Hexagonal mesh model

564 of 801 new or added lines in 6 files covered. (70.41%)

116 existing lines in 16 files now uncovered.

53118 of 62575 relevant lines covered (84.89%)

37709984.48 hits per line

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

67.08
/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
HexagonalMesh::HexagonalMesh(pugi::xml_node node)
17✔
48
  : PeriodicStructuredMesh {node}
17✔
49
{
50
  // Determine number of dimensions for mesh
51
  if (!check_for_node(node, "dimension")) {
17✔
NEW
52
    fatal_error("Must specify <dimension> on a regular mesh.");
×
53
  }
54

55
  xt::xtensor<int, 1> shape = get_node_xarray<int>(node, "dimension");
17✔
56
  int n = n_dimension_ = shape.size();
17✔
57
  if (n != 1 && n != 2) {
17✔
NEW
58
    fatal_error("Mesh must be one or two dimensions.");
×
59
  }
60
  std::copy(shape.begin(), shape.end(), shape_.begin());
17✔
61

62
  // Make sure shape has two numbers
63
  if (n == 1) {
17✔
NEW
64
    shape_[1] = 1;
×
NEW
65
    n = 2;
×
66
  }
67

68
  // Check that dimensions are all greater than zero
69
  if (xt::any(shape <= 0)) {
17✔
NEW
70
    fatal_error("All entries on the <dimension> element for a tally "
×
71
                "mesh must be positive.");
72
  }
73
  if (shape_[0] % 2 == 0) {
17✔
NEW
74
    fatal_error("First shape coordinate must be odd to avoid non-integer "
×
75
                "ring count.");
76
  }
77
  hex_radius_ = (shape_[0] - 1) / 2;
17✔
78

79
  // Check for lower-left coordinates
80
  if (check_for_node(node, "lower_left")) {
17✔
81
    // Read mesh lower-left corner location
82
    lower_left_ = get_node_xarray<double>(node, "lower_left");
17✔
83
  } else {
NEW
84
    fatal_error("Must specify <lower_left> on a hexagonal mesh.");
×
85
  }
86

87
  // Make sure lower_left and dimension match
88
  if (shape.size() != lower_left_.size()) {
17✔
NEW
89
    fatal_error("Number of entries on <lower_left> must be the same "
×
90
                "as the number of entries on <dimension>.");
91
  }
92

93
  if (check_for_node(node, "width")) {
17✔
NEW
94
    width_ = get_node_xarray<double>(node, "width");
×
95
    // Make sure one of upper-right or width were specified
NEW
96
    if (check_for_node(node, "upper_right")) {
×
NEW
97
      fatal_error(
×
98
        "Cannot specify both <upper_right> and <width> on a hexgonal mesh.");
99
    }
100

101
    // Check to ensure width has same dimensions
NEW
102
    auto n = width_.size();
×
NEW
103
    if (n != lower_left_.size()) {
×
NEW
104
      fatal_error("Number of entries on <width> must be the same as "
×
105
                  "the number of entries on <lower_left>.");
106
    }
107

108
    // Check for negative widths
NEW
109
    if (xt::any(width_ < 0.0)) {
×
NEW
110
      fatal_error("Cannot have a negative <width> on a tally hexgonal mesh.");
×
111
    }
112

113
    // Set width and upper right coordinate
NEW
114
    upper_right_ = xt::eval(lower_left_ + shape * width_);
×
115

116
  } else if (check_for_node(node, "upper_right")) {
17✔
117
    upper_right_ = get_node_xarray<double>(node, "upper_right");
17✔
118

119
    // Check to ensure width has same dimensions
120
    auto n = upper_right_.size();
17✔
121
    if (n != lower_left_.size()) {
17✔
NEW
122
      fatal_error("Number of entries on <upper_right> must be the "
×
123
                  "same as the number of entries on <lower_left>.");
124
    }
125

126
    // Check that upper-right is above lower-left
127
    if (xt::any(upper_right_ < lower_left_)) {
17✔
NEW
128
      fatal_error("The <upper_right> coordinates must be greater than "
×
129
                  "the <lower_left> coordinates on a tally hexgonal mesh.");
130
    }
131

132
    // Set width
133
    width_ = xt::eval((upper_right_ - lower_left_) / shape);
17✔
134
  } else {
NEW
135
    fatal_error(
×
136
      "Must specify either <upper_right> or <width> on a hexagonal mesh.");
137
  }
138

139
  // n.b. must check that the number of hexes is odd
140
  //   or allow a parameter crown/ring
141
  if (hex_radius_ == 0)
17✔
NEW
142
    hex_count_ = 1;
×
143
  else
144
    hex_count_ = 1 + 3 * (hex_radius_ + 1) * hex_radius_;
17✔
145

146
  // width[1] is the height of the full mesh block, width[0] is the width of
147
  // the hexagon from flat end to flat end
148
  element_volume_ = width_[1] * width_[0] * width_[0] * sqrt(3) * 0.5;
17✔
149

150
  // Set material volumes
151
  volume_frac_ = 1.0 / hex_count_ / shape_[1];
17✔
152

153
  // size of hex is defined as the radius of the circumscribed circle
154
  size_ = width_[0] / sqrt(3.0);
17✔
155

156
  // radius of enclosing cylinder
157
  r_encl_ = (hex_radius_ - 0.5) * sqrt(3) * size_ + (1 - sqrt(3) * 0.5) * size_;
17✔
158

159
  // set the plane normals or 3 principal directions in the hex mesh
160
  init_plane_normals();
17✔
161

162
  // scale grid vectors of hexagonal mesh
163
  scale_grid_vectors(size_);
17✔
164
}
17✔
165

166
const std::string HexagonalMesh::mesh_type = "hexagonal";
167

NEW
168
double HexagonalMesh::volume(const StructuredMesh::MeshIndex& ijk) const
×
169
{
NEW
170
  return element_volume_;
×
171
}
172

173
int HexagonalMesh::scale_grid_vectors(double s)
17✔
174
{
175
  // scale basis vectors of hexagonal mesh
176
  r_ = r_ * s;
17✔
177
  q_ = q_ * s;
17✔
178
  s_ = s_ * s;
17✔
179
  return 0;
17✔
180
}
181

182
int HexagonalMesh::init_plane_normals()
17✔
183
{
184
  n0_ = 0.5 * (r_ + q_ * 0.5);
17✔
185
  n1_ = 0.5 * (-s_ - r_ * 0.5);
17✔
186
  n2_ = 0.5 * (q_ + s_ * 0.5);
17✔
187

188
  n0_ /= n0_.norm();
17✔
189
  n1_ /= n1_.norm();
17✔
190
  n2_ /= n2_.norm();
17✔
191

192
  return 0;
17✔
193
}
194

195
std::string HexagonalMesh::get_mesh_type() const
3,660✔
196
{
197
  return mesh_type;
3,660✔
198
}
199

200
int HexagonalMesh::hex_distance(
202,824✔
201
  const HexMeshIndex& ijkl0, const HexMeshIndex& ijkl1) const
202
{
203
  // return the integer lateral hex-distance between two hexes (ignores z)
204
  return std::max(
202,824✔
205
    std::max(std::abs(ijkl0[0] - ijkl1[0]), std::abs(ijkl0[1] - ijkl1[1])),
202,824✔
206
    std::abs(ijkl0[2] - ijkl1[2]));
405,648✔
207
}
208

209
int HexagonalMesh::hex_radius(const HexMeshIndex& ijkl) const
234,720✔
210
{
211
  // return the integer hex-radius of a hex. (ignores z)
212
  return std::max(
234,720✔
213
    std::max(std::abs(ijkl[0]), std::abs(ijkl[1])), std::abs(ijkl[2]));
469,440✔
214
}
215

216
int32_t HexagonalMesh::get_bin_from_hexindices(const HexMeshIndex& ijkl) const
230,844✔
217
{
218
  // get linear index from the HexMeshIndex
219
  int32_t r_0 = hex_radius(ijkl);
230,844✔
220
  if (r_0 == 0)
230,844✔
221
    return (ijkl[3] - 1) * hex_count_;
35,364✔
222
  int32_t start_of_ring = (1 + 3 * r_0 * (r_0 - 1));
195,480✔
223
  int32_t bin_no = (ijkl[3] - 1) * hex_count_ + (1 + 3 * r_0 * (r_0 - 1)) +
195,480✔
224
                   offset_in_ring(ijkl, r_0);
195,480✔
225
  return bin_no;
195,480✔
226
}
227

228
int32_t HexagonalMesh::offset_in_ring(const HexMeshIndex& ijkl, int32_t r) const
203,232✔
229
{
230
  // find the offset within a ring
231
  if (r == 0)
203,232✔
232
    return 0;
408✔
233
  HexMeshIndex i {ijkl};
202,824✔
234
  HexMeshIndex corner {r, 0, -r, 0};
202,824✔
235

236
  int segment {0};
202,824✔
237
  if (abs(i[2]) >= abs(i[1]) && abs(i[2]) >= abs(i[0])) {
202,824✔
238
    if (i[2] < 0)
111,732✔
239
      segment = 0;
53,268✔
240
    else
241
      segment = 3;
58,464✔
242
  } else if (abs(i[1]) >= abs(i[0])) {
91,092✔
243
    if (i[1] > 0)
68,520✔
244
      segment = 1;
33,540✔
245
    else
246
      segment = 4;
34,980✔
247
  } else {
248
    if (i[0] < 0)
22,572✔
249
      segment = 2;
11,556✔
250
    else
251
      segment = 5;
11,016✔
252
  }
253

254
  for (int k = 0; k < segment; k++)
629,868✔
255
    corner = rotate_hexindex(corner);
427,044✔
256

257
  int32_t hexd = hex_distance(corner, i);
202,824✔
258
  int ii = r * segment + hexd;
202,824✔
259
  return ii;
202,824✔
260
}
261

262
HexagonalMesh::HexMeshIndex HexagonalMesh::rotate_hexindex(
436,224✔
263
  const HexMeshIndex& ijkl) const
264
{
265
  HexMeshIndex new_ijkl {ijkl};
436,224✔
266
  new_ijkl[0] = -ijkl[1];
436,224✔
267
  new_ijkl[1] = -ijkl[2];
436,224✔
268
  new_ijkl[2] = -ijkl[0];
436,224✔
269
  return new_ijkl;
436,224✔
270
}
271

272
HexagonalMesh::HexMeshIndex HexagonalMesh::get_hexindices_from_bin(
3,876✔
273
  const int32_t bin) const
274
{
275
  std::array<HexMeshIndex, 6> directions;
276
  directions[0] = {-1, 1, 0, 0};
3,876✔
277
  directions[1] = {-1, 0, 1, 0};
3,876✔
278
  directions[2] = {0, -1, 1, 0};
3,876✔
279
  directions[3] = {1, -1, 0, 0};
3,876✔
280
  directions[4] = {1, 0, -1, 0};
3,876✔
281
  directions[5] = {0, 1, -1, 0};
3,876✔
282

283
  HexMeshIndex ijkl = {0, 0, 0, 1};
3,876✔
284
  ijkl[3] = (int)floor(bin / hex_count_) + 1;
3,876✔
285
  int spiral_index = bin % hex_count_;
3,876✔
286
  if (spiral_index == 0) {
3,876✔
287
    return ijkl;
204✔
288
  }
289
  int ring = (int)floor((sqrt(12 * spiral_index - 3) + 3) / 6);
3,672✔
290
  int start_of_ring = (1 + 3 * ring * (ring - 1));
3,672✔
291

292
  if (ring > 0) {
3,672✔
293
    int segment = (spiral_index - start_of_ring) / ring;
3,672✔
294

295
    ijkl[0] = ring;
3,672✔
296
    ijkl[2] = -ring;
3,672✔
297

298
    for (int k = 0; k < segment; k++)
12,852✔
299
      ijkl = rotate_hexindex(ijkl);
9,180✔
300

301
    for (int k = 0; k < spiral_index - start_of_ring - ring * segment; k++) {
4,896✔
302
      for (int l = 0; l < ijkl.size(); l++)
6,120✔
303
        ijkl[l] = ijkl[l] + directions[segment][l];
4,896✔
304
    }
305
  }
306
  return ijkl;
3,672✔
307
}
308

309
int HexagonalMesh::n_bins() const
34✔
310
{
311
  return hex_count_ * shape_[1];
34✔
312
}
313

314
int HexagonalMesh::n_surface_bins() const
17✔
315
{
316
  return 4 * 4 * n_bins();
17✔
317
}
318

319
xt::xtensor<int, 1> HexagonalMesh::get_x_shape() const
12✔
320
{
321
  // because method is const, shape_ is const as well and can't be adapted
322
  auto tmp_shape = shape_;
12✔
323
  return xt::adapt(tmp_shape, {2});
24✔
324
}
325

326
std::string HexagonalMesh::bin_label(int bin) const
3,876✔
327
{
328
  HexMeshIndex ijkl = get_hexindices_from_bin(bin);
3,876✔
329
  int hr = hex_radius(ijkl);
3,876✔
330
  int ofr = offset_in_ring(ijkl, hr);
3,876✔
331
  return fmt::format(
332
    "Mesh Index ({}, {}, {})", hr, offset_in_ring(ijkl, hr), ijkl[3]);
7,752✔
333
}
334

335
double HexagonalMesh::frac_hexindex_in_direction(const Position& r, int i) const
2,701,056✔
336
{
337
  switch (i) {
2,701,056✔
338
  case 0:
675,264✔
339
    return (2.0 / 3.0 * -r.y) / this->size_;
675,264✔
340
  case 1:
675,264✔
341
    return (sqrt(3.0) / 3.0 * r.x + 1.0 / 3.0 * r.y) / this->size_;
675,264✔
342
  case 2:
675,264✔
343
    return -(2.0 / 3.0 * -r.y) / this->size_ -
675,264✔
344
           (sqrt(3.0) / 3.0 * r.x + 1.0 / 3.0 * r.y) / this->size_;
675,264✔
345
  case 3:
675,264✔
346
    // z is idx 1 in width_ and lower_left_ / upper_right_
347
    return (r.z - lower_left_[1]) / width_[1];
675,264✔
348
  }
NEW
349
  return -1;
×
350
}
351

NEW
352
int HexagonalMesh::get_index_in_direction(double r, int i) const
×
353
{
354
  // dummy placeholder
NEW
355
  return 0;
×
356
}
357

358
Position HexagonalMesh::get_position_from_hexindex(HexMeshIndex ijkl) const
1,553,076✔
359
{
360
  // return the cartesian position of center of a hexagon indexed by ijkl
361
  // Note thet we have to use the plane normals as basis vectors, as opposed to
362
  // the grid vectors (r, q, s)
363
  Position r;
1,553,076✔
364
  r.x = ijkl[0] * n0_[0] * size_ * sqrt(3) + ijkl[1] * n1_[0] * size_ * sqrt(3);
1,553,076✔
365
  r.y = ijkl[0] * n0_[1] * size_ * sqrt(3) + ijkl[1] * n1_[1] * size_ * sqrt(3);
1,553,076✔
366
  r.z = (ijkl[3] - 0.5) * width_[1] + lower_left_[1];
1,553,076✔
367

368
  return r;
1,553,076✔
369
}
370

371
HexagonalMesh::HexMeshIndex HexagonalMesh::get_hexindices(
675,264✔
372
  Position r, bool& in_mesh) const
373
{
374
  // return index of mesh element in hexes
375
  local_coords(r);
675,264✔
376
  vector<double> frac_cds {0, 0, 0, 0};
675,264✔
377

378
  // r coordinate
379
  frac_cds[0] = frac_hexindex_in_direction(r, 0);
675,264✔
380
  // q coordinate
381
  frac_cds[1] = frac_hexindex_in_direction(r, 1);
675,264✔
382
  // s coordinate
383
  frac_cds[2] = frac_hexindex_in_direction(r, 2);
675,264✔
384
  // z-coordinate
385
  frac_cds[3] = frac_hexindex_in_direction(r, 3);
675,264✔
386

387
  HexMeshIndex idx = round_frac_hexindex(frac_cds);
675,264✔
388
  // check if either index is out of bounds
389
  in_mesh = in_hexmesh(idx);
675,264✔
390
  return idx;
675,264✔
391
}
675,264✔
392

393
HexagonalMesh::HexMeshIndex HexagonalMesh::round_frac_hexindex(
675,264✔
394
  vector<double> frac_ijkl) const
395
{
396
  std::vector<double> diff(4);
675,264✔
397
  HexMeshIndex ijkl {0, 0, 0, 1};
675,264✔
398

399
  for (int i = 0; i < frac_ijkl.size(); ++i) {
3,376,320✔
400
    diff[i] = (std::abs(std::round(frac_ijkl[i]) - frac_ijkl[i]));
2,701,056✔
401
    ijkl[i] = std::round(frac_ijkl[i]);
2,701,056✔
402
  }
403
  if (diff[0] > diff[1] && diff[0] > diff[2]) {
675,264✔
404
    ijkl[0] = -ijkl[1] - ijkl[2];
185,124✔
405
  } else if (diff[1] > diff[2]) {
490,140✔
406
    ijkl[1] = -ijkl[0] - ijkl[2];
241,824✔
407
  } else {
408
    ijkl[2] = -ijkl[0] - ijkl[1];
248,316✔
409
  }
410
  // z-coordinate should be treated differently
411
  ijkl[3] = std::ceil(frac_ijkl[3]);
675,264✔
412

413
  return ijkl;
675,264✔
414
}
675,264✔
415

416
bool HexagonalMesh::in_hexmesh(HexMeshIndex& ijkl) const
731,988✔
417
{
418
  for (auto it = ijkl.begin(); it != std::prev(ijkl.end()); it++) {
2,252,520✔
419
    auto elem = *it;
1,804,812✔
420
    if (abs(elem) > hex_radius_)
1,804,812✔
421
      return false;
284,280✔
422
  }
423
  if (ijkl[3] > shape_[1] || ijkl[3] <= 0)
447,708✔
424
    return false;
100,560✔
425
  return true;
347,148✔
426
}
427

NEW
428
Position HexagonalMesh::sample_element(
×
429
  const HexMeshIndex& ijkl, uint64_t* seed) const
430
{
431
  // return random position inside mesh element
NEW
432
  Position rh = get_position_from_hexindex(ijkl);
×
433

NEW
434
  Position xy = sample_hexagon(seed) * size_;
×
435
  Position z(
NEW
436
    0, 0, uniform_distribution(-width_[1] / 2.0, width_[1] / 2.0, seed));
×
NEW
437
  return origin_ + rh + xy + z;
×
438
}
439

NEW
440
Position HexagonalMesh::sample_hexagon(uint64_t* seed) const
×
441
{
442
  // return a position inside hexagon at (0,0,0)
NEW
443
  double x = uniform_distribution(-sqrt(3) * 0.5, sqrt(3) * 0.5, seed);
×
NEW
444
  double y = uniform_distribution(-0.5, 1.0, seed);
×
445

NEW
446
  if (y > 0.5)
×
NEW
447
    if (y - 0.5 > 0.5 - std::abs(x) / sqrt(3)) { // reflect the point
×
NEW
448
      if (x > 0)
×
NEW
449
        x -= sqrt(3) * 0.5;
×
450
      else
NEW
451
        x += sqrt(3) * 0.5;
×
NEW
452
      y -= 1.5;
×
453
    }
NEW
454
  return Position(x, y, 0);
×
455
}
456

457
double HexagonalMesh::find_r_crossing(
675,264✔
458
  const Position& r, const Direction& u, double l) const
459
{
460
  // solve r.x^2 + r.y^2 == r0^2
461
  // x^2 + 2*s*u*x + s^2*u^2 + s^2*v^2+2*s*v*y + y^2 -r0^2 = 0
462
  // s^2 * (u^2 + v^2) + 2*s*(u*x+v*y) + x^2+y^2-r0^2 = 0
463

464
  const double r0 = r_encl_;
675,264✔
465
  if (r0 == 0.0)
675,264✔
NEW
466
    return INFTY;
×
467

468
  const double denominator = u.x * u.x + u.y * u.y;
675,264✔
469

470
  // Direction of flight is in z-direction. Will never intersect r.
471
  if (std::abs(denominator) < FP_PRECISION)
675,264✔
NEW
472
    return INFTY;
×
473

474
  // inverse of dominator to help the compiler to speed things up
475
  const double inv_denominator = 1.0 / denominator;
675,264✔
476

477
  const double p = (u.x * r.x + u.y * r.y) * inv_denominator;
675,264✔
478
  double c = r.x * r.x + r.y * r.y - r0 * r0;
675,264✔
479
  double D = p * p - c * inv_denominator;
675,264✔
480

481
  if (D < 0.0)
675,264✔
482
    return INFTY;
203,340✔
483

484
  D = std::sqrt(D);
471,924✔
485

486
  // the solution -p - D is always smaller as -p + D : Check this one first
487
  if (std::abs(c) <= RADIAL_MESH_TOL)
471,924✔
NEW
488
    return INFTY;
×
489

490
  if (-p - D > l)
471,924✔
491
    return -p - D;
116,808✔
492
  if (-p + D > l)
355,116✔
493
    return -p + D;
248,832✔
494

495
  return INFTY;
106,284✔
496
}
497

498
template<class T>
499
void HexagonalMesh::raytrace_mesh(
675,264✔
500
  Position r0, Position r1, const Direction& u, T tally) const
501
{
502
  // TODO: when c++-17 is available, use "if constexpr ()" to compile-time
503
  // enable/disable tally calls for now, T template type needs to provide both
504
  // surface and track methods, which might be empty. modern optimizing
505
  // compilers will (hopefully) eliminate the complete code (including
506
  // calculation of parameters) but for the future: be explicit
507

508
  // very similar to to the structured mesh raytrace_mesh but we cannot rely on
509
  // simply recomputing only a single distance. Also the distance to the outside
510
  // of the mesh is somewhat more complicated
511
  int iteration {0};
675,264✔
512
  // Compute the length of the entire track.
513
  double total_distance = (r1 - r0).norm();
675,264✔
514
  if (total_distance == 0.0 && settings::solver_type != SolverType::RANDOM_RAY)
675,264✔
NEW
515
    return;
×
516

517
  const int n = n_dimension_ * 2;
675,264✔
518

519
  // Flag if position is inside the mesh
520
  bool in_mesh;
521

522
  // Position is r = r0 + u * traveled_distance, start at r0
523
  double traveled_distance {0.0};
675,264✔
524

525
  // Calculate index of current cell. Offset the position a tiny bit in
526
  // direction of flight
527
  HexMeshIndex ijkl = get_hexindices(r0 + TINY_BIT * u, in_mesh);
675,264✔
528

529
  // If outside mesh and not on the way towards it (i.e. missing the
530
  // circumscribed cylinder) exit early.
531
  double dist_to_enclosing_cyl = find_r_crossing(r0, u, traveled_distance);
675,264✔
532

533
  if (!in_mesh) {
675,264✔
534
    if (dist_to_enclosing_cyl < INFTY &&
333,168✔
535
        dist_to_enclosing_cyl < total_distance) {
112,056✔
536
      traveled_distance = dist_to_enclosing_cyl;
31,992✔
537
    } else
538
      return;
301,176✔
539
  }
540

541
  // if track is very short, assume that it is completely inside one cell.
542
  // Only the current cell will score and no surfaces
543
  if (total_distance < 2 * TINY_BIT) {
374,088✔
NEW
544
    if (in_mesh) {
×
NEW
545
      tally.track(ijkl, 1.0);
×
546
    }
NEW
547
    return;
×
548
  }
549

550
  // translate start and end positions,
551
  // this needs to come after the get_hexindices call because it does its own
552
  // translation
553
  local_coords(r0);
374,088✔
554
  local_coords(r1);
374,088✔
555

556
  // Calculate initial distances to next surfaces in all three dimensions
557
  std::array<HexMeshDistance, 4> distances;
1,122,264✔
558
  for (int k = 0; k < n; ++k) {
1,870,440✔
559
    distances[k] = distance_to_hex_boundary(ijkl, k, r0, u, traveled_distance);
1,496,352✔
560
  }
561

562
  // Loop until r = r1 is eventually reached
563
  while (true) {
56,724✔
564
    iteration++;
430,812✔
565
    // std::cout << iteration << std::endl;
566
    //  find surface with minimal distance to current position
567
    const auto k =
430,812✔
568
      std::min_element(distances.begin(), distances.end()) - distances.begin();
430,812✔
569

570
    if (in_mesh) {
430,812✔
571
      // Tally track length delta since last step
572
      tally.track(ijkl,
347,148✔
573
        (std::min(distances[k].distance, total_distance) - traveled_distance) /
347,148✔
574
          total_distance);
575
    }
576

577
    // update position and leave, if we have reached end position
578
    traveled_distance = distances[k].distance;
430,812✔
579
    if (traveled_distance >= total_distance)
430,812✔
580
      return;
374,088✔
581

582
    if (in_mesh) {
56,724✔
583
      // If we have not reached r1, we have hit a surface. Tally outward current
584
      tally.surface(ijkl, k, distances[k].max_surface, false);
46,680✔
585
    }
586

587
    // Update cell and calculate distance to next surface in k-direction.
588
    ijkl = distances[k].next_index;
56,724✔
589
    // now the index has been updated recompute the distances - unfortunately
590
    // we have to do more than one again (as opposed to for cartesian mesh)
591

592
    if (k < 3) {
56,724✔
NEW
593
      for (int j = 0; j < 4; ++j) {
×
NEW
594
        distances[j] =
×
NEW
595
          distance_to_hex_boundary(ijkl, j, r0, u, traveled_distance);
×
596
      }
597
    } else {
598
      distances[3] =
56,724✔
599
        distance_to_hex_boundary(ijkl, 3, r0, u, traveled_distance);
56,724✔
600
      for (int j = 0; j < 3; ++j) {
226,896✔
601
        distances[j].next_index[3] = ijkl[3];
170,172✔
602
      }
603
    }
604

605
    // Check if we have left the interior of the mesh
606
    // Do this by getting new index
607
    in_mesh = in_hexmesh(ijkl);
56,724✔
608

609
    // If we are still inside the mesh, tally inward current for the next cell
610
    if (in_mesh)
56,724✔
611
      tally.surface(ijkl, k, !distances[k].max_surface, true);
5,052✔
612
  }
613
}
614

262,116✔
615
HexagonalMesh::HexMeshDistance HexagonalMesh::distance_to_hex_boundary(
616
  const HexMeshIndex& ijkl, int i, const Position& r0, const Direction& u,
617
  double l) const
618
{
619
  // Compute the distance to the element boundary of index i \in {0, ..., 6}
620
  // i==6 means z
621

622
  Position r = r0 - origin_;
623
  // Given the hex index - we now find the distance from r0 to the 0:q, 1:r, 2:s
624
  // plane(s) successively, given that the hex-center is given by the hexindex
625
  // quadruplet and hence also the planes.
626
  Position rh = get_position_from_hexindex(ijkl);
262,116✔
627
  // local position relative to hex center
628
  Position rloc = r0 + l * u - rh;
262,116✔
629
  HexMeshDistance d;
262,116✔
NEW
630
  d.next_index = ijkl;
×
631

632
  double dh = 0;
262,116✔
633

634
  if (i < 3) {
635
    if (std::abs(u[0]) + std::abs(u[1]) < FP_PRECISION)
636
      return d;
637
    switch (i) {
638
    case 0:
262,116✔
639
      if (abs(u.dot(n0_)) < FP_PRECISION) {
640
        return d;
641
      }
642
      dh = rh.dot(n0_) / u.dot(n0_);
262,116✔
643
      d.max_surface = n0_.dot(u) > 0;
644
      if (abs(ijkl[0]) > hex_radius_ + 1) {
645
        return d;
646
      }
262,116✔
647
      if (d.max_surface) {
648
        d.distance = dh + (this->r_ - r0).dot(this->n0_) / u.dot(this->n0_);
262,116✔
649
        d.next_index[0]++;
122,688✔
650
        d.next_index[2]--;
49,152✔
651
      } else {
22,056✔
652
        d.distance = dh + (-this->r_ - r0).dot(this->n0_) / u.dot(this->n0_);
653
        d.next_index[0]--;
100,632✔
654
        d.next_index[2]++;
655
      }
656
      break;
657
    case 1:
658
      if (abs(u.dot(n1_)) < FP_PRECISION) {
161,484✔
NEW
659
        return d;
×
NEW
660
      }
×
661
      dh = rh.dot(n1_) / u.dot(n1_);
NEW
662
      d.max_surface = n1_.dot(u) > 0;
×
663
      if (abs(ijkl[1]) > hex_radius_ + 1) {
664
        return d;
665
      }
666
      if (d.max_surface) {
667
        d.distance = dh + (-this->s_ - r0).dot(this->n1_) / u.dot(this->n1_);
668
        d.next_index[1]++;
161,484✔
669
        d.next_index[2]--;
161,484✔
670
      } else {
671
        d.distance = dh + (this->s_ - r0).dot(this->n1_) / u.dot(this->n1_);
672
        d.next_index[1]--;
484,452✔
673
        d.next_index[2]++;
807,420✔
674
      }
645,936✔
675
      break;
676
    case 2:
677
      if (abs(u.dot(n2_)) < FP_PRECISION) {
678
        return d;
30,756✔
679
      }
192,240✔
680
      dh = rh.dot(n2_) / u.dot(n2_);
681
      d.max_surface = n2_.dot(u) > 0;
682
      if (abs(ijkl[2]) > hex_radius_ + 1) {
192,240✔
683
        return d;
192,240✔
684
      }
685
      if (d.max_surface) {
192,240✔
686
        d.distance = dh + (this->q_ - r0).dot(this->n2_) / u.dot(this->n2_);
687
        d.next_index[0]--;
142,260✔
688
        d.next_index[1]++;
142,260✔
689
      } else {
690
        d.distance = dh + (-this->q_ - r0).dot(this->n2_) / u.dot(this->n2_);
691
        d.next_index[0]++;
692
        d.next_index[1]--;
693
      }
192,240✔
694
      break;
192,240✔
695
    }
161,484✔
696
  } else {
697
    if (std::abs(u[2]) < FP_PRECISION) {
30,756✔
698
      return d;
699
    }
23,124✔
700
    // Z-planes z-index has index 3 (nr 4) in ijkl.
701
    d.max_surface = (u[2] > 0);
702
    if (d.max_surface) {
703
      d.next_index[3]++;
30,756✔
704
      d.distance = ((lower_left_[1] + ijkl[3] * width_[1]) - r0[2]) / u[2];
705
    } else {
706
      d.next_index[3]--;
707
      d.distance =
30,756✔
NEW
708
        ((lower_left_[1] + (ijkl[3] - 1) * width_[1]) - r0[2]) / u[2];
×
NEW
709
    }
×
NEW
710
  }
×
711
  return d;
712
}
713

30,756✔
714
StructuredMesh::MeshDistance HexagonalMesh::distance_to_grid_boundary(
30,756✔
715
  const MeshIndex& ijk, int i, const Position& r0, const Direction& u,
123,024✔
716
  double l) const
92,268✔
717
{
718
  MeshDistance d;
719
  d.distance = INFTY;
720
  return d;
721
}
722

30,756✔
723
std::pair<vector<double>, vector<double>> HexagonalMesh::plot(
724
  Position plot_ll, Position plot_ur) const
725
{
30,756✔
726
  fatal_error("Plot of hexagonal Mesh not implemented");
2,832✔
727

728
  // Figure out which axes lie in the plane of the plot.
729
  array<vector<double>, 2> axis_lines;
413,148✔
730
  return {axis_lines[0], axis_lines[1]};
731
}
732

733
void HexagonalMesh::to_hdf5_inner(hid_t mesh_group) const
734
{
735
  write_dataset(mesh_group, "dimension", get_x_shape());
736
  write_dataset(mesh_group, "lower_left", lower_left_);
737
  write_dataset(mesh_group, "upper_right", upper_right_);
738
  write_dataset(mesh_group, "width", width_);
739
  // hex-mesh specifics
740
  write_dataset(mesh_group, "hex_count", hex_count_);
741
  write_dataset(mesh_group, "hex_radius", hex_radius_);
413,148✔
742
}
743

413,148✔
744
double HexagonalMesh::volume(const HexMeshIndex& ijkl) const
413,148✔
NEW
745
{
×
746
  double zdiff = (upper_right_[2] - lower_left_[2]) / shape_[2];
747
  return 6 * sqrt(3.0) * 0.25 * size_ * size_ * zdiff;
413,148✔
748
}
749

750
void HexagonalMesh::bins_crossed(Position r0, Position r1, const Direction& u,
751
  vector<int>& bins, vector<double>& lengths) const
752
{
753

413,148✔
754
  // Helper tally class.
755
  // stores a pointer to the mesh class and references to bins and lengths
756
  // parameters. Performs the actual tally through the track method.
757
  struct TrackAggregator {
413,148✔
758
    TrackAggregator(
759
      const HexagonalMesh* _mesh, vector<int>& _bins, vector<double>& _lengths)
760
      : mesh(_mesh), bins(_bins), lengths(_lengths)
761
    {}
413,148✔
762
    void surface(const HexMeshIndex& ijkl, int k, bool max, bool inward) const
763
    {}
413,148✔
764
    void track(const HexMeshIndex& ijkl, double l) const
210,480✔
765
    {
62,904✔
766
      bins.push_back(mesh->get_bin_from_hexindices(ijkl));
9,936✔
767
      lengths.push_back(l);
768
    }
200,544✔
769

770
    const HexagonalMesh* mesh;
771
    vector<int>& bins;
772
    vector<double>& lengths;
773
  };
212,604✔
NEW
774

×
NEW
775
  // Perform the mesh raytrace with the helper class.
×
776
  raytrace_mesh(r0, r1, u, TrackAggregator(this, bins, lengths));
NEW
777
}
×
778

779
void HexagonalMesh::surface_bins_crossed(
780
  Position r0, Position r1, const Direction& u, vector<int>& bins) const
781
{
782

783
  // Helper tally class.
212,604✔
784
  // stores a pointer to the mesh class and a reference to the bins parameter.
212,604✔
785
  // Performs the actual tally through the surface method.
786
  struct SurfaceAggregator {
787
    SurfaceAggregator(const HexagonalMesh* _mesh, vector<int>& _bins)
637,812✔
788
      : mesh(_mesh), bins(_bins)
1,063,020✔
789
    {}
850,416✔
790
    void surface(const HexMeshIndex& ijkl, int k, bool max, bool inward) const
791
    {
792
      int i_bin = 4 * 4 * mesh->get_bin_from_hexindices(ijkl) + 4 * k;
793
      if (max)
25,968✔
794
        i_bin += 2;
238,572✔
795
      if (inward)
796
        i_bin += 1;
797
      bins.push_back(i_bin);
238,572✔
798
    }
238,572✔
799
    void track(const HexMeshIndex& idx, double l) const {}
800

238,572✔
801
    const HexagonalMesh* mesh;
802
    vector<int>& bins;
204,888✔
803
  };
204,888✔
804

805
  // Perform the mesh raytrace with the helper class.
806
  raytrace_mesh(r0, r1, u, SurfaceAggregator(this, bins));
807
}
808

238,572✔
809
//==============================================================================
238,572✔
810
// Helper functions for the C API
212,604✔
811
//==============================================================================
812

25,968✔
813
int check_hex_mesh(int32_t index)
814
{
23,556✔
815
  if (index < 0 || index >= model::meshes.size()) {
816
    set_errmsg("Index in meshes array is out of bounds.");
817
    return OPENMC_E_OUT_OF_BOUNDS;
818
  }
25,968✔
819
  return 0;
820
}
821
// This is identical to the one in mesh.cpp
822
template<class T>
25,968✔
NEW
823
int check_mesh_type(int32_t index)
×
NEW
824
{
×
NEW
825
  if (int err = check_hex_mesh(index))
×
826
    return err;
827

828
  T* mesh = dynamic_cast<T*>(model::meshes[index].get());
25,968✔
829
  if (!mesh) {
25,968✔
830
    set_errmsg("This function is not valid for input mesh.");
103,872✔
831
    return OPENMC_E_INVALID_TYPE;
77,904✔
832
  }
833
  return 0;
834
}
835

836
// This is identical to the one in mesh.cpp
837
template<class T>
25,968✔
838
bool is_mesh_type(int32_t index)
839
{
840
  T* mesh = dynamic_cast<T*>(model::meshes[index].get());
25,968✔
841
  return mesh;
2,220✔
842
}
843

844
//! Get the dimension of a hexagonal mesh
845
extern "C" int openmc_hexagonal_mesh_get_dimension(
1,553,076✔
846
  int32_t index, int** dims, int* n)
847
{
848
  if (int err = check_mesh_type<HexagonalMesh>(index))
849
    return err;
850
  HexagonalMesh* mesh =
851
    dynamic_cast<HexagonalMesh*>(model::meshes[index].get());
852
  *dims = mesh->shape_.data();
1,553,076✔
853
  *n = mesh->n_dimension_;
854
  return 0;
855
}
856

1,553,076✔
857
//! Set the dimension of a hexagonal mesh
858
extern "C" int openmc_hexagonal_mesh_set_dimension(
1,553,076✔
859
  int32_t index, int n, const int* dims)
1,553,076✔
860
{
1,553,076✔
861
  if (int err = check_mesh_type<HexagonalMesh>(index))
862
    return err;
1,553,076✔
863
  HexagonalMesh* mesh =
864
    dynamic_cast<HexagonalMesh*>(model::meshes[index].get());
1,553,076✔
865

1,122,264✔
NEW
866
  // Copy dimension
×
867
  mesh->n_dimension_ = n;
1,122,264✔
868
  std::copy(dims, dims + n, mesh->shape_.begin());
374,088✔
869

374,088✔
870
  // TODO: incorporate this bit into method in HexagonalMesh that can be called
374,088✔
871
  // from here and from constructor
NEW
872
  mesh->hex_radius_ = (mesh->shape_[0] - 1) / 2;
×
NEW
873
  if (mesh->hex_radius_ == 0)
×
NEW
874
    mesh->hex_count_ = 1;
×
NEW
875
  else
×
876
    mesh->hex_count_ = 1 + 3 * (mesh->hex_radius_ + 1) * mesh->hex_radius_;
NEW
877

×
NEW
878
  return 0;
×
NEW
879
}
×
NEW
880
//! Get the hexagonal mesh parameters
×
881
extern "C" int openmc_hexagonal_mesh_get_params(
NEW
882
  int32_t index, double** ll, double** ur, double** width, int* n)
×
NEW
883
{
×
NEW
884
  if (int err = check_mesh_type<HexagonalMesh>(index))
×
885
    return err;
NEW
886
  HexagonalMesh* m = dynamic_cast<HexagonalMesh*>(model::meshes[index].get());
×
887

374,088✔
888
  if (m->lower_left_.dimension() == 0) {
374,088✔
889
    set_errmsg("Mesh parameters have not been set.");
374,088✔
890
    return OPENMC_E_ALLOCATE;
NEW
891
  }
×
NEW
892

×
NEW
893
  *ll = m->lower_left_.data();
×
NEW
894
  *ur = m->upper_right_.data();
×
895
  *width = m->width_.data();
NEW
896
  *n = m->n_dimension_;
×
NEW
897
  return 0;
×
NEW
898
}
×
NEW
899

×
900
//! Set the hexagonal mesh parameters
NEW
901
extern "C" int openmc_hexagonal_mesh_set_params(
×
NEW
902
  int32_t index, int n, const double* ll, const double* ur, const double* width)
×
NEW
903
{
×
904
  if (int err = check_mesh_type<HexagonalMesh>(index))
NEW
905
    return err;
×
906
  HexagonalMesh* m = dynamic_cast<HexagonalMesh*>(model::meshes[index].get());
374,088✔
907

374,088✔
908
  if (m->n_dimension_ == -1) {
374,088✔
909
    set_errmsg("Need to set mesh dimension before setting parameters.");
NEW
910
    return OPENMC_E_UNASSIGNED;
×
NEW
911
  }
×
NEW
912

×
NEW
913
  vector<std::size_t> shape = {static_cast<std::size_t>(n)};
×
914
  if (ll && ur) {
NEW
915
    m->lower_left_ = xt::adapt(ll, n, xt::no_ownership(), shape);
×
NEW
916
    m->upper_right_ = xt::adapt(ur, n, xt::no_ownership(), shape);
×
NEW
917
    m->width_ = (m->upper_right_ - m->lower_left_) / m->get_x_shape();
×
NEW
918
  } else if (ll && width) {
×
919
    m->lower_left_ = xt::adapt(ll, n, xt::no_ownership(), shape);
NEW
920
    m->width_ = xt::adapt(width, n, xt::no_ownership(), shape);
×
NEW
921
    m->upper_right_ = m->lower_left_ + m->get_x_shape() * m->width_;
×
NEW
922
  } else if (ur && width) {
×
923
    m->upper_right_ = xt::adapt(ur, n, xt::no_ownership(), shape);
NEW
924
    m->width_ = xt::adapt(width, n, xt::no_ownership(), shape);
×
925
    m->lower_left_ = m->upper_right_ - m->get_x_shape() * m->width_;
926
  } else {
927
    set_errmsg("At least two parameters must be specified.");
430,812✔
NEW
928
    return OPENMC_E_INVALID_ARGUMENT;
×
929
  }
930

931
  // TODO: incorporate this into method in HexagonalMesh that can be called from
430,812✔
932
  // here and from constructor
430,812✔
933

214,380✔
934
  // Set material volumes etc.
214,380✔
935
  m->size_ = m->width_[0] / sqrt(3.0);
936
  m->init_plane_normals();
216,432✔
937
  m->scale_grid_vectors(m->size_);
216,432✔
938

216,432✔
939
  m->volume_frac_ = 1.0 / m->shape_[1] / m->hex_count_;
940
  m->element_volume_ =
941
    m->width_[1] * m->width_[0] * m->width_[0] * sqrt(3) * 0.5;
430,812✔
942

943
  return 0;
NEW
944
}
×
945

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