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

openmc-dev / openmc / 20163164157

12 Dec 2025 10:01AM UTC coverage: 81.832% (-0.04%) from 81.872%
20163164157

Pull #3279

github

web-flow
Merge c32841e75 into bbfa18d72
Pull Request #3279: Hexagonal mesh model

17271 of 24053 branches covered (71.8%)

Branch coverage included in aggregate %.

602 of 876 new or added lines in 9 files covered. (68.72%)

1836 existing lines in 44 files now uncovered.

55648 of 65055 relevant lines covered (85.54%)

42918761.78 hits per line

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

58.8
/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 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
  // size of hex is defined as the radius of the circumscribed circle
NEW
199
  size_ = width_[0] / sqrt(3.0);
×
200

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

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

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

225
const std::string HexagonalMesh::mesh_type = "hexagonal";
226

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

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

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

247
  n0_ /= n0_.norm();
16✔
248
  n1_ /= n1_.norm();
16✔
249
  n2_ /= n2_.norm();
16✔
250

251
  return 0;
16✔
252
}
253

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

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

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

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

NEW
287
int HexagonalMesh::get_bin(Position r) const
×
288
{
289
  // Determine indices
290
  bool in_mesh;
NEW
291
  HexMeshIndex ijkl = get_hexindices(r, in_mesh);
×
NEW
292
  if (!in_mesh)
×
NEW
293
    return -1;
×
294

295
  // Convert indices to bin
NEW
296
  return get_bin_from_hexindices(ijkl);
×
297
}
298

299
int32_t HexagonalMesh::offset_in_ring(const HexMeshIndex& ijkl, int32_t r) const
638,253✔
300
{
301
  // find the offset within a ring
302
  if (r == 0)
638,253✔
303
    return 0;
374✔
304
  HexMeshIndex i {ijkl};
637,879✔
305
  HexMeshIndex corner {r, 0, -r, 0};
637,879✔
306

307
  int segment {0};
637,879✔
308
  if (abs(i[2]) >= abs(i[1]) && abs(i[2]) >= abs(i[0])) {
637,879✔
309
    if (i[2] < 0)
363,022✔
310
      segment = 0;
178,101✔
311
    else
312
      segment = 3;
184,921✔
313
  } else if (abs(i[1]) >= abs(i[0])) {
274,857✔
314
    if (i[1] > 0)
213,620✔
315
      segment = 1;
109,087✔
316
    else
317
      segment = 4;
104,533✔
318
  } else {
319
    if (i[0] < 0)
61,237✔
320
      segment = 2;
30,470✔
321
    else
322
      segment = 5;
30,767✔
323
  }
324

325
  for (int k = 0; k < segment; k++)
1,934,636✔
326
    corner = rotate_hexindex(corner);
1,296,757✔
327

328
  int32_t hexd = hex_distance(corner, i);
637,879✔
329
  int ii = r * segment + hexd;
637,879✔
330
  return ii;
637,879✔
331
}
332

333
HexagonalMesh::HexMeshIndex HexagonalMesh::rotate_hexindex(
1,305,172✔
334
  const HexMeshIndex& ijkl) const
335
{
336
  HexMeshIndex new_ijkl {ijkl};
1,305,172✔
337
  new_ijkl[0] = -ijkl[1];
1,305,172✔
338
  new_ijkl[1] = -ijkl[2];
1,305,172✔
339
  new_ijkl[2] = -ijkl[0];
1,305,172✔
340
  return new_ijkl;
1,305,172✔
341
}
342

343
HexagonalMesh::HexMeshIndex HexagonalMesh::get_hexindices_from_bin(
3,553✔
344
  const int32_t bin) const
