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

openmc-dev / openmc / 17191148992

24 Aug 2025 04:31PM UTC coverage: 85.197% (-0.007%) from 85.204%
17191148992

Pull #3355

github

web-flow
Merge 465402bb2 into d1df80a21
Pull Request #3355: Add corner checks for rectangular lattice crossings

2 of 8 new or added lines in 1 file covered. (25.0%)

52942 of 62141 relevant lines covered (85.2%)

37888375.47 hits per line

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

93.12
/src/lattice.cpp
1
#include "openmc/lattice.h"
2

3
#include <cmath>
4
#include <string>
5

6
#include <fmt/core.h>
7

8
#include "openmc/cell.h"
9
#include "openmc/error.h"
10
#include "openmc/geometry.h"
11
#include "openmc/geometry_aux.h"
12
#include "openmc/hdf5_interface.h"
13
#include "openmc/string_utils.h"
14
#include "openmc/vector.h"
15
#include "openmc/xml_interface.h"
16

17
namespace openmc {
18

19
//==============================================================================
20
// Global variables
21
//==============================================================================
22

23
namespace model {
24
std::unordered_map<int32_t, int32_t> lattice_map;
25
vector<unique_ptr<Lattice>> lattices;
26
} // namespace model
27

28
//==============================================================================
29
// Lattice implementation
30
//==============================================================================
31

32
Lattice::Lattice(pugi::xml_node lat_node)
1,799✔
33
{
34
  if (check_for_node(lat_node, "id")) {
1,799✔
35
    id_ = std::stoi(get_node_value(lat_node, "id"));
1,799✔
36
  } else {
37
    fatal_error("Must specify id of lattice in geometry XML file.");
×
38
  }
39

40
  if (check_for_node(lat_node, "name")) {
1,799✔
41
    name_ = get_node_value(lat_node, "name");
358✔
42
  }
43

44
  if (check_for_node(lat_node, "outer")) {
1,799✔
45
    outer_ = std::stoi(get_node_value(lat_node, "outer"));
387✔
46
  }
47
}
1,799✔
48

49
//==============================================================================
50

51
LatticeIter Lattice::begin()
289,798,457✔
52
{
53
  return LatticeIter(*this, 0);
289,798,457✔
54
}
55

56
LatticeIter Lattice::end()
48,650,462✔
57
{
58
  return LatticeIter(*this, universes_.size());
48,650,462✔
59
}
60

61
int32_t& Lattice::back()
22✔
62
{
63
  return universes_.back();
22✔
64
}
65

66
ReverseLatticeIter Lattice::rbegin()
1,638,252✔
67
{
68
  return ReverseLatticeIter(*this, universes_.size() - 1);
1,638,252✔
69
}
70

71
ReverseLatticeIter Lattice::rend()
289,047,726✔
72
{
73
  return ReverseLatticeIter(*this, -1);
289,047,726✔
74
}
75

76
//==============================================================================
77

78
void Lattice::adjust_indices()
1,799✔
79
{
80
  // Adjust the indices for the universes array.
81
  for (LatticeIter it = begin(); it != end(); ++it) {
890,717✔
82
    int uid = *it;
888,918✔
83
    auto search = model::universe_map.find(uid);
888,918✔
84
    if (search != model::universe_map.end()) {
888,918✔
85
      *it = search->second;
888,918✔
86
    } else {
87
      fatal_error(fmt::format(
×
88
        "Invalid universe number {} specified on lattice {}", uid, id_));
×
89
    }
90
  }
91

92
  // Adjust the index for the outer universe.
93
  if (outer_ != NO_OUTER_UNIVERSE) {
1,799✔
94
    auto search = model::universe_map.find(outer_);
387✔
95
    if (search != model::universe_map.end()) {
387✔
96
      outer_ = search->second;
387✔
97
    } else {
98
      fatal_error(fmt::format(
×
99
        "Invalid universe number {} specified on lattice {}", outer_, id_));
×
100
    }
101
  }
102
}
1,799✔
103

104
//==============================================================================
105

106
int32_t Lattice::fill_offset_table(int32_t target_univ_id, int map,
12,571✔
107
  std::unordered_map<int32_t, int32_t>& univ_count_memo)
108
{
109
  // If the offsets have already been determined for this "map", don't bother
110
  // recalculating all of them and just return the total offset. Note that the
111
  // offsets_ array doesn't actually include the offset accounting for the last
112
  // universe, so we get the before-last offset for the given map and then
113
  // explicitly add the count for the last universe.
114
  if (offsets_[map * universes_.size() + this->begin().indx_] != C_NONE) {
12,571✔
115
    int last_offset =
116
      offsets_[(map + 1) * universes_.size() - this->begin().indx_ - 1];
33✔
117
    int last_univ = this->back();
33✔
118
    return last_offset +
119
           count_universe_instances(last_univ, target_univ_id, univ_count_memo);
33✔
120
  }
121

122
  int32_t offset = 0;
12,538✔
123
  for (LatticeIter it = begin(); it != end(); ++it) {
9,184,053✔
124
    offsets_[map * universes_.size() + it.indx_] = offset;
9,171,515✔
125
    offset += count_universe_instances(*it, target_univ_id, univ_count_memo);
9,171,515✔
126
  }
127

128
  return offset;
12,538✔
129
}
130

131
//==============================================================================
132

133
void Lattice::to_hdf5(hid_t lattices_group) const
1,287✔
134
{
135
  // Make a group for the lattice.
136
  std::string group_name {"lattice "};
1,287✔
137
  group_name += std::to_string(id_);
1,287✔
138
  hid_t lat_group = create_group(lattices_group, group_name);
1,287✔
139

140
  // Write the name and outer universe.
141
  if (!name_.empty()) {
1,287✔
142
    write_string(lat_group, "name", name_, false);
242✔
143
  }
144

145
  if (outer_ != NO_OUTER_UNIVERSE) {
1,287✔
146
    int32_t outer_id = model::universes[outer_]->id_;
266✔
147
    write_dataset(lat_group, "outer", outer_id);
266✔
148
  } else {
149
    write_dataset(lat_group, "outer", outer_);
1,021✔
150
  }
151

152
  // Call subclass-overriden function to fill in other details.
153
  to_hdf5_inner(lat_group);
1,287✔
154

155
  close_group(lat_group);
1,287✔
156
}
1,287✔
157

158
//==============================================================================
159
// RectLattice implementation
160
//==============================================================================
161

162
RectLattice::RectLattice(pugi::xml_node lat_node) : Lattice {lat_node}
1,708✔
163
{
164
  type_ = LatticeType::rect;
1,708✔
165

166
  // Read the number of lattice cells in each dimension.
167
  std::string dimension_str {get_node_value(lat_node, "dimension")};
1,708✔
168
  vector<std::string> dimension_words {split(dimension_str)};
1,708✔
169
  if (dimension_words.size() == 2) {
1,708✔
170
    n_cells_[0] = std::stoi(dimension_words[0]);
1,284✔
171
    n_cells_[1] = std::stoi(dimension_words[1]);
1,284✔
172
    n_cells_[2] = 1;
1,284✔
173
    is_3d_ = false;
1,284✔
174
  } else if (dimension_words.size() == 3) {
424✔
175
    n_cells_[0] = std::stoi(dimension_words[0]);
424✔
176
    n_cells_[1] = std::stoi(dimension_words[1]);
424✔
177
    n_cells_[2] = std::stoi(dimension_words[2]);
424✔
178
    is_3d_ = true;
424✔
179
  } else {
180
    fatal_error("Rectangular lattice must be two or three dimensions.");
×
181
  }
182

183
  // Read the lattice lower-left location.
184
  std::string ll_str {get_node_value(lat_node, "lower_left")};
1,708✔
185
  vector<std::string> ll_words {split(ll_str)};
1,708✔
186
  if (ll_words.size() != dimension_words.size()) {
1,708✔
187
    fatal_error("Number of entries on <lower_left> must be the same as the "
×
188
                "number of entries on <dimension>.");
189
  }
190
  lower_left_[0] = stod(ll_words[0]);
1,708✔
191
  lower_left_[1] = stod(ll_words[1]);
1,708✔
192
  if (is_3d_) {
1,708✔
193
    lower_left_[2] = stod(ll_words[2]);
424✔
194
  }
195

196
  // Read the lattice pitches.
197
  std::string pitch_str {get_node_value(lat_node, "pitch")};
1,708✔
198
  vector<std::string> pitch_words {split(pitch_str)};
1,708✔
199
  if (pitch_words.size() != dimension_words.size()) {
1,708✔
200
    fatal_error("Number of entries on <pitch> must be the same as the "
×
201
                "number of entries on <dimension>.");
202
  }
203
  pitch_[0] = stod(pitch_words[0]);
1,708✔
204
  pitch_[1] = stod(pitch_words[1]);
1,708✔
205
  if (is_3d_) {
1,708✔
206
    pitch_[2] = stod(pitch_words[2]);
424✔
207
  }
208

209
  // Read the universes and make sure the correct number was specified.
210
  std::string univ_str {get_node_value(lat_node, "universes")};
1,708✔
211
  vector<std::string> univ_words {split(univ_str)};
1,708✔
212
  if (univ_words.size() != n_cells_[0] * n_cells_[1] * n_cells_[2]) {
1,708✔
213
    fatal_error(fmt::format(
×
214
      "Expected {} universes for a rectangular lattice of size {}x{}x{} but {} "
215
      "were specified.",
216
      n_cells_[0] * n_cells_[1] * n_cells_[2], n_cells_[0], n_cells_[1],
×
217
      n_cells_[2], univ_words.size()));
×
218
  }
219

220
  // Parse the universes.
221
  universes_.resize(n_cells_[0] * n_cells_[1] * n_cells_[2], C_NONE);
1,708✔
222
  for (int iz = 0; iz < n_cells_[2]; iz++) {
7,728✔
223
    for (int iy = n_cells_[1] - 1; iy > -1; iy--) {
74,113✔
224
      for (int ix = 0; ix < n_cells_[0]; ix++) {
929,926✔
225
        int indx1 = n_cells_[0] * n_cells_[1] * iz +
861,833✔
226
                    n_cells_[0] * (n_cells_[1] - iy - 1) + ix;
861,833✔
227
        int indx2 = n_cells_[0] * n_cells_[1] * iz + n_cells_[0] * iy + ix;
861,833✔
228
        universes_[indx1] = std::stoi(univ_words[indx2]);
861,833✔
229
      }
230
    }
231
  }
232
}
1,708✔
233

234
//==============================================================================
235

236
const int32_t& RectLattice::operator[](const array<int, 3>& i_xyz)
769,896,203✔
237
{
238
  return universes_[get_flat_index(i_xyz)];
769,896,203✔
239
}
240

241
//==============================================================================
242

243
bool RectLattice::are_valid_indices(const array<int, 3>& i_xyz) const
1,820,782,750✔
244
{
245
  return ((i_xyz[0] >= 0) && (i_xyz[0] < n_cells_[0]) && (i_xyz[1] >= 0) &&
2,147,483,647✔
246
          (i_xyz[1] < n_cells_[1]) && (i_xyz[2] >= 0) &&
2,147,483,647✔
247
          (i_xyz[2] < n_cells_[2]));
2,147,483,647✔
248
}
249

250
//==============================================================================
251

252
std::pair<double, array<int, 3>> RectLattice::distance(
1,065,933,043✔
253
  Position r, Direction u, const array<int, 3>& i_xyz) const
254
{
255
  // Get short aliases to the coordinates.
256
  double x = r.x;
1,065,933,043✔
257
  double y = r.y;
1,065,933,043✔
258
  double z = r.z;
1,065,933,043✔
259

260
  // Determine the oncoming edge.
261
  double x0 {copysign(0.5 * pitch_[0], u.x)};
1,065,933,043✔
262
  double y0 {copysign(0.5 * pitch_[1], u.y)};
1,065,933,043✔
263

264
  // Left and right sides
265
  double d {INFTY};
1,065,933,043✔
266
  array<int, 3> lattice_trans;
267
  if ((std::abs(x - x0) > FP_PRECISION) && u.x != 0) {
1,065,933,043✔
268
    d = (x0 - x) / u.x;
1,065,933,043✔
269
    if (u.x > 0) {
1,065,933,043✔
270
      lattice_trans = {1, 0, 0};
543,084,607✔
271
    } else {
272
      lattice_trans = {-1, 0, 0};
522,848,436✔
273
    }
274
  }
275

276
  // Front and back sides
277
  if ((std::abs(y - y0) > FP_PRECISION) && u.y != 0) {
1,065,933,043✔
278
    double this_d = (y0 - y) / u.y;
1,065,933,043✔
279
    if (this_d < d) {
1,065,933,043✔
280
      d = this_d;
528,879,624✔
281
      if (u.y > 0) {
528,879,624✔
282
        lattice_trans = {0, 1, 0};
269,483,564✔
283
      } else {
284
        lattice_trans = {0, -1, 0};
259,396,060✔
285
      }
286
    }
287
  }
288

289
  // Top and bottom sides
290
  if (is_3d_) {
1,065,933,043✔
291
    double z0 {copysign(0.5 * pitch_[2], u.z)};
632,481,492✔
292
    if ((std::abs(z - z0) > FP_PRECISION) && u.z != 0) {
632,481,492✔
293
      double this_d = (z0 - z) / u.z;
632,481,492✔
294
      if (this_d < d) {
632,481,492✔
295
        d = this_d;
207,729,593✔
296
        if (u.z > 0) {
207,729,593✔
297
          lattice_trans = {0, 0, 1};
106,978,480✔
298
        } else {
299
          lattice_trans = {0, 0, -1};
100,751,113✔
300
        }
301
      }
302
    }
303
  }
304

305
  // Corner cases.
306
  if (std::abs(x + u.x * d - x0) < FP_PRECISION &&
1,498,725,681✔
307
      std::abs(y + u.y * d - y0) < FP_PRECISION) {
432,792,638✔
NEW
308
    lattice_trans[0] = u.x > 0 ? 1 : -1;
×
NEW
309
    lattice_trans[1] = u.y > 0 ? 1 : -1;
×
310

NEW
311
    if (is_3d_) {
×
NEW
312
      double z0 {copysign(0.5 * pitch_[2], u.z)};
×
NEW
313
      if (std::abs(z + u.z * d - z0) < FP_PRECISION) {
×
NEW
314
        lattice_trans[2] = u.z > 0 ? 1 : -1;
×
315
      }
316
    }
317
  }
318

319
  return {d, lattice_trans};
2,131,866,086✔
320
}
321

322
//==============================================================================
323

324
void RectLattice::get_indices(
115,813,453✔
325
  Position r, Direction u, array<int, 3>& result) const
326
{
327
  // Determine x index, accounting for coincidence
328
  double ix_ {(r.x - lower_left_.x) / pitch_.x};
115,813,453✔
329
  long ix_close {std::lround(ix_)};
115,813,453✔
330
  if (coincident(ix_, ix_close)) {
115,813,453✔
331
    result[0] = (u.x > 0) ? ix_close : ix_close - 1;
35,659,876✔
332
  } else {
333
    result[0] = std::floor(ix_);
80,153,577✔
334
  }
335

336
  // Determine y index, accounting for coincidence
337
  double iy_ {(r.y - lower_left_.y) / pitch_.y};
115,813,453✔
338
  long iy_close {std::lround(iy_)};
115,813,453✔
339
  if (coincident(iy_, iy_close)) {
115,813,453✔
340
    result[1] = (u.y > 0) ? iy_close : iy_close - 1;
36,078,889✔
341
  } else {
342
    result[1] = std::floor(iy_);
79,734,564✔
343
  }
344

345
  // Determine z index, accounting for coincidence
346
  result[2] = 0;
115,813,453✔
347
  if (is_3d_) {
115,813,453✔
348
    double iz_ {(r.z - lower_left_.z) / pitch_.z};
65,112,754✔
349
    long iz_close {std::lround(iz_)};
65,112,754✔
350
    if (coincident(iz_, iz_close)) {
65,112,754✔
351
      result[2] = (u.z > 0) ? iz_close : iz_close - 1;
17,634,058✔
352
    } else {
353
      result[2] = std::floor(iz_);
47,478,696✔
354
    }
355
  }
356
}
115,813,453✔
357

358
int RectLattice::get_flat_index(const array<int, 3>& i_xyz) const
769,896,203✔
359
{
360
  return n_cells_[0] * n_cells_[1] * i_xyz[2] + n_cells_[0] * i_xyz[1] +
769,896,203✔
361
         i_xyz[0];
769,896,203✔
362
}
363

364
//==============================================================================
365

366
Position RectLattice::get_local_position(
774,129,091✔
367
  Position r, const array<int, 3>& i_xyz) const
368
{
369
  r.x -= (lower_left_.x + (i_xyz[0] + 0.5) * pitch_.x);
774,129,091✔
370
  r.y -= (lower_left_.y + (i_xyz[1] + 0.5) * pitch_.y);
774,129,091✔
371
  if (is_3d_) {
774,129,091✔
372
    r.z -= (lower_left_.z + (i_xyz[2] + 0.5) * pitch_.z);
628,521,119✔
373
  }
374
  return r;
774,129,091✔
375
}
376

377
//==============================================================================
378

379
int32_t& RectLattice::offset(int map, const array<int, 3>& i_xyz)
1,044,068,252✔
380
{
381
  return offsets_[n_cells_[0] * n_cells_[1] * n_cells_[2] * map +
1,044,068,252✔
382
                  n_cells_[0] * n_cells_[1] * i_xyz[2] +
1,044,068,252✔
383
                  n_cells_[0] * i_xyz[1] + i_xyz[0]];
2,088,136,504✔
384
}
385

386
//==============================================================================
387

388
int32_t RectLattice::offset(int map, int indx) const
88,752✔
389
{
390
  return offsets_[n_cells_[0] * n_cells_[1] * n_cells_[2] * map + indx];
88,752✔
391
}
392

393
//==============================================================================
394

395
std::string RectLattice::index_to_string(int indx) const
1,638,252✔
396
{
397
  int iz {indx / (n_cells_[0] * n_cells_[1])};
1,638,252✔
398
  int iy {(indx - n_cells_[0] * n_cells_[1] * iz) / n_cells_[0]};
1,638,252✔
399
  int ix {indx - n_cells_[0] * n_cells_[1] * iz - n_cells_[0] * iy};
1,638,252✔
400
  std::string out {std::to_string(ix)};
1,638,252✔
401
  out += ',';
1,638,252✔
402
  out += std::to_string(iy);
1,638,252✔
403
  if (is_3d_) {
1,638,252✔
404
    out += ',';
×
405
    out += std::to_string(iz);
×
406
  }
407
  return out;
1,638,252✔
408
}
×
409

410
//==============================================================================
411

412
void RectLattice::to_hdf5_inner(hid_t lat_group) const
1,232✔
413
{
414
  // Write basic lattice information.
415
  write_string(lat_group, "type", "rectangular", false);
1,232✔
416
  if (is_3d_) {
1,232✔
417
    write_dataset(lat_group, "pitch", pitch_);
304✔
418
    write_dataset(lat_group, "lower_left", lower_left_);
304✔
419
    write_dataset(lat_group, "dimension", n_cells_);
304✔
420
  } else {
421
    array<double, 2> pitch_short {{pitch_[0], pitch_[1]}};
928✔
422
    write_dataset(lat_group, "pitch", pitch_short);
928✔
423
    array<double, 2> ll_short {{lower_left_[0], lower_left_[1]}};
928✔
424
    write_dataset(lat_group, "lower_left", ll_short);
928✔
425
    array<int, 2> nc_short {{n_cells_[0], n_cells_[1]}};
928✔
426
    write_dataset(lat_group, "dimension", nc_short);
928✔
427
  }
428

429
  // Write the universe ids.  The convention here is to switch the ordering on
430
  // the y-axis to match the way universes are input in a text file.
431
  if (is_3d_) {
1,232✔
432
    hsize_t nx {static_cast<hsize_t>(n_cells_[0])};
304✔
433
    hsize_t ny {static_cast<hsize_t>(n_cells_[1])};
304✔
434
    hsize_t nz {static_cast<hsize_t>(n_cells_[2])};
304✔
435
    vector<int> out(nx * ny * nz);
304✔
436

437
    for (int m = 0; m < nz; m++) {
3,665✔
438
      for (int k = 0; k < ny; k++) {
42,792✔
439
        for (int j = 0; j < nx; j++) {
510,952✔
440
          int indx1 = nx * ny * m + nx * k + j;
471,521✔
441
          int indx2 = nx * ny * m + nx * (ny - k - 1) + j;
471,521✔
442
          out[indx2] = model::universes[universes_[indx1]]->id_;
471,521✔
443
        }
444
      }
445
    }
446

447
    hsize_t dims[3] {nz, ny, nx};
304✔
448
    write_int(lat_group, 3, dims, "universes", out.data(), false);
304✔
449

450
  } else {
304✔
451
    hsize_t nx {static_cast<hsize_t>(n_cells_[0])};
928✔
452
    hsize_t ny {static_cast<hsize_t>(n_cells_[1])};
928✔
453
    vector<int> out(nx * ny);
928✔
454

455
    for (int k = 0; k < ny; k++) {
9,693✔
456
      for (int j = 0; j < nx; j++) {
146,748✔
457
        int indx1 = nx * k + j;
137,983✔
458
        int indx2 = nx * (ny - k - 1) + j;
137,983✔
459
        out[indx2] = model::universes[universes_[indx1]]->id_;
137,983✔
460
      }
461
    }
462

463
    hsize_t dims[3] {1, ny, nx};
928✔
464
    write_int(lat_group, 3, dims, "universes", out.data(), false);
928✔
465
  }
928✔
466
}
1,232✔
467

468
//==============================================================================
469
// HexLattice implementation
470
//==============================================================================
471

472
HexLattice::HexLattice(pugi::xml_node lat_node) : Lattice {lat_node}
91✔
473
{
474
  type_ = LatticeType::hex;
91✔
475

476
  // Read the number of lattice cells in each dimension.
477
  n_rings_ = std::stoi(get_node_value(lat_node, "n_rings"));
91✔
478
  if (check_for_node(lat_node, "n_axial")) {
91✔
479
    n_axial_ = std::stoi(get_node_value(lat_node, "n_axial"));
32✔
480
    is_3d_ = true;
32✔
481
  } else {
482
    n_axial_ = 1;
59✔
483
    is_3d_ = false;
59✔
484
  }
485

486
  // Read the lattice orientation.  Default to 'y'.
487
  if (check_for_node(lat_node, "orientation")) {
91✔
488
    std::string orientation = get_node_value(lat_node, "orientation");
16✔
489
    if (orientation == "y") {
16✔
490
      orientation_ = Orientation::y;
×
491
    } else if (orientation == "x") {
16✔
492
      orientation_ = Orientation::x;
16✔
493
    } else {
494
      fatal_error("Unrecognized orientation '" + orientation +
×
495
                  "' for lattice " + std::to_string(id_));
×
496
    }
497
  } else {
16✔
498
    orientation_ = Orientation::y;
75✔
499
  }
500

501
  // Read the lattice center.
502
  std::string center_str {get_node_value(lat_node, "center")};
91✔
503
  vector<std::string> center_words {split(center_str)};
91✔
504
  if (is_3d_ && (center_words.size() != 3)) {
91✔
505
    fatal_error("A hexagonal lattice with <n_axial> must have <center> "
×
506
                "specified by 3 numbers.");
507
  } else if (!is_3d_ && center_words.size() != 2) {
91✔
508
    fatal_error("A hexagonal lattice without <n_axial> must have <center> "
×
509
                "specified by 2 numbers.");
510
  }
511
  center_[0] = stod(center_words[0]);
91✔
512
  center_[1] = stod(center_words[1]);
91✔
513
  if (is_3d_) {
91✔
514
    center_[2] = stod(center_words[2]);
32✔
515
  }
516

517
  // Read the lattice pitches.
518
  std::string pitch_str {get_node_value(lat_node, "pitch")};
91✔
519
  vector<std::string> pitch_words {split(pitch_str)};
91✔
520
  if (is_3d_ && (pitch_words.size() != 2)) {
91✔
521
    fatal_error("A hexagonal lattice with <n_axial> must have <pitch> "
×
522
                "specified by 2 numbers.");
523
  } else if (!is_3d_ && (pitch_words.size() != 1)) {
91✔
524
    fatal_error("A hexagonal lattice without <n_axial> must have <pitch> "
×
525
                "specified by 1 number.");
526
  }
527
  pitch_[0] = stod(pitch_words[0]);
91✔
528
  if (is_3d_) {
91✔
529
    pitch_[1] = stod(pitch_words[1]);
32✔
530
  }
531

532
  // Read the universes and make sure the correct number was specified.
533
  int n_univ = (3 * n_rings_ * n_rings_ - 3 * n_rings_ + 1) * n_axial_;
91✔
534
  std::string univ_str {get_node_value(lat_node, "universes")};
91✔
535
  vector<std::string> univ_words {split(univ_str)};
91✔
536
  if (univ_words.size() != n_univ) {
91✔
537
    fatal_error(fmt::format(
×
538
      "Expected {} universes for a hexagonal lattice with {} rings and {} "
539
      "axial levels but {} were specified.",
540
      n_univ, n_rings_, n_axial_, univ_words.size()));
×
541
  }
542

543
  // Parse the universes.
544
  // Universes in hexagonal lattices are stored in a manner that represents
545
  // a skewed coordinate system: (x, alpha) in case of 'y' orientation
546
  // and (alpha,y) in 'x' one rather than (x, y).  There is
547
  // no obvious, direct relationship between the order of universes in the
548
  // input and the order that they will be stored in the skewed array so
549
  // the following code walks a set of index values across the skewed array
550
  // in a manner that matches the input order.  Note that i_x = 0, i_a = 0
551
  // or i_a = 0, i_y = 0 corresponds to the center of the hexagonal lattice.
552
  universes_.resize((2 * n_rings_ - 1) * (2 * n_rings_ - 1) * n_axial_, C_NONE);
91✔
553
  if (orientation_ == Orientation::y) {
91✔
554
    fill_lattice_y(univ_words);
75✔
555
  } else {
556
    fill_lattice_x(univ_words);
16✔
557
  }
558
}
91✔
559

560
//==============================================================================
561

562
void HexLattice::fill_lattice_x(const vector<std::string>& univ_words)
16✔
563
{
564
  int input_index = 0;
16✔
565
  for (int m = 0; m < n_axial_; m++) {
48✔
566
    // Initialize lattice indecies.
567
    int i_a = -(n_rings_ - 1);
32✔
568
    int i_y = n_rings_ - 1;
32✔
569

570
    // Map upper region of hexagonal lattice which is found in the
571
    // first n_rings-1 rows of the input.
572
    for (int k = 0; k < n_rings_ - 1; k++) {
352✔
573

574
      // Iterate over the input columns.
575
      for (int j = 0; j < k + n_rings_; j++) {
5,280✔
576
        int indx = (2 * n_rings_ - 1) * (2 * n_rings_ - 1) * m +
4,960✔
577
                   (2 * n_rings_ - 1) * (i_y + n_rings_ - 1) +
4,960✔
578
                   (i_a + n_rings_ - 1);
4,960✔
579
        universes_[indx] = std::stoi(univ_words[input_index]);
4,960✔
580
        input_index++;
4,960✔
581
        // Move to the next right neighbour cell
582
        i_a += 1;
4,960✔
583
      }
584

585
      // Return the lattice index to the start of the current row.
586
      i_a = -(n_rings_ - 1);
320✔
587
      i_y -= 1;
320✔
588
    }
589

590
    // Map the lower region from the centerline of cart to down side
591
    for (int k = 0; k < n_rings_; k++) {
384✔
592
      // Walk the index to the lower-right neighbor of the last row start.
593
      i_a = -(n_rings_ - 1) + k;
352✔
594

595
      // Iterate over the input columns.
596
      for (int j = 0; j < 2 * n_rings_ - k - 1; j++) {
5,984✔
597
        int indx = (2 * n_rings_ - 1) * (2 * n_rings_ - 1) * m +
5,632✔
598
                   (2 * n_rings_ - 1) * (i_y + n_rings_ - 1) +
5,632✔
599
                   (i_a + n_rings_ - 1);
5,632✔
600
        universes_[indx] = std::stoi(univ_words[input_index]);
5,632✔
601
        input_index++;
5,632✔
602
        // Move to the next right neighbour cell
603
        i_a += 1;
5,632✔
604
      }
605

606
      // Return lattice index to start of current row.
607
      i_y -= 1;
352✔
608
    }
609
  }
610
}
16✔
611

612
//==============================================================================
613

614
void HexLattice::fill_lattice_y(const vector<std::string>& univ_words)
75✔
615
{
616
  int input_index = 0;
75✔
617
  for (int m = 0; m < n_axial_; m++) {
182✔
618
    // Initialize lattice indecies.
619
    int i_x = 1;
107✔
620
    int i_a = n_rings_ - 1;
107✔
621

622
    // Map upper triangular region of hexagonal lattice which is found in the
623
    // first n_rings-1 rows of the input.
624
    for (int k = 0; k < n_rings_ - 1; k++) {
662✔
625
      // Walk the index to lower-left neighbor of last row start.
626
      i_x -= 1;
555✔
627

628
      // Iterate over the input columns.
629
      for (int j = 0; j < k + 1; j++) {
3,286✔
630
        int indx = (2 * n_rings_ - 1) * (2 * n_rings_ - 1) * m +
2,731✔
631
                   (2 * n_rings_ - 1) * (i_a + n_rings_ - 1) +
2,731✔
632
                   (i_x + n_rings_ - 1);
2,731✔
633
        universes_[indx] = std::stoi(univ_words[input_index]);
2,731✔
634
        input_index++;
2,731✔
635
        // Walk the index to the right neighbor (which is not adjacent).
636
        i_x += 2;
2,731✔
637
        i_a -= 1;
2,731✔
638
      }
639

640
      // Return the lattice index to the start of the current row.
641
      i_x -= 2 * (k + 1);
555✔
642
      i_a += (k + 1);
555✔
643
    }
644

645
    // Map the middle square region of the hexagonal lattice which is found in
646
    // the next 2*n_rings-1 rows of the input.
647
    for (int k = 0; k < 2 * n_rings_ - 1; k++) {
1,324✔
648
      if ((k % 2) == 0) {
1,217✔
649
        // Walk the index to the lower-left neighbor of the last row start.
650
        i_x -= 1;
662✔
651
      } else {
652
        // Walk the index to the lower-right neighbor of the last row start.
653
        i_x += 1;
555✔
654
        i_a -= 1;
555✔
655
      }
656

657
      // Iterate over the input columns.
658
      for (int j = 0; j < n_rings_ - (k % 2); j++) {
12,248✔
659
        int indx = (2 * n_rings_ - 1) * (2 * n_rings_ - 1) * m +
11,031✔
660
                   (2 * n_rings_ - 1) * (i_a + n_rings_ - 1) +
11,031✔
661
                   (i_x + n_rings_ - 1);
11,031✔
662
        universes_[indx] = std::stoi(univ_words[input_index]);
11,031✔
663
        input_index++;
11,031✔
664
        // Walk the index to the right neighbor (which is not adjacent).
665
        i_x += 2;
11,031✔
666
        i_a -= 1;
11,031✔
667
      }
668

669
      // Return the lattice index to the start of the current row.
670
      i_x -= 2 * (n_rings_ - (k % 2));
1,217✔
671
      i_a += n_rings_ - (k % 2);
1,217✔
672
    }
673

674
    // Map the lower triangular region of the hexagonal lattice.
675
    for (int k = 0; k < n_rings_ - 1; k++) {
662✔
676
      // Walk the index to the lower-right neighbor of the last row start.
677
      i_x += 1;
555✔
678
      i_a -= 1;
555✔
679

680
      // Iterate over the input columns.
681
      for (int j = 0; j < n_rings_ - k - 1; j++) {
3,286✔
682
        int indx = (2 * n_rings_ - 1) * (2 * n_rings_ - 1) * m +
2,731✔
683
                   (2 * n_rings_ - 1) * (i_a + n_rings_ - 1) +
2,731✔
684
                   (i_x + n_rings_ - 1);
2,731✔
685
        universes_[indx] = std::stoi(univ_words[input_index]);
2,731✔
686
        input_index++;
2,731✔
687
        // Walk the index to the right neighbor (which is not adjacent).
688
        i_x += 2;
2,731✔
689
        i_a -= 1;
2,731✔
690
      }
691

692
      // Return lattice index to start of current row.
693
      i_x -= 2 * (n_rings_ - k - 1);
555✔
694
      i_a += n_rings_ - k - 1;
555✔
695
    }
696
  }
697
}
75✔
698

699
//==============================================================================
700

701
const int32_t& HexLattice::operator[](const array<int, 3>& i_xyz)
29,393,276✔
702
{
703
  return universes_[get_flat_index(i_xyz)];
29,393,276✔
704
}
705

706
//==============================================================================
707

708
// The HexLattice iterators need their own versions b/c the universes array is
709
// "square", meaning that it is allocated with entries that are intentionally
710
// left empty. As such, the iterator indices need to skip the empty entries to
711
// get cell instances and geometry paths correct. See the image in the Theory
712
// and Methodology section on "Hexagonal Lattice Indexing" for a visual of where
713
// the empty positions are.
714
LatticeIter HexLattice::begin()
2,151✔
715
{
716
  return LatticeIter(*this, n_rings_ - 1);
2,151✔
717
}
718

719
ReverseLatticeIter HexLattice::rbegin()
231✔
720
{
721
  return ReverseLatticeIter(*this, universes_.size() - n_rings_);
231✔
722
}
723

724
int32_t& HexLattice::back()
11✔
725
{
726
  return universes_[universes_.size() - n_rings_];
11✔
727
}
728

729
LatticeIter HexLattice::end()
685,917✔
730
{
731
  return LatticeIter(*this, universes_.size() - n_rings_ + 1);
685,917✔
732
}
733

734
ReverseLatticeIter HexLattice::rend()
924✔
735
{
736
  return ReverseLatticeIter(*this, n_rings_ - 2);
924✔
737
}
738

739
//==============================================================================
740

741
bool HexLattice::are_valid_indices(const array<int, 3>& i_xyz) const
114,002,816✔
742
{
743
  // Check if (x, alpha, z) indices are valid, accounting for number of rings
744
  return ((i_xyz[0] >= 0) && (i_xyz[1] >= 0) && (i_xyz[2] >= 0) &&
227,464,256✔
745
          (i_xyz[0] < 2 * n_rings_ - 1) && (i_xyz[1] < 2 * n_rings_ - 1) &&
110,710,703✔
746
          (i_xyz[0] + i_xyz[1] > n_rings_ - 2) &&
109,932,552✔
747
          (i_xyz[0] + i_xyz[1] < 3 * n_rings_ - 2) && (i_xyz[2] < n_axial_));
227,464,256✔
748
}
749

750
//==============================================================================
751

752
std::pair<double, array<int, 3>> HexLattice::distance(
85,718,490✔
753
  Position r, Direction u, const array<int, 3>& i_xyz) const
754
{
755
  // Short description of the direction vectors used here.  The beta, gamma, and
756
  // delta vectors point towards the flat sides of each hexagonal tile.
757
  // Y - orientation:
758
  //   basis0 = (1, 0)
759
  //   basis1 = (-1/sqrt(3), 1)   = +120 degrees from basis0
760
  //   beta   = (sqrt(3)/2, 1/2)  = +30 degrees from basis0
761
  //   gamma  = (sqrt(3)/2, -1/2) = -60 degrees from beta
762
  //   delta  = (0, 1)            = +60 degrees from beta
763
  // X - orientation:
764
  //   basis0 = (1/sqrt(3), -1)
765
  //   basis1 = (0, 1)            = +120 degrees from basis0
766
  //   beta   = (1, 0)            = +30 degrees from basis0
767
  //   gamma  = (1/2, -sqrt(3)/2) = -60 degrees from beta
768
  //   delta  = (1/2, sqrt(3)/2)  = +60 degrees from beta
769
  // The z-axis is considered separately.
770
  double beta_dir;
771
  double gamma_dir;
772
  double delta_dir;
773
  if (orientation_ == Orientation::y) {
85,718,490✔
774
    beta_dir = u.x * std::sqrt(3.0) / 2.0 + u.y / 2.0;
71,249,211✔
775
    gamma_dir = u.x * std::sqrt(3.0) / 2.0 - u.y / 2.0;
71,249,211✔
776
    delta_dir = u.y;
71,249,211✔
777
  } else {
778
    beta_dir = u.x;
14,469,279✔
779
    gamma_dir = u.x / 2.0 - u.y * std::sqrt(3.0) / 2.0;
14,469,279✔
780
    delta_dir = u.x / 2.0 + u.y * std::sqrt(3.0) / 2.0;
14,469,279✔
781
  }
782

783
  // Note that hexagonal lattice distance calculations are performed
784
  // using the particle's coordinates relative to the neighbor lattice
785
  // cells, not relative to the particle's current cell.  This is done
786
  // because there is significant disagreement between neighboring cells
787
  // on where the lattice boundary is due to finite precision issues.
788

789
  // beta direction
790
  double d {INFTY};
85,718,490✔
791
  array<int, 3> lattice_trans;
792
  double edge = -copysign(0.5 * pitch_[0], beta_dir); // Oncoming edge
85,718,490✔
793
  Position r_t;
85,718,490✔
794
  if (beta_dir > 0) {
85,718,490✔
795
    const array<int, 3> i_xyz_t {i_xyz[0] + 1, i_xyz[1], i_xyz[2]};
42,898,680✔
796
    r_t = get_local_position(r, i_xyz_t);
42,898,680✔
797
  } else {
798
    const array<int, 3> i_xyz_t {i_xyz[0] - 1, i_xyz[1], i_xyz[2]};
42,819,810✔
799
    r_t = get_local_position(r, i_xyz_t);
42,819,810✔
800
  }
801
  double beta;
802
  if (orientation_ == Orientation::y) {
85,718,490✔
803
    beta = r_t.x * std::sqrt(3.0) / 2.0 + r_t.y / 2.0;
71,249,211✔
804
  } else {
805
    beta = r_t.x;
14,469,279✔
806
  }
807
  if ((std::abs(beta - edge) > FP_PRECISION) && beta_dir != 0) {
85,718,490✔
808
    d = (edge - beta) / beta_dir;
85,718,490✔
809
    if (beta_dir > 0) {
85,718,490✔
810
      lattice_trans = {1, 0, 0};
42,898,680✔
811
    } else {
812
      lattice_trans = {-1, 0, 0};
42,819,810✔
813
    }
814
  }
815

816
  // gamma direction
817
  edge = -copysign(0.5 * pitch_[0], gamma_dir);
85,718,490✔
818
  if (gamma_dir > 0) {
85,718,490✔
819
    const array<int, 3> i_xyz_t {i_xyz[0] + 1, i_xyz[1] - 1, i_xyz[2]};
42,875,382✔
820
    r_t = get_local_position(r, i_xyz_t);
42,875,382✔
821
  } else {
822
    const array<int, 3> i_xyz_t {i_xyz[0] - 1, i_xyz[1] + 1, i_xyz[2]};
42,843,108✔
823
    r_t = get_local_position(r, i_xyz_t);
42,843,108✔
824
  }
825
  double gamma;
826
  if (orientation_ == Orientation::y) {
85,718,490✔
827
    gamma = r_t.x * std::sqrt(3.0) / 2.0 - r_t.y / 2.0;
71,249,211✔
828
  } else {
829
    gamma = r_t.x / 2.0 - r_t.y * std::sqrt(3.0) / 2.0;
14,469,279✔
830
  }
831
  if ((std::abs(gamma - edge) > FP_PRECISION) && gamma_dir != 0) {
85,718,490✔
832
    double this_d = (edge - gamma) / gamma_dir;
85,718,490✔
833
    if (this_d < d) {
85,718,490✔
834
      if (gamma_dir > 0) {
42,886,041✔
835
        lattice_trans = {1, -1, 0};
21,467,996✔
836
      } else {
837
        lattice_trans = {-1, 1, 0};
21,418,045✔
838
      }
839
      d = this_d;
42,886,041✔
840
    }
841
  }
842

843
  // delta direction
844
  edge = -copysign(0.5 * pitch_[0], delta_dir);
85,718,490✔
845
  if (delta_dir > 0) {
85,718,490✔
846
    const array<int, 3> i_xyz_t {i_xyz[0], i_xyz[1] + 1, i_xyz[2]};
42,841,645✔
847
    r_t = get_local_position(r, i_xyz_t);
42,841,645✔
848
  } else {
849
    const array<int, 3> i_xyz_t {i_xyz[0], i_xyz[1] - 1, i_xyz[2]};
42,876,845✔
850
    r_t = get_local_position(r, i_xyz_t);
42,876,845✔
851
  }
852
  double delta;
853
  if (orientation_ == Orientation::y) {
85,718,490✔
854
    delta = r_t.y;
71,249,211✔
855
  } else {
856
    delta = r_t.x / 2.0 + r_t.y * std::sqrt(3.0) / 2.0;
14,469,279✔
857
  }
858
  if ((std::abs(delta - edge) > FP_PRECISION) && delta_dir != 0) {
85,718,490✔
859
    double this_d = (edge - delta) / delta_dir;
85,718,490✔
860
    if (this_d < d) {
85,718,490✔
861
      if (delta_dir > 0) {
28,611,253✔
862
        lattice_trans = {0, 1, 0};
14,299,769✔
863
      } else {
864
        lattice_trans = {0, -1, 0};
14,311,484✔
865
      }
866
      d = this_d;
28,611,253✔
867
    }
868
  }
869

870
  // Top and bottom sides
871
  if (is_3d_) {
85,718,490✔
872
    double z = r.z;
20,079,686✔
873
    double z0 {copysign(0.5 * pitch_[1], u.z)};
20,079,686✔
874
    if ((std::abs(z - z0) > FP_PRECISION) && u.z != 0) {
20,079,686✔
875
      double this_d = (z0 - z) / u.z;
20,079,686✔
876
      if (this_d < d) {
20,079,686✔
877
        d = this_d;
3,089,537✔
878
        if (u.z > 0) {
3,089,537✔
879
          lattice_trans = {0, 0, 1};
1,543,773✔
880
        } else {
881
          lattice_trans = {0, 0, -1};
1,545,764✔
882
        }
883
        d = this_d;
3,089,537✔
884
      }
885
    }
886
  }
887

888
  return {d, lattice_trans};
171,436,980✔
889
}
890

891
//==============================================================================
892

893
void HexLattice::get_indices(
11,946,770✔
894
  Position r, Direction u, array<int, 3>& result) const
895
{
896
  // Offset the xyz by the lattice center.
897
  Position r_o {r.x - center_.x, r.y - center_.y, r.z};
11,946,770✔
898
  if (is_3d_) {
11,946,770✔
899
    r_o.z -= center_.z;
2,495,438✔
900
  }
901

902
  // Index the z direction, accounting for coincidence
903
  result[2] = 0;
11,946,770✔
904
  if (is_3d_) {
11,946,770✔
905
    double iz_ {r_o.z / pitch_[1] + 0.5 * n_axial_};
2,495,438✔
906
    long iz_close {std::lround(iz_)};
2,495,438✔
907
    if (coincident(iz_, iz_close)) {
2,495,438✔
908
      result[2] = (u.z > 0) ? iz_close : iz_close - 1;
774,026✔
909
    } else {
910
      result[2] = std::floor(iz_);
1,721,412✔
911
    }
912
  }
913

914
  if (orientation_ == Orientation::y) {
11,946,770✔
915
    // Convert coordinates into skewed bases.  The (x, alpha) basis is used to
916
    // find the index of the global coordinates to within 4 cells.
917
    double alpha = r_o.y - r_o.x / std::sqrt(3.0);
11,130,537✔
918
    result[0] = std::floor(r_o.x / (0.5 * std::sqrt(3.0) * pitch_[0]));
11,130,537✔
919
    result[1] = std::floor(alpha / pitch_[0]);
11,130,537✔
920
  } else {
921
    // Convert coordinates into skewed bases.  The (alpha, y) basis is used to
922
    // find the index of the global coordinates to within 4 cells.
923
    double alpha = r_o.y - r_o.x * std::sqrt(3.0);
816,233✔
924
    result[0] = std::floor(-alpha / (std::sqrt(3.0) * pitch_[0]));
816,233✔
925
    result[1] = std::floor(r_o.y / (0.5 * std::sqrt(3.0) * pitch_[0]));
816,233✔
926
  }
927

928
  // Add offset to indices (the center cell is (i1, i2) = (0, 0) but
929
  // the array is offset so that the indices never go below 0).
930
  result[0] += n_rings_ - 1;
11,946,770✔
931
  result[1] += n_rings_ - 1;
11,946,770✔
932

933
  // Calculate the (squared) distance between the particle and the centers of
934
  // the four possible cells.  Regular hexagonal tiles form a Voronoi
935
  // tessellation so the xyz should be in the hexagonal cell that it is closest
936
  // to the center of.  This method is used over a method that uses the
937
  // remainders of the floor divisions above because it provides better finite
938
  // precision performance.  Squared distances are used because they are more
939
  // computationally efficient than normal distances.
940

941
  // COINCIDENCE CHECK
942
  // if a distance to center, d, is within the coincidence tolerance of the
943
  // current minimum distance, d_min, the particle is on an edge or vertex.
944
  // In this case, the dot product of the position vector and direction vector
945
  // for the current indices, dp, and the dot product for the currently selected
946
  // indices, dp_min, are compared. The cell which the particle is moving into
947
  // is kept (i.e. the cell with the lowest dot product as the vectors will be
948
  // completely opposed if the particle is moving directly toward the center of
949
  // the cell).
950
  int i1_chg;
951
  int i2_chg;
952
  double d_min {INFTY};
11,946,770✔
953
  double dp_min {INFTY};
11,946,770✔
954
  for (int i = 0; i < 2; i++) {
35,840,310✔
955
    for (int j = 0; j < 2; j++) {
71,680,620✔
956
      // get local coordinates
957
      const array<int, 3> i_xyz {result[0] + j, result[1] + i, 0};
47,787,080✔
958
      Position r_t = get_local_position(r, i_xyz);
47,787,080✔
959
      // calculate distance
960
      double d = r_t.x * r_t.x + r_t.y * r_t.y;
47,787,080✔
961
      // check for coincidence. Because the numerical error incurred
962
      // in hex geometry is higher than other geometries, the relative
963
      // coincidence is checked here so that coincidence is successfully
964
      // detected on large hex lattice with particles far from the origin
965
      // which have rounding errors larger than the FP_COINCIDENT thresdhold.
966
      bool on_boundary = coincident(1.0, d_min / d);
47,787,080✔
967
      if (d < d_min || on_boundary) {
47,787,080✔
968
        // normalize r_t and find dot product
969
        r_t /= std::sqrt(d);
29,929,493✔
970
        double dp = u.x * r_t.x + u.y * r_t.y;
29,929,493✔
971
        // do not update values if particle is on a
972
        // boundary and not moving into this cell
973
        if (on_boundary && dp > dp_min)
29,929,493✔
974
          continue;
2,061,675✔
975
        // update values
976
        d_min = d;
27,867,818✔
977
        i1_chg = j;
27,867,818✔
978
        i2_chg = i;
27,867,818✔
979
        dp_min = dp;
27,867,818✔
980
      }
981
    }
982
  }
983

984
  // update outgoing indices
985
  result[0] += i1_chg;
11,946,770✔
986
  result[1] += i2_chg;
11,946,770✔
987
}
11,946,770✔
988

989
int HexLattice::get_flat_index(const array<int, 3>& i_xyz) const
29,393,276✔
990
{
991
  return (2 * n_rings_ - 1) * (2 * n_rings_ - 1) * i_xyz[2] +
29,393,276✔
992
         (2 * n_rings_ - 1) * i_xyz[1] + i_xyz[0];
29,393,276✔
993
}
994

995
//==============================================================================
996

997
Position HexLattice::get_local_position(
338,714,970✔
998
  Position r, const array<int, 3>& i_xyz) const
999
{
1000
  if (orientation_ == Orientation::y) {
338,714,970✔
1001
    // x_l = x_g - (center + pitch_x*cos(30)*index_x)
1002
    r.x -=
287,785,982✔
1003
      center_.x + std::sqrt(3.0) / 2.0 * (i_xyz[0] - n_rings_ + 1) * pitch_[0];
287,785,982✔
1004
    // y_l = y_g - (center + pitch_x*index_x + pitch_y*sin(30)*index_y)
1005
    r.y -= (center_.y + (i_xyz[1] - n_rings_ + 1) * pitch_[0] +
287,785,982✔
1006
            (i_xyz[0] - n_rings_ + 1) * pitch_[0] / 2.0);
287,785,982✔
1007
  } else {
1008
    // x_l = x_g - (center + pitch_x*index_a + pitch_y*sin(30)*index_y)
1009
    r.x -= (center_.x + (i_xyz[0] - n_rings_ + 1) * pitch_[0] +
50,928,988✔
1010
            (i_xyz[1] - n_rings_ + 1) * pitch_[0] / 2.0);
50,928,988✔
1011
    // y_l = y_g - (center + pitch_y*cos(30)*index_y)
1012
    r.y -=
50,928,988✔
1013
      center_.y + std::sqrt(3.0) / 2.0 * (i_xyz[1] - n_rings_ + 1) * pitch_[0];
50,928,988✔
1014
  }
1015

1016
  if (is_3d_) {
338,714,970✔
1017
    r.z -= center_.z - (0.5 * n_axial_ - i_xyz[2] - 0.5) * pitch_[1];
78,243,693✔
1018
  }
1019

1020
  return r;
338,714,970✔
1021
}
1022

1023
//==============================================================================
1024

1025
bool HexLattice::is_valid_index(int indx) const
387,424✔
1026
{
1027
  int nx {2 * n_rings_ - 1};
387,424✔
1028
  int ny {2 * n_rings_ - 1};
387,424✔
1029
  int iz = indx / (nx * ny);
387,424✔
1030
  int iy = (indx - nx * ny * iz) / nx;
387,424✔
1031
  int ix = indx - nx * ny * iz - nx * iy;
387,424✔
1032
  array<int, 3> i_xyz {ix, iy, iz};
387,424✔
1033
  return are_valid_indices(i_xyz);
774,848✔
1034
}
1035

1036
//==============================================================================
1037

1038
int32_t& HexLattice::offset(int map, const array<int, 3>& i_xyz)
77,413,281✔
1039
{
1040
  int nx {2 * n_rings_ - 1};
77,413,281✔
1041
  int ny {2 * n_rings_ - 1};
77,413,281✔
1042
  int nz {n_axial_};
77,413,281✔
1043
  return offsets_[nx * ny * nz * map + nx * ny * i_xyz[2] + nx * i_xyz[1] +
77,413,281✔
1044
                  i_xyz[0]];
154,826,562✔
1045
}
1046

1047
int32_t HexLattice::offset(int map, int indx) const
×
1048
{
1049
  int nx {2 * n_rings_ - 1};
×
1050
  int ny {2 * n_rings_ - 1};
×
1051
  int nz {n_axial_};
×
1052
  return offsets_[nx * ny * nz * map + indx];
×
1053
}
1054

1055
//==============================================================================
1056

1057
std::string HexLattice::index_to_string(int indx) const
231✔
1058
{
1059
  int nx {2 * n_rings_ - 1};
231✔
1060
  int ny {2 * n_rings_ - 1};
231✔
1061
  int iz {indx / (nx * ny)};
231✔
1062
  int iy {(indx - nx * ny * iz) / nx};
231✔
1063
  int ix {indx - nx * ny * iz - nx * iy};
231✔
1064
  std::string out {std::to_string(ix - n_rings_ + 1)};
231✔
1065
  out += ',';
231✔
1066
  out += std::to_string(iy - n_rings_ + 1);
231✔
1067
  if (is_3d_) {
231✔
1068
    out += ',';
×
1069
    out += std::to_string(iz);
×
1070
  }
1071
  return out;
231✔
1072
}
×
1073

1074
//==============================================================================
1075

1076
void HexLattice::to_hdf5_inner(hid_t lat_group) const
55✔
1077
{
1078
  // Write basic lattice information.
1079
  write_string(lat_group, "type", "hexagonal", false);
55✔
1080
  write_dataset(lat_group, "n_rings", n_rings_);
55✔
1081
  write_dataset(lat_group, "n_axial", n_axial_);
55✔
1082
  if (orientation_ == Orientation::y) {
55✔
1083
    write_string(lat_group, "orientation", "y", false);
44✔
1084
  } else {
1085
    write_string(lat_group, "orientation", "x", false);
11✔
1086
  }
1087
  if (is_3d_) {
55✔
1088
    write_dataset(lat_group, "pitch", pitch_);
22✔
1089
    write_dataset(lat_group, "center", center_);
22✔
1090
  } else {
1091
    array<double, 1> pitch_short {{pitch_[0]}};
33✔
1092
    write_dataset(lat_group, "pitch", pitch_short);
33✔
1093
    array<double, 2> center_short {{center_[0], center_[1]}};
33✔
1094
    write_dataset(lat_group, "center", center_short);
33✔
1095
  }
1096

1097
  // Write the universe ids.
1098
  hsize_t nx {static_cast<hsize_t>(2 * n_rings_ - 1)};
55✔
1099
  hsize_t ny {static_cast<hsize_t>(2 * n_rings_ - 1)};
55✔
1100
  hsize_t nz {static_cast<hsize_t>(n_axial_)};
55✔
1101
  vector<int> out(nx * ny * nz);
55✔
1102

1103
  for (int m = 0; m < nz; m++) {
143✔
1104
    for (int k = 0; k < ny; k++) {
1,364✔
1105
      for (int j = 0; j < nx; j++) {
26,004✔
1106
        int indx = nx * ny * m + nx * k + j;
24,728✔
1107
        if (j + k < n_rings_ - 1) {
24,728✔
1108
          // This array position is never used; put a -1 to indicate this.
1109
          out[indx] = -1;
3,080✔
1110
        } else if (j + k > 3 * n_rings_ - 3) {
21,648✔
1111
          // This array position is never used; put a -1 to indicate this.
1112
          out[indx] = -1;
3,080✔
1113
        } else {
1114
          out[indx] = model::universes[universes_[indx]]->id_;
18,568✔
1115
        }
1116
      }
1117
    }
1118
  }
1119

1120
  hsize_t dims[3] {nz, ny, nx};
55✔
1121
  write_int(lat_group, 3, dims, "universes", out.data(), false);
55✔
1122
}
55✔
1123

1124
//==============================================================================
1125
// Non-method functions
1126
//==============================================================================
1127

1128
void read_lattices(pugi::xml_node node)
6,904✔
1129
{
1130
  for (pugi::xml_node lat_node : node.children("lattice")) {
8,612✔
1131
    model::lattices.push_back(make_unique<RectLattice>(lat_node));
1,708✔
1132
  }
1133
  for (pugi::xml_node lat_node : node.children("hex_lattice")) {
6,995✔
1134
    model::lattices.push_back(make_unique<HexLattice>(lat_node));
91✔
1135
  }
1136

1137
  // Fill the lattice map.
1138
  for (int i_lat = 0; i_lat < model::lattices.size(); i_lat++) {
8,703✔
1139
    int id = model::lattices[i_lat]->id_;
1,799✔
1140
    auto in_map = model::lattice_map.find(id);
1,799✔
1141
    if (in_map == model::lattice_map.end()) {
1,799✔
1142
      model::lattice_map[id] = i_lat;
1,799✔
1143
    } else {
1144
      fatal_error(
×
1145
        fmt::format("Two or more lattices use the same unique ID: {}", id));
×
1146
    }
1147
  }
1148
}
6,904✔
1149

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