345
{
346
  std::array<HexMeshIndex, 6> directions;
347
  directions[0] = {-1, 1, 0, 0};
3,553✔
348
  directions[1] = {-1, 0, 1, 0};
3,553✔
349
  directions[2] = {0, -1, 1, 0};
3,553✔
350
  directions[3] = {1, -1, 0, 0};
3,553✔
351
  directions[4] = {1, 0, -1, 0};
3,553✔
352
  directions[5] = {0, 1, -1, 0};
3,553✔
353

354
  HexMeshIndex ijkl = {0, 0, 0, 1};
3,553✔
355
  ijkl[3] = (int)floor(bin / hex_count_) + 1;
3,553✔
356
  int spiral_index = bin % hex_count_;
3,553✔
357
  if (spiral_index == 0) {
3,553✔
358
    return ijkl;
187✔
359
  }
360
  int ring = (int)floor((sqrt(12 * spiral_index - 3) + 3) / 6);
3,366✔
361
  int start_of_ring = (1 + 3 * ring * (ring - 1));
3,366✔
362

363
  if (ring > 0) {
3,366!
364
    int segment = (spiral_index - start_of_ring) / ring;
3,366✔
365

366
    ijkl[0] = ring;
3,366✔
367
    ijkl[2] = -ring;
3,366✔
368

369
    for (int k = 0; k < segment; k++)
11,781✔
370
      ijkl = rotate_hexindex(ijkl);
8,415✔
371

372
    for (int k = 0; k < spiral_index - start_of_ring - ring * segment; k++) {
4,488✔
373
      for (int l = 0; l < ijkl.size(); l++)
5,610✔
374
        ijkl[l] = ijkl[l] + directions[segment][l];
4,488✔
375
    }
376
  }
377
  return ijkl;
3,366✔
378
}
379

380
int HexagonalMesh::n_bins() const
32✔
381
{
382
  return hex_count_ * shape_[1];
32✔
383
}
384

385
int HexagonalMesh::n_surface_bins() const
16✔
386
{
387
  return 4 * 4 * n_bins();
16✔
388
}
389

390
xt::xtensor<int, 1> HexagonalMesh::get_x_shape() const
11✔
391
{
392
  // because method is const, shape_ is const as well and can't be adapted
393
  auto tmp_shape = shape_;
11✔
394
  return xt::adapt(tmp_shape, {2});
22✔
395
}
396

397
std::string HexagonalMesh::bin_label(int bin) const
3,553✔
398
{
399
  HexMeshIndex ijkl = get_hexindices_from_bin(bin);
3,553✔
400
  int hr = hex_radius(ijkl);
3,553✔
401
  int ofr = offset_in_ring(ijkl, hr);
3,553✔
402
  return fmt::format(
403
    "Mesh Index ({}, {}, {})", hr, offset_in_ring(ijkl, hr), ijkl[3]);
7,106✔
404
}
405

406
double HexagonalMesh::frac_hexindex_in_direction(const Position& r, int i) const
2,480,676✔
407
{
408
  switch (i) {
2,480,676!
409
  case 0:
620,169✔
410
    return (2.0 / 3.0 * -r.y) / this->size_;
620,169✔
411
  case 1:
620,169✔
412
    return (sqrt(3.0) / 3.0 * r.x + 1.0 / 3.0 * r.y) / this->size_;
620,169✔
413
  case 2:
620,169✔
414
    return -(2.0 / 3.0 * -r.y) / this->size_ -
620,169✔
415
           (sqrt(3.0) / 3.0 * r.x + 1.0 / 3.0 * r.y) / this->size_;
620,169✔
416
  case 3:
620,169✔
417
    // z is idx 1 in width_ and lower_left_ / upper_right_
418
    return (r.z - lower_left_[1]) / width_[1];
620,169✔
419
  }
NEW
420
  return -1;
×
421
}
422

NEW
423
int HexagonalMesh::get_index_in_direction(double r, int i) const
×
424
{
425
  // dummy placeholder
NEW
426
  return 0;
×
427
}
428

429
Position HexagonalMesh::get_position_from_hexindex(HexMeshIndex ijkl) const
3,598,397✔
430
{
431
  // return the cartesian position of center of a hexagon indexed by ijkl
432
  // Note thet we have to use the plane normals as basis vectors, as opposed to
433
  // the grid vectors (r, q, s)
434
  Position r;
3,598,397✔
435
  r.x = ijkl[0] * n0_[0] * size_ * sqrt(3) + ijkl[1] * n1_[0] * size_ * sqrt(3);
3,598,397✔
436
  r.y = ijkl[0] * n0_[1] * size_ * sqrt(3) + ijkl[1] * n1_[1] * size_ * sqrt(3);
3,598,397✔
437
  r.z = (ijkl[3] - 0.5) * width_[1] + lower_left_[1];
3,598,397✔
438

439
  return r;
3,598,397✔
440
}
441

442
HexagonalMesh::HexMeshIndex HexagonalMesh::get_hexindices(
620,169✔
443
  Position r, bool& in_mesh) const
444
{
445
  // return index of mesh element in hexes
446
  local_coords(r);
620,169✔
447
  vector<double> frac_cds {0, 0, 0, 0};
620,169✔
448

449
  // r coordinate
450
  frac_cds[0] = frac_hexindex_in_direction(r, 0);
620,169✔
451
  // q coordinate
452
  frac_cds[1] = frac_hexindex_in_direction(r, 1);
620,169✔
453
  // s coordinate
454
  frac_cds[2] = frac_hexindex_in_direction(r, 2);
620,169✔
455
  // z-coordinate
456
  frac_cds[3] = frac_hexindex_in_direction(r, 3);
620,169✔
457

458
  HexMeshIndex idx = round_frac_hexindex(frac_cds);
620,169✔
459
  // check if either index is out of bounds
460
  in_mesh = in_hexmesh(idx);
620,169✔
461
  return idx;
620,169✔
462
}
620,169✔
463

464
HexagonalMesh::HexMeshIndex HexagonalMesh::round_frac_hexindex(
620,169✔
465
  vector<double> frac_ijkl) const
466
{
467
  std::vector<double> diff(4);
620,169✔
468
  HexMeshIndex ijkl {0, 0, 0, 1};
620,169✔
469

470
  for (int i = 0; i < frac_ijkl.size(); ++i) {
3,100,845✔
471
    diff[i] = (std::abs(std::round(frac_ijkl[i]) - frac_ijkl[i]));
2,480,676✔
472
    ijkl[i] = std::round(frac_ijkl[i]);
2,480,676✔
473
  }
474
  if (diff[0] > diff[1] && diff[0] > diff[2]) {
620,169✔
475
    ijkl[0] = -ijkl[1] - ijkl[2];
165,814✔
476
  } else if (diff[1] > diff[2]) {
454,355✔
477
    ijkl[1] = -ijkl[0] - ijkl[2];
230,208✔
478
  } else {
479
    ijkl[2] = -ijkl[0] - ijkl[1];
224,147✔
480
  }
481
  // z-coordinate should be treated differently
482
  ijkl[3] = std::ceil(frac_ijkl[3]);
620,169✔
483

484
  return ijkl;
620,169✔
485
}
620,169✔
486

487
bool HexagonalMesh::in_hexmesh(HexMeshIndex& ijkl) const
1,215,082✔
488
{
489
  for (auto it = ijkl.begin(); it != std::prev(ijkl.end()); it++) {
3,781,305✔
490
    auto elem = *it;
3,027,816✔
491
    if (abs(elem) > hex_radius_)
3,027,816✔
492
      return false;
461,593✔
493
  }
494
  if (ijkl[3] > shape_[1] || ijkl[3] <= 0)
753,489✔
495
    return false;
123,123✔
496
  return true;
630,366✔
497
}
498

NEW
499
Position HexagonalMesh::sample_element(
×
500
  const HexMeshIndex& ijkl, uint64_t* seed) const
501
{
502
  // return random position inside mesh element
NEW
503
  Position rh = get_position_from_hexindex(ijkl);
×
504

NEW
505
  Position xy = sample_hexagon(seed) * size_;
×
506
  Position z(
NEW
507
    0, 0, uniform_distribution(-width_[1] / 2.0, width_[1] / 2.0, seed));
×
NEW
508
  return origin_ + rh + xy + z;
×
509
}
510

NEW
511
Position HexagonalMesh::sample_hexagon(uint64_t* seed) const
×
512
{
513
  // return a position inside hexagon at (0,0,0)
NEW
514
  double x = uniform_distribution(-sqrt(3) * 0.5, sqrt(3) * 0.5, seed);
×
NEW
515
  double y = uniform_distribution(-0.5, 1.0, seed);
×
516

NEW
517
  if (y > 0.5)
×
NEW
518
    if (y - 0.5 > 0.5 - std::abs(x) / sqrt(3)) { // reflect the point
×
NEW
519
      if (x > 0)
×
NEW
520
        x -= sqrt(3) * 0.5;
×
521
      else
NEW
522
        x += sqrt(3) * 0.5;
×
NEW
523
      y -= 1.5;
×
524
    }
NEW
525
  return Position(x, y, 0);
×
526
}
527

528
double HexagonalMesh::find_r_crossing(
620,169✔
529
  const Position& r, const Direction& u, double l) const
530
{
531
  // solve r.x^2 + r.y^2 == r0^2
532
  // x^2 + 2*s*u*x + s^2*u^2 + s^2*v^2+2*s*v*y + y^2 -r0^2 = 0
533
  // s^2 * (u^2 + v^2) + 2*s*(u*x+v*y) + x^2+y^2-r0^2 = 0
534

535
  const double r0 = r_encl_;
620,169✔
536
  if (r0 == 0.0)
620,169!
NEW
537
    return INFTY;
×
538

539
  const double denominator = u.x * u.x + u.y * u.y;
620,169✔
540

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

545
  // inverse of dominator to help the compiler to speed things up
546
  const double inv_denominator = 1.0 / denominator;
620,169✔
547

548
  const double p = (u.x * r.x + u.y * r.y) * inv_denominator;
620,169✔
549
  double c = r.x * r.x + r.y * r.y - r0 * r0;
620,169✔
550
  double D = p * p - c * inv_denominator;
620,169✔
551

552
  if (D < 0.0)
620,169✔
553
    return INFTY;
187,550✔
554

555
  D = std::sqrt(D);
432,619✔
556

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

561
  if (-p - D > l)
432,619✔
562
    return -p - D;
106,667✔
563
  if (-p + D > l)
325,952✔
564
    return -p + D;
227,436✔
565

566
  return INFTY;
98,516✔
567
}
568

569
template<class T>
570
void HexagonalMesh::raytrace_mesh(
620,169✔
571
  Position r0, Position r1, const Direction& u, T tally) const
572
{
573
  // TODO: when c++-17 is available, use "if constexpr ()" to compile-time
574
  // enable/disable tally calls for now, T template type needs to provide both
575
  // surface and track methods, which might be empty. modern optimizing
576
  // compilers will (hopefully) eliminate the complete code (including
577
  // calculation of parameters) but for the future: be explicit
578

579
  // very similar to to the structured mesh raytrace_mesh but we cannot rely on
580
  // simply recomputing only a single distance. Also the distance to the outside
581
  // of the mesh is somewhat more complicated
582
  int iteration {0};
620,169✔
583
  // Compute the length of the entire track.
584
  double total_distance = (r1 - r0).norm();
620,169✔
585
  if (total_distance == 0.0 && settings::solver_type != SolverType::RANDOM_RAY)
620,169!
NEW
586
    return;
×
587

588
  const int n = n_dimension_ * 2;
620,169✔
589

590
  // Flag if position is inside the mesh
591
  bool in_mesh;
592

593
  // Position is r = r0 + u * traveled_distance, start at r0
594
  double traveled_distance {0.0};
620,169✔
595

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

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

604
  if (!in_mesh) {
620,169✔
605
    if (dist_to_enclosing_cyl < INFTY &&
305,371✔
606
        dist_to_enclosing_cyl < total_distance) {
102,311✔
607
      traveled_distance = dist_to_enclosing_cyl;
29,084✔
608
    } else
609
      return;
276,287✔
610
  }
611

612
  // if track is very short, assume that it is completely inside one cell.
613
  // Only the current cell will score and no surfaces
614
  if (total_distance < 2 * TINY_BIT) {
343,882!
NEW
615
    if (in_mesh) {
×
NEW
616
      tally.track(ijkl, 1.0);
×
617
    }
NEW
618
    return;
×
619
  }
620

621
  // translate start and end positions,
622
  // this needs to come after the get_hexindices call because it does its own
623
  // translation
624
  local_coords(r0);
343,882✔
625
  local_coords(r1);
343,882✔
626

627
  // Calculate initial distances to next surfaces in all three dimensions
628
  std::array<HexMeshDistance, 4> distances;
1,031,646✔
629
  for (int k = 0; k < n; ++k) {
1,719,410✔
630
    distances[k] = distance_to_hex_boundary(ijkl, k, r0, u, traveled_distance);
1,375,528✔
631
  }
632

633
  // Loop until r = r1 is eventually reached
634
  while (true) {
594,913✔
635
    iteration++;
938,795✔
636
    // std::cout << iteration << std::endl;
637
    //  find surface with minimal distance to current position
638
    const auto k =
938,795✔
639
      std::min_element(distances.begin(), distances.end()) - distances.begin();
938,795✔
640

641
    if (in_mesh) {
938,795✔
642
      // Tally track length delta since last step
643
      tally.track(ijkl,
630,366✔
644
        (std::min(distances[k].distance, total_distance) - traveled_distance) /
630,366✔
645
          total_distance);
646
    }
647

648
    // update position and leave, if we have reached end position
649
    traveled_distance = distances[k].distance;
938,795✔
650
    if (traveled_distance >= total_distance)
938,795✔
651
      return;
343,882✔
652

653
    if (in_mesh) {
594,913✔
654
      // If we have not reached r1, we have hit a surface. Tally outward current
655
      tally.surface(ijkl, k, distances[k].max_surface, false);
413,369✔
656
    }
657

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

663
    if (k < 3) {
594,913✔
664
      for (int j = 0; j < 4; ++j) {
2,713,260✔
665
        distances[j] =
2,170,608✔
666
          distance_to_hex_boundary(ijkl, j, r0, u, traveled_distance);
2,170,608✔
667
      }
668
    } else {
669
      distances[3] =
52,261✔
670
        distance_to_hex_boundary(ijkl, 3, r0, u, traveled_distance);
52,261✔
671
      for (int j = 0; j < 3; ++j) {
209,044✔
672
        distances[j].next_index[3] = ijkl[3];
156,783✔
673
      }
674
    }
675

676
    // Check if we have left the interior of the mesh
677
    // Do this by getting new index
678
    in_mesh = in_hexmesh(ijkl);
594,913✔
679

680
    // If we are still inside the mesh, tally inward current for the next cell
681
    if (in_mesh)
594,913✔
682
      tally.surface(ijkl, k, !distances[k].max_surface, true);
315,568✔
683
  }
684
}
685

686
HexagonalMesh::HexMeshDistance HexagonalMesh::distance_to_hex_boundary(
3,598,397✔
687
  const HexMeshIndex& ijkl, int i, const Position& r0, const Direction& u,
688
  double l) const
689
{
690
  // Compute the distance to the element boundary of index i \in {0, ..., 3}
691
  // i==3 means z
692

693
  Position r = r0 - origin_;
3,598,397✔
694
  // Given the hex index - we now find the distance from r0 to the 0:q, 1:r, 2:s
695
  // plane(s) successively, given that the hex-center is given by the hexindex
696
  // quadruplet and hence also the planes.
697
  Position rh = get_position_from_hexindex(ijkl);
3,598,397✔
698
  // local position relative to hex center
699
  Position rloc = r0 + l * u - rh;
3,598,397✔
700
  HexMeshDistance d;
3,598,397✔
701
  d.next_index = ijkl;
3,598,397✔
702

703
  double dh = 0;
3,598,397✔
704

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

NEW
785
StructuredMesh::MeshDistance HexagonalMesh::distance_to_grid_boundary(
×
786
  const MeshIndex& ijk, int i, const Position& r0, const Direction& u,
787
  double l) const
788
{
NEW
789
  MeshDistance d;
×
NEW
790
  d.distance = INFTY;
×
NEW
791
  return d;
×
792
}
793

NEW
794
std::pair<vector<double>, vector<double>> HexagonalMesh::plot(
×
795
  Position plot_ll, Position plot_ur) const
796
{
NEW
797
  fatal_error("Plot of hexagonal Mesh not implemented");
×
798

799
  // Figure out which axes lie in the plane of the plot.
800
  array<vector<double>, 2> axis_lines;
801
  return {axis_lines[0], axis_lines[1]};
802
}
803

804
void HexagonalMesh::to_hdf5_inner(hid_t mesh_group) const
11✔
805
{
806
  write_dataset(mesh_group, "dimension", get_x_shape());
11✔
807
  write_dataset(mesh_group, "lower_left", lower_left_);
11✔
808
  write_dataset(mesh_group, "upper_right", upper_right_);
11✔
809
  write_dataset(mesh_group, "width", width_);
11✔
810
  // hex-mesh specifics
811
  write_dataset(mesh_group, "hex_count", hex_count_);
11✔
812
  write_dataset(mesh_group, "hex_radius", hex_radius_);
11✔
813
}
11✔
814

NEW
815
double HexagonalMesh::volume(const HexMeshIndex& ijkl) const
×
816
{
NEW
817
  double zdiff = (upper_right_[2] - lower_left_[2]) / shape_[2];
×
NEW
818
  return 6 * sqrt(3.0) * 0.25 * size_ * size_ * zdiff;
×
819
}
820

821
void HexagonalMesh::bins_crossed(Position r0, Position r1, const Direction& u,
378,983✔
822
  vector<int>& bins, vector<double>& lengths) const
823
{
824

825
  // Helper tally class.
826
  // stores a pointer to the mesh class and references to bins and lengths
827
  // parameters. Performs the actual tally through the track method.
828
  struct TrackAggregator {
829
    TrackAggregator(
378,983✔
830
      const HexagonalMesh* _mesh, vector<int>& _bins, vector<double>& _lengths)
831
      : mesh(_mesh), bins(_bins), lengths(_lengths)
378,983✔
832
    {}
378,983✔
833
    void surface(const HexMeshIndex& ijkl, int k, bool max, bool inward) const
361,592✔
834
    {}
361,592✔
835
    void track(const HexMeshIndex& ijkl, double l) const
339,460✔
836
    {
837
      bins.push_back(mesh->get_bin_from_hexindices(ijkl));
339,460✔
838
      lengths.push_back(l);
339,460✔
839
    }
339,460✔
840

841
    const HexagonalMesh* mesh;
842
    vector<int>& bins;
843
    vector<double>& lengths;
844
  };
845

846
  // Perform the mesh raytrace with the helper class.
847
  raytrace_mesh(r0, r1, u, TrackAggregator(this, bins, lengths));
378,983✔
848
}
378,983✔
849

850
void HexagonalMesh::surface_bins_crossed(
241,186✔
851
  Position r0, Position r1, const Direction& u, vector<int>& bins) const
852
{
853

854
  // Helper tally class.
855
  // stores a pointer to the mesh class and a reference to the bins parameter.
856
  // Performs the actual tally through the surface method.
857
  struct SurfaceAggregator {
858
    SurfaceAggregator(const HexagonalMesh* _mesh, vector<int>& _bins)
241,186✔
859
      : mesh(_mesh), bins(_bins)
241,186✔
860
    {}
241,186✔
861
    void surface(const HexMeshIndex& ijkl, int k, bool max, bool inward) const
367,345✔
862
    {
863
      int i_bin = 4 * 4 * mesh->get_bin_from_hexindices(ijkl) + 4 * k;
367,345✔
864
      if (max)
367,345✔
865
        i_bin += 2;
183,216✔
866
      if (inward)
367,345✔
867
        i_bin += 1;
162,624✔
868
      bins.push_back(i_bin);
367,345✔
869
    }
367,345✔
870
    void track(const HexMeshIndex& idx, double l) const {}
290,906✔
871

872
    const HexagonalMesh* mesh;
873
    vector<int>& bins;
874
  };
875

876
  // Perform the mesh raytrace with the helper class.
877
  raytrace_mesh(r0, r1, u, SurfaceAggregator(this, bins));
241,186✔
878
}
241,186✔
879

880
//==============================================================================
881
// Helper functions for the C API
882
//==============================================================================
883

NEW
884
int check_hex_mesh(int32_t index)
×
885
{
NEW
886
  if (index < 0 || index >= model::meshes.size()) {
×
NEW
887
    set_errmsg("Index in meshes array is out of bounds.");
×
NEW
888
    return OPENMC_E_OUT_OF_BOUNDS;
×
889
  }
NEW
890
  return 0;
×
891
}
892
// This is identical to the one in mesh.cpp
893
template<class T>
NEW
894
int check_mesh_type(int32_t index)
×
895
{
NEW
896
  if (int err = check_hex_mesh(index))
×
NEW
897
    return err;
×
898

NEW
899
  T* mesh = dynamic_cast<T*>(model::meshes[index].get());
×
NEW
900
  if (!mesh) {
×
NEW
901
    set_errmsg("This function is not valid for input mesh.");
×
NEW
902
    return OPENMC_E_INVALID_TYPE;
×
903
  }
NEW
904
  return 0;
×
905
}
906

907
// This is identical to the one in mesh.cpp
908
template<class T>
909
bool is_mesh_type(int32_t index)
910
{
911
  T* mesh = dynamic_cast<T*>(model::meshes[index].get());
912
  return mesh;
913
}
914

915
//! Get the dimension of a hexagonal mesh
NEW
916
extern "C" int openmc_hexagonal_mesh_get_dimension(
×
917
  int32_t index, int** dims, int* n)
918
{
NEW
919
  if (int err = check_mesh_type<HexagonalMesh>(index))
×
NEW
920
    return err;
×
921
  HexagonalMesh* mesh =
NEW
922
    dynamic_cast<HexagonalMesh*>(model::meshes[index].get());
×
NEW
923
  *dims = mesh->shape_.data();
×
NEW
924
  *n = mesh->n_dimension_;
×
NEW
925
  return 0;
×
926
}
927

928
//! Set the dimension of a hexagonal mesh
NEW
929
extern "C" int openmc_hexagonal_mesh_set_dimension(
×
930
  int32_t index, int n, const int* dims)
931
{
NEW
932
  if (int err = check_mesh_type<HexagonalMesh>(index))
×
NEW
933
    return err;
×
934
  HexagonalMesh* mesh =
NEW
935
    dynamic_cast<HexagonalMesh*>(model::meshes[index].get());
×
936

937
  // Copy dimension
NEW
938
  mesh->n_dimension_ = n;
×
NEW
939
  std::copy(dims, dims + n, mesh->shape_.begin());
×
940

941
  // TODO: incorporate this bit into method in HexagonalMesh that can be called
942
  // from here and from constructor
NEW
943
  mesh->hex_radius_ = (mesh->shape_[0] - 1) / 2;
×
NEW
944
  if (mesh->hex_radius_ == 0)
×
NEW
945
    mesh->hex_count_ = 1;
×
946
  else
NEW
947
    mesh->hex_count_ = 1 + 3 * (mesh->hex_radius_ + 1) * mesh->hex_radius_;
×
948

NEW
949
  return 0;
×
950
}
951
//! Get the hexagonal mesh parameters
NEW
952
extern "C" int openmc_hexagonal_mesh_get_params(
×
953
  int32_t index, double** ll, double** ur, double** width, int* n)
954
{
NEW
955
  if (int err = check_mesh_type<HexagonalMesh>(index))
×
NEW
956
    return err;
×
NEW
957
  HexagonalMesh* m = dynamic_cast<HexagonalMesh*>(model::meshes[index].get());
×
958

NEW
959
  if (m->lower_left_.dimension() == 0) {
×
NEW
960
    set_errmsg("Mesh parameters have not been set.");
×
NEW
961
    return OPENMC_E_ALLOCATE;
×
962
  }
963

NEW
964
  *ll = m->lower_left_.data();
×
NEW
965
  *ur = m->upper_right_.data();
×
NEW
966
  *width = m->width_.data();
×
NEW
967
  *n = m->n_dimension_;
×
NEW
968
  return 0;
×
969
}
970

971
//! Set the hexagonal mesh parameters
NEW
972
extern "C" int openmc_hexagonal_mesh_set_params(
×
973
  int32_t index, int n, const double* ll, const double* ur, const double* width)
974
{
NEW
975
  if (int err = check_mesh_type<HexagonalMesh>(index))
×
NEW
976
    return err;
×
NEW
977
  HexagonalMesh* m = dynamic_cast<HexagonalMesh*>(model::meshes[index].get());
×
978

NEW
979
  if (m->n_dimension_ == -1) {
×
NEW
980
    set_errmsg("Need to set mesh dimension before setting parameters.");
×
NEW
981
    return OPENMC_E_UNASSIGNED;
×
982
  }
983

NEW
984
  vector<std::size_t> shape = {static_cast<std::size_t>(n)};
×
NEW
985
  if (ll && ur) {
×
NEW
986
    m->lower_left_ = xt::adapt(ll, n, xt::no_ownership(), shape);
×
NEW
987
    m->upper_right_ = xt::adapt(ur, n, xt::no_ownership(), shape);
×
NEW
988
    m->width_ = (m->upper_right_ - m->lower_left_) / m->get_x_shape();
×
NEW
989
  } else if (ll && width) {
×
NEW
990
    m->lower_left_ = xt::adapt(ll, n, xt::no_ownership(), shape);
×
NEW
991
    m->width_ = xt::adapt(width, n, xt::no_ownership(), shape);
×
NEW
992
    m->upper_right_ = m->lower_left_ + m->get_x_shape() * m->width_;
×
NEW
993
  } else if (ur && width) {
×
NEW
994
    m->upper_right_ = xt::adapt(ur, n, xt::no_ownership(), shape);
×
NEW
995
    m->width_ = xt::adapt(width, n, xt::no_ownership(), shape);
×
NEW
996
    m->lower_left_ = m->upper_right_ - m->get_x_shape() * m->width_;
×
997
  } else {
NEW
998
    set_errmsg("At least two parameters must be specified.");
×
NEW
999
    return OPENMC_E_INVALID_ARGUMENT;
×
1000
  }
1001

1002
  // TODO: incorporate this into method in HexagonalMesh that can be called from
1003
  // here and from constructor
1004

1005
  // Set material volumes etc.
NEW
1006
  m->size_ = m->width_[0] / sqrt(3.0);
×
NEW
1007
  m->init_plane_normals();
×
NEW
1008
  m->scale_grid_vectors(m->size_);
×
1009

NEW
1010
  return 0;
×
NEW
1011
}
×
1012

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