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

openmc-dev / openmc / 20645008691

01 Jan 2026 08:23PM UTC coverage: 82.174% (+0.004%) from 82.17%
20645008691

Pull #3703

github

web-flow
Merge b25cf8635 into f08326a9a
Pull Request #3703: Simplify lattice crossing and correctly handle corner checks

17072 of 23633 branches covered (72.24%)

Branch coverage included in aggregate %.

10 of 10 new or added lines in 1 file covered. (100.0%)

15 existing lines in 1 file now uncovered.

55184 of 64297 relevant lines covered (85.83%)

43397931.78 hits per line

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

90.19
/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,870✔
33
{
34
  if (check_for_node(lat_node, "id")) {
1,870!
35
    id_ = std::stoi(get_node_value(lat_node, "id"));
1,870✔
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,870✔
41
    name_ = get_node_value(lat_node, "name");
358✔
42
  }
43

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

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

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

56
LatticeIter Lattice::end()
49,435,687✔
57
{
58
  return LatticeIter(*this, universes_.size());
49,435,687✔
59
}
60

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

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

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

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

78
void Lattice::adjust_indices()
1,870✔
79
{
80
  // Adjust the indices for the universes array.
81
  for (LatticeIter it = begin(); it != end(); ++it) {
925,138✔
82
    int uid = *it;
923,268✔
83
    auto search = model::universe_map.find(uid);
923,268✔
84
    if (search != model::universe_map.end()) {
923,268!
85
      *it = search->second;
923,268✔
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,870✔
94
    auto search = model::universe_map.find(outer_);
403✔
95
    if (search != model::universe_map.end()) {
403!
96
      outer_ = search->second;
403✔
97
    } else {
98
      fatal_error(fmt::format(
×
99
        "Invalid universe number {} specified on lattice {}", outer_, id_));
×
100
    }
101
  }
102
}
1,870✔
103

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

106
int32_t Lattice::fill_offset_table(int32_t target_univ_id, int map,
12,740✔
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,740✔
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,707✔
123
  for (LatticeIter it = begin(); it != end(); ++it) {
9,287,126✔
124
    offsets_[map * universes_.size() + it.indx_] = offset;
9,274,419✔
125
    offset += count_universe_instances(*it, target_univ_id, univ_count_memo);
9,274,419✔
126
  }
127

128
  return offset;
12,707✔
129
}
130

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

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

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

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

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

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

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

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

166
  // Read the number of lattice cells in each dimension.
167
  std::string dimension_str {get_node_value(lat_node, "dimension")};
1,779✔
168
  vector<std::string> dimension_words {split(dimension_str)};
1,779✔
169
  if (dimension_words.size() == 2) {
1,779✔
170
    n_cells_[0] = std::stoi(dimension_words[0]);
1,317✔
171
    n_cells_[1] = std::stoi(dimension_words[1]);
1,317✔
172
    n_cells_[2] = 1;
1,317✔
173
    is_3d_ = false;
1,317✔
174
  } else if (dimension_words.size() == 3) {
462!
175
    n_cells_[0] = std::stoi(dimension_words[0]);
462✔
176
    n_cells_[1] = std::stoi(dimension_words[1]);
462✔
177
    n_cells_[2] = std::stoi(dimension_words[2]);
462✔
178
    is_3d_ = true;
462✔
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,779✔
185
  vector<std::string> ll_words {split(ll_str)};
1,779✔
186
  if (ll_words.size() != dimension_words.size()) {
1,779!
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,779✔
191
  lower_left_[1] = stod(ll_words[1]);
1,779✔
192
  if (is_3d_) {
1,779✔
193
    lower_left_[2] = stod(ll_words[2]);
462✔
194
  }
195

196
  // Read the lattice pitches.
197
  std::string pitch_str {get_node_value(lat_node, "pitch")};
1,779✔
198
  vector<std::string> pitch_words {split(pitch_str)};
1,779✔
199
  if (pitch_words.size() != dimension_words.size()) {
1,779!
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,779✔
204
  pitch_[1] = stod(pitch_words[1]);
1,779✔
205
  if (is_3d_) {
1,779✔
206
    pitch_[2] = stod(pitch_words[2]);
462✔
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,779✔
211
  vector<std::string> univ_words {split(univ_str)};
1,779✔
212
  if (univ_words.size() != n_cells_[0] * n_cells_[1] * n_cells_[2]) {
1,779!
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,779✔
222
  for (int iz = 0; iz < n_cells_[2]; iz++) {
8,046✔
223
    for (int iy = n_cells_[1] - 1; iy > -1; iy--) {
76,746✔
224
      for (int ix = 0; ix < n_cells_[0]; ix++) {
966,662✔
225
        int indx1 = n_cells_[0] * n_cells_[1] * iz +
896,183✔
226
                    n_cells_[0] * (n_cells_[1] - iy - 1) + ix;
896,183✔
227
        int indx2 = n_cells_[0] * n_cells_[1] * iz + n_cells_[0] * iy + ix;
896,183✔
228
        universes_[indx1] = std::stoi(univ_words[indx2]);
896,183✔
229
      }
230
    }
231
  }
232
}
1,779✔
233

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

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

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

243
bool RectLattice::are_valid_indices(const array<int, 3>& i_xyz) const
2,012,428,887✔
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,161,741,694✔
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,161,741,694✔
257
  double y = r.y;
1,161,741,694✔
258
  double z = r.z;
1,161,741,694✔
259

260
  // Determine the oncoming edge.
261
  double x0 {copysign(0.5 * pitch_[0], u.x)};
1,161,741,694✔
262
  double y0 {copysign(0.5 * pitch_[1], u.y)};
1,161,741,694✔
263
  double z0;
264

265
  double d = std::min((x0 - x) / u.x, (y0 - y) / u.y);
1,161,741,694✔
266
  if (is_3d_) {
1,161,741,694✔
267
    z0 = copysign(0.5 * pitch_[2], u.z);
679,695,662✔
268
    d = std::min(d, (z0 - z) / u.z);
679,695,662✔
269
  }
270

271
  array<int, 3> lattice_trans = {0, 0, 0};
1,161,741,694✔
272

273
  if (std::abs(x + u.x * d - x0) < FP_PRECISION)
1,161,741,694✔
274
    lattice_trans[0] = copysign(1, u.x);
501,411,584✔
275
  if (std::abs(y + u.y * d - y0) < FP_PRECISION)
1,161,741,694✔
276
    lattice_trans[1] = copysign(1, u.y);
451,385,371✔
277
  if (is_3d_) {
1,161,741,694✔
278
    if (std::abs(z + u.z * d - z0) < FP_PRECISION)
679,695,662✔
279
      lattice_trans[2] = copysign(1, u.z);
209,162,372✔
280
  }
281

282
  return {d, lattice_trans};
2,147,483,647✔
283
}
284

285
//==============================================================================
286

287
void RectLattice::get_indices(
131,738,116✔
288
  Position r, Direction u, array<int, 3>& result) const
289
{
290
  // Determine x index, accounting for coincidence
291
  double ix_ {(r.x - lower_left_.x) / pitch_.x};
131,738,116✔
292
  long ix_close {std::lround(ix_)};
131,738,116✔
293
  if (coincident(ix_, ix_close)) {
131,738,116✔
294
    result[0] = (u.x > 0) ? ix_close : ix_close - 1;
42,931,385✔
295
  } else {
296
    result[0] = std::floor(ix_);
88,806,731✔
297
  }
298

299
  // Determine y index, accounting for coincidence
300
  double iy_ {(r.y - lower_left_.y) / pitch_.y};
131,738,116✔
301
  long iy_close {std::lround(iy_)};
131,738,116✔
302
  if (coincident(iy_, iy_close)) {
131,738,116✔
303
    result[1] = (u.y > 0) ? iy_close : iy_close - 1;
43,372,446✔
304
  } else {
305
    result[1] = std::floor(iy_);
88,365,670✔
306
  }
307

308
  // Determine z index, accounting for coincidence
309
  result[2] = 0;
131,738,116✔
310
  if (is_3d_) {
131,738,116✔
311
    double iz_ {(r.z - lower_left_.z) / pitch_.z};
67,327,912✔
312
    long iz_close {std::lround(iz_)};
67,327,912✔
313
    if (coincident(iz_, iz_close)) {
67,327,912✔
314
      result[2] = (u.z > 0) ? iz_close : iz_close - 1;
17,834,973✔
315
    } else {
316
      result[2] = std::floor(iz_);
49,492,939✔
317
    }
318
  }
319
}
131,738,116✔
320

321
int RectLattice::get_flat_index(const array<int, 3>& i_xyz) const
824,640,709✔
322
{
323
  return n_cells_[0] * n_cells_[1] * i_xyz[2] + n_cells_[0] * i_xyz[1] +
824,640,709✔
324
         i_xyz[0];
824,640,709✔
325
}
326

327
//==============================================================================
328

329
Position RectLattice::get_local_position(
828,878,118✔
330
  Position r, const array<int, 3>& i_xyz) const
331
{
332
  r.x -= (lower_left_.x + (i_xyz[0] + 0.5) * pitch_.x);
828,878,118✔
333
  r.y -= (lower_left_.y + (i_xyz[1] + 0.5) * pitch_.y);
828,878,118✔
334
  if (is_3d_) {
828,878,118✔
335
    r.z -= (lower_left_.z + (i_xyz[2] + 0.5) * pitch_.z);
655,613,933✔
336
  }
337
  return r;
828,878,118✔
338
}
339

340
//==============================================================================
341

342
int32_t& RectLattice::offset(int map, const array<int, 3>& i_xyz)
1,180,961,050✔
343
{
344
  return offsets_[n_cells_[0] * n_cells_[1] * n_cells_[2] * map +
1,180,961,050✔
345
                  n_cells_[0] * n_cells_[1] * i_xyz[2] +
1,180,961,050✔
346
                  n_cells_[0] * i_xyz[1] + i_xyz[0]];
2,147,483,647✔
347
}
348

349
//==============================================================================
350

351
int32_t RectLattice::offset(int map, int indx) const
88,848✔
352
{
353
  return offsets_[n_cells_[0] * n_cells_[1] * n_cells_[2] * map + indx];
88,848✔
354
}
355

356
//==============================================================================
357

358
std::string RectLattice::index_to_string(int indx) const
1,638,296✔
359
{
360
  int iz {indx / (n_cells_[0] * n_cells_[1])};
1,638,296✔
361
  int iy {(indx - n_cells_[0] * n_cells_[1] * iz) / n_cells_[0]};
1,638,296✔
362
  int ix {indx - n_cells_[0] * n_cells_[1] * iz - n_cells_[0] * iy};
1,638,296✔
363
  std::string out {std::to_string(ix)};
1,638,296✔
364
  out += ',';
1,638,296✔
365
  out += std::to_string(iy);
1,638,296✔
366
  if (is_3d_) {
1,638,296!
UNCOV
367
    out += ',';
×
368
    out += std::to_string(iz);
×
369
  }
370
  return out;
1,638,296✔
UNCOV
371
}
×
372

373
//==============================================================================
374

375
void RectLattice::to_hdf5_inner(hid_t lat_group) const
1,288✔
376
{
377
  // Write basic lattice information.
378
  write_string(lat_group, "type", "rectangular", false);
1,288✔
379
  if (is_3d_) {
1,288✔
380
    write_dataset(lat_group, "pitch", pitch_);
337✔
381
    write_dataset(lat_group, "lower_left", lower_left_);
337✔
382
    write_dataset(lat_group, "dimension", n_cells_);
337✔
383
  } else {
384
    array<double, 2> pitch_short {{pitch_[0], pitch_[1]}};
951✔
385
    write_dataset(lat_group, "pitch", pitch_short);
951✔
386
    array<double, 2> ll_short {{lower_left_[0], lower_left_[1]}};
951✔
387
    write_dataset(lat_group, "lower_left", ll_short);
951✔
388
    array<int, 2> nc_short {{n_cells_[0], n_cells_[1]}};
951✔
389
    write_dataset(lat_group, "dimension", nc_short);
951✔
390
  }
391

392
  // Write the universe ids.  The convention here is to switch the ordering on
393
  // the y-axis to match the way universes are input in a text file.
394
  if (is_3d_) {
1,288✔
395
    hsize_t nx {static_cast<hsize_t>(n_cells_[0])};
337✔
396
    hsize_t ny {static_cast<hsize_t>(n_cells_[1])};
337✔
397
    hsize_t nz {static_cast<hsize_t>(n_cells_[2])};
337✔
398
    vector<int> out(nx * ny * nz);
337✔
399

400
    for (int m = 0; m < nz; m++) {
3,852✔
401
      for (int k = 0; k < ny; k++) {
44,552✔
402
        for (int j = 0; j < nx; j++) {
538,166✔
403
          int indx1 = nx * ny * m + nx * k + j;
497,129✔
404
          int indx2 = nx * ny * m + nx * (ny - k - 1) + j;
497,129✔
405
          out[indx2] = model::universes[universes_[indx1]]->id_;
497,129✔
406
        }
407
      }
408
    }
409

410
    hsize_t dims[3] {nz, ny, nx};
337✔
411
    write_int(lat_group, 3, dims, "universes", out.data(), false);
337✔
412

413
  } else {
337✔
414
    hsize_t nx {static_cast<hsize_t>(n_cells_[0])};
951✔
415
    hsize_t ny {static_cast<hsize_t>(n_cells_[1])};
951✔
416
    vector<int> out(nx * ny);
951✔
417

418
    for (int k = 0; k < ny; k++) {
9,756✔
419
      for (int j = 0; j < nx; j++) {
146,850✔
420
        int indx1 = nx * k + j;
138,045✔
421
        int indx2 = nx * (ny - k - 1) + j;
138,045✔
422
        out[indx2] = model::universes[universes_[indx1]]->id_;
138,045✔
423
      }
424
    }
425

426
    hsize_t dims[3] {1, ny, nx};
951✔
427
    write_int(lat_group, 3, dims, "universes", out.data(), false);
951✔
428
  }
951✔
429
}
1,288✔
430

431
//==============================================================================
432
// HexLattice implementation
433
//==============================================================================
434

435
HexLattice::HexLattice(pugi::xml_node lat_node) : Lattice {lat_node}
91✔
436
{
437
  type_ = LatticeType::hex;
91✔
438

439
  // Read the number of lattice cells in each dimension.
440
  n_rings_ = std::stoi(get_node_value(lat_node, "n_rings"));
91✔
441
  if (check_for_node(lat_node, "n_axial")) {
91✔
442
    n_axial_ = std::stoi(get_node_value(lat_node, "n_axial"));
32✔
443
    is_3d_ = true;
32✔
444
  } else {
445
    n_axial_ = 1;
59✔
446
    is_3d_ = false;
59✔
447
  }
448

449
  // Read the lattice orientation.  Default to 'y'.
450
  if (check_for_node(lat_node, "orientation")) {
91✔
451
    std::string orientation = get_node_value(lat_node, "orientation");
16✔
452
    if (orientation == "y") {
16!
UNCOV
453
      orientation_ = Orientation::y;
×
454
    } else if (orientation == "x") {
16!
455
      orientation_ = Orientation::x;
16✔
456
    } else {
UNCOV
457
      fatal_error("Unrecognized orientation '" + orientation +
×
458
                  "' for lattice " + std::to_string(id_));
×
459
    }
460
  } else {
16✔
461
    orientation_ = Orientation::y;
75✔
462
  }
463

464
  // Read the lattice center.
465
  std::string center_str {get_node_value(lat_node, "center")};
91✔
466
  vector<std::string> center_words {split(center_str)};
91✔
467
  if (is_3d_ && (center_words.size() != 3)) {
91!
UNCOV
468
    fatal_error("A hexagonal lattice with <n_axial> must have <center> "
×
469
                "specified by 3 numbers.");
470
  } else if (!is_3d_ && center_words.size() != 2) {
91!
UNCOV
471
    fatal_error("A hexagonal lattice without <n_axial> must have <center> "
×
472
                "specified by 2 numbers.");
473
  }
474
  center_[0] = stod(center_words[0]);
91✔
475
  center_[1] = stod(center_words[1]);
91✔
476
  if (is_3d_) {
91✔
477
    center_[2] = stod(center_words[2]);
32✔
478
  }
479

480
  // Read the lattice pitches.
481
  std::string pitch_str {get_node_value(lat_node, "pitch")};
91✔
482
  vector<std::string> pitch_words {split(pitch_str)};
91✔
483
  if (is_3d_ && (pitch_words.size() != 2)) {
91!
UNCOV
484
    fatal_error("A hexagonal lattice with <n_axial> must have <pitch> "
×
485
                "specified by 2 numbers.");
486
  } else if (!is_3d_ && (pitch_words.size() != 1)) {
91!
UNCOV
487
    fatal_error("A hexagonal lattice without <n_axial> must have <pitch> "
×
488
                "specified by 1 number.");
489
  }
490
  pitch_[0] = stod(pitch_words[0]);
91✔
491
  if (is_3d_) {
91✔
492
    pitch_[1] = stod(pitch_words[1]);
32✔
493
  }
494

495
  // Read the universes and make sure the correct number was specified.
496
  int n_univ = (3 * n_rings_ * n_rings_ - 3 * n_rings_ + 1) * n_axial_;
91✔
497
  std::string univ_str {get_node_value(lat_node, "universes")};
91✔
498
  vector<std::string> univ_words {split(univ_str)};
91✔
499
  if (univ_words.size() != n_univ) {
91!
UNCOV
500
    fatal_error(fmt::format(
×
501
      "Expected {} universes for a hexagonal lattice with {} rings and {} "
502
      "axial levels but {} were specified.",
UNCOV
503
      n_univ, n_rings_, n_axial_, univ_words.size()));
×
504
  }
505

506
  // Parse the universes.
507
  // Universes in hexagonal lattices are stored in a manner that represents
508
  // a skewed coordinate system: (x, alpha) in case of 'y' orientation
509
  // and (alpha,y) in 'x' one rather than (x, y).  There is
510
  // no obvious, direct relationship between the order of universes in the
511
  // input and the order that they will be stored in the skewed array so
512
  // the following code walks a set of index values across the skewed array
513
  // in a manner that matches the input order.  Note that i_x = 0, i_a = 0
514
  // or i_a = 0, i_y = 0 corresponds to the center of the hexagonal lattice.
515
  universes_.resize((2 * n_rings_ - 1) * (2 * n_rings_ - 1) * n_axial_, C_NONE);
91✔
516
  if (orientation_ == Orientation::y) {
91✔
517
    fill_lattice_y(univ_words);
75✔
518
  } else {
519
    fill_lattice_x(univ_words);
16✔
520
  }
521
}
91✔
522

523
//==============================================================================
524

525
void HexLattice::fill_lattice_x(const vector<std::string>& univ_words)
16✔
526
{
527
  int input_index = 0;
16✔
528
  for (int m = 0; m < n_axial_; m++) {
48✔
529
    // Initialize lattice indecies.
530
    int i_a = -(n_rings_ - 1);
32✔
531
    int i_y = n_rings_ - 1;
32✔
532

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

537
      // Iterate over the input columns.
538
      for (int j = 0; j < k + n_rings_; j++) {
5,280✔
539
        int indx = (2 * n_rings_ - 1) * (2 * n_rings_ - 1) * m +
4,960✔
540
                   (2 * n_rings_ - 1) * (i_y + n_rings_ - 1) +
4,960✔
541
                   (i_a + n_rings_ - 1);
4,960✔
542
        universes_[indx] = std::stoi(univ_words[input_index]);
4,960✔
543
        input_index++;
4,960✔
544
        // Move to the next right neighbour cell
545
        i_a += 1;
4,960✔
546
      }
547

548
      // Return the lattice index to the start of the current row.
549
      i_a = -(n_rings_ - 1);
320✔
550
      i_y -= 1;
320✔
551
    }
552

553
    // Map the lower region from the centerline of cart to down side
554
    for (int k = 0; k < n_rings_; k++) {
384✔
555
      // Walk the index to the lower-right neighbor of the last row start.
556
      i_a = -(n_rings_ - 1) + k;
352✔
557

558
      // Iterate over the input columns.
559
      for (int j = 0; j < 2 * n_rings_ - k - 1; j++) {
5,984✔
560
        int indx = (2 * n_rings_ - 1) * (2 * n_rings_ - 1) * m +
5,632✔
561
                   (2 * n_rings_ - 1) * (i_y + n_rings_ - 1) +
5,632✔
562
                   (i_a + n_rings_ - 1);
5,632✔
563
        universes_[indx] = std::stoi(univ_words[input_index]);
5,632✔
564
        input_index++;
5,632✔
565
        // Move to the next right neighbour cell
566
        i_a += 1;
5,632✔
567
      }
568

569
      // Return lattice index to start of current row.
570
      i_y -= 1;
352✔
571
    }
572
  }
573
}
16✔
574

575
//==============================================================================
576

577
void HexLattice::fill_lattice_y(const vector<std::string>& univ_words)
75✔
578
{
579
  int input_index = 0;
75✔
580
  for (int m = 0; m < n_axial_; m++) {
182✔
581
    // Initialize lattice indecies.
582
    int i_x = 1;
107✔
583
    int i_a = n_rings_ - 1;
107✔
584

585
    // Map upper triangular region of hexagonal lattice which is found in the
586
    // first n_rings-1 rows of the input.
587
    for (int k = 0; k < n_rings_ - 1; k++) {
662✔
588
      // Walk the index to lower-left neighbor of last row start.
589
      i_x -= 1;
555✔
590

591
      // Iterate over the input columns.
592
      for (int j = 0; j < k + 1; j++) {
3,286✔
593
        int indx = (2 * n_rings_ - 1) * (2 * n_rings_ - 1) * m +
2,731✔
594
                   (2 * n_rings_ - 1) * (i_a + n_rings_ - 1) +
2,731✔
595
                   (i_x + n_rings_ - 1);
2,731✔
596
        universes_[indx] = std::stoi(univ_words[input_index]);
2,731✔
597
        input_index++;
2,731✔
598
        // Walk the index to the right neighbor (which is not adjacent).
599
        i_x += 2;
2,731✔
600
        i_a -= 1;
2,731✔
601
      }
602

603
      // Return the lattice index to the start of the current row.
604
      i_x -= 2 * (k + 1);
555✔
605
      i_a += (k + 1);
555✔
606
    }
607

608
    // Map the middle square region of the hexagonal lattice which is found in
609
    // the next 2*n_rings-1 rows of the input.
610
    for (int k = 0; k < 2 * n_rings_ - 1; k++) {
1,324✔
611
      if ((k % 2) == 0) {
1,217✔
612
        // Walk the index to the lower-left neighbor of the last row start.
613
        i_x -= 1;
662✔
614
      } else {
615
        // Walk the index to the lower-right neighbor of the last row start.
616
        i_x += 1;
555✔
617
        i_a -= 1;
555✔
618
      }
619

620
      // Iterate over the input columns.
621
      for (int j = 0; j < n_rings_ - (k % 2); j++) {
12,248✔
622
        int indx = (2 * n_rings_ - 1) * (2 * n_rings_ - 1) * m +
11,031✔
623
                   (2 * n_rings_ - 1) * (i_a + n_rings_ - 1) +
11,031✔
624
                   (i_x + n_rings_ - 1);
11,031✔
625
        universes_[indx] = std::stoi(univ_words[input_index]);
11,031✔
626
        input_index++;
11,031✔
627
        // Walk the index to the right neighbor (which is not adjacent).
628
        i_x += 2;
11,031✔
629
        i_a -= 1;
11,031✔
630
      }
631

632
      // Return the lattice index to the start of the current row.
633
      i_x -= 2 * (n_rings_ - (k % 2));
1,217✔
634
      i_a += n_rings_ - (k % 2);
1,217✔
635
    }
636

637
    // Map the lower triangular region of the hexagonal lattice.
638
    for (int k = 0; k < n_rings_ - 1; k++) {
662✔
639
      // Walk the index to the lower-right neighbor of the last row start.
640
      i_x += 1;
555✔
641
      i_a -= 1;
555✔
642

643
      // Iterate over the input columns.
644
      for (int j = 0; j < n_rings_ - k - 1; j++) {
3,286✔
645
        int indx = (2 * n_rings_ - 1) * (2 * n_rings_ - 1) * m +
2,731✔
646
                   (2 * n_rings_ - 1) * (i_a + n_rings_ - 1) +
2,731✔
647
                   (i_x + n_rings_ - 1);
2,731✔
648
        universes_[indx] = std::stoi(univ_words[input_index]);
2,731✔
649
        input_index++;
2,731✔
650
        // Walk the index to the right neighbor (which is not adjacent).
651
        i_x += 2;
2,731✔
652
        i_a -= 1;
2,731✔
653
      }
654

655
      // Return lattice index to start of current row.
656
      i_x -= 2 * (n_rings_ - k - 1);
555✔
657
      i_a += n_rings_ - k - 1;
555✔
658
    }
659
  }
660
}
75✔
661

662
//==============================================================================
663

664
const int32_t& HexLattice::operator[](const array<int, 3>& i_xyz)
29,285,960✔
665
{
666
  return universes_[get_flat_index(i_xyz)];
29,285,960✔
667
}
668

669
//==============================================================================
670

671
// The HexLattice iterators need their own versions b/c the universes array is
672
// "square", meaning that it is allocated with entries that are intentionally
673
// left empty. As such, the iterator indices need to skip the empty entries to
674
// get cell instances and geometry paths correct. See the image in the Theory
675
// and Methodology section on "Hexagonal Lattice Indexing" for a visual of where
676
// the empty positions are.
677
LatticeIter HexLattice::begin()
2,151✔
678
{
679
  return LatticeIter(*this, n_rings_ - 1);
2,151✔
680
}
681

682
ReverseLatticeIter HexLattice::rbegin()
231✔
683
{
684
  return ReverseLatticeIter(*this, universes_.size() - n_rings_);
231✔
685
}
686

687
int32_t& HexLattice::back()
11✔
688
{
689
  return universes_[universes_.size() - n_rings_];
11✔
690
}
691

692
LatticeIter HexLattice::end()
685,917✔
693
{
694
  return LatticeIter(*this, universes_.size() - n_rings_ + 1);
685,917✔
695
}
696

697
ReverseLatticeIter HexLattice::rend()
924✔
698
{
699
  return ReverseLatticeIter(*this, n_rings_ - 2);
924✔
700
}
701

702
//==============================================================================
703

704
bool HexLattice::are_valid_indices(const array<int, 3>& i_xyz) const
113,662,828✔
705
{
706
  // Check if (x, alpha, z) indices are valid, accounting for number of rings
707
  return ((i_xyz[0] >= 0) && (i_xyz[1] >= 0) && (i_xyz[2] >= 0) &&
226,743,250✔
708
          (i_xyz[0] < 2 * n_rings_ - 1) && (i_xyz[1] < 2 * n_rings_ - 1) &&
110,342,797✔
709
          (i_xyz[0] + i_xyz[1] > n_rings_ - 2) &&
109,569,145✔
710
          (i_xyz[0] + i_xyz[1] < 3 * n_rings_ - 2) && (i_xyz[2] < n_axial_));
226,743,250✔
711
}
712

713
//==============================================================================
714

715
std::pair<double, array<int, 3>> HexLattice::distance(
85,503,297✔
716
  Position r, Direction u, const array<int, 3>& i_xyz) const
717
{
718
  // Short description of the direction vectors used here.  The beta, gamma, and
719
  // delta vectors point towards the flat sides of each hexagonal tile.
720
  // Y - orientation:
721
  //   basis0 = (1, 0)
722
  //   basis1 = (-1/sqrt(3), 1)   = +120 degrees from basis0
723
  //   beta   = (sqrt(3)/2, 1/2)  = +30 degrees from basis0
724
  //   gamma  = (sqrt(3)/2, -1/2) = -60 degrees from beta
725
  //   delta  = (0, 1)            = +60 degrees from beta
726
  // X - orientation:
727
  //   basis0 = (1/sqrt(3), -1)
728
  //   basis1 = (0, 1)            = +120 degrees from basis0
729
  //   beta   = (1, 0)            = +30 degrees from basis0
730
  //   gamma  = (1/2, -sqrt(3)/2) = -60 degrees from beta
731
  //   delta  = (1/2, sqrt(3)/2)  = +60 degrees from beta
732
  // The z-axis is considered separately.
733
  double beta_dir;
734
  double gamma_dir;
735
  double delta_dir;
736
  if (orientation_ == Orientation::y) {
85,503,297✔
737
    beta_dir = u.x * std::sqrt(3.0) / 2.0 + u.y / 2.0;
70,869,348✔
738
    gamma_dir = u.x * std::sqrt(3.0) / 2.0 - u.y / 2.0;
70,869,348✔
739
    delta_dir = u.y;
70,869,348✔
740
  } else {
741
    beta_dir = u.x;
14,633,949✔
742
    gamma_dir = u.x / 2.0 - u.y * std::sqrt(3.0) / 2.0;
14,633,949✔
743
    delta_dir = u.x / 2.0 + u.y * std::sqrt(3.0) / 2.0;
14,633,949✔
744
  }
745

746
  // Note that hexagonal lattice distance calculations are performed
747
  // using the particle's coordinates relative to the neighbor lattice
748
  // cells, not relative to the particle's current cell.  This is done
749
  // because there is significant disagreement between neighboring cells
750
  // on where the lattice boundary is due to finite precision issues.
751

752
  // beta direction
753
  double d {INFTY};
85,503,297✔
754
  array<int, 3> lattice_trans;
755
  double edge = -copysign(0.5 * pitch_[0], beta_dir); // Oncoming edge
85,503,297✔
756
  Position r_t;
85,503,297✔
757
  if (beta_dir > 0) {
85,503,297✔
758
    const array<int, 3> i_xyz_t {i_xyz[0] + 1, i_xyz[1], i_xyz[2]};
42,766,713✔
759
    r_t = get_local_position(r, i_xyz_t);
42,766,713✔
760
  } else {
761
    const array<int, 3> i_xyz_t {i_xyz[0] - 1, i_xyz[1], i_xyz[2]};
42,736,584✔
762
    r_t = get_local_position(r, i_xyz_t);
42,736,584✔
763
  }
764
  double beta;
765
  if (orientation_ == Orientation::y) {
85,503,297✔
766
    beta = r_t.x * std::sqrt(3.0) / 2.0 + r_t.y / 2.0;
70,869,348✔
767
  } else {
768
    beta = r_t.x;
14,633,949✔
769
  }
770
  if ((std::abs(beta - edge) > FP_PRECISION) && beta_dir != 0) {
85,503,297!
771
    d = (edge - beta) / beta_dir;
85,503,297✔
772
    if (beta_dir > 0) {
85,503,297✔
773
      lattice_trans = {1, 0, 0};
42,766,713✔
774
    } else {
775
      lattice_trans = {-1, 0, 0};
42,736,584✔
776
    }
777
  }
778

779
  // gamma direction
780
  edge = -copysign(0.5 * pitch_[0], gamma_dir);
85,503,297✔
781
  if (gamma_dir > 0) {
85,503,297✔
782
    const array<int, 3> i_xyz_t {i_xyz[0] + 1, i_xyz[1] - 1, i_xyz[2]};
42,833,450✔
783
    r_t = get_local_position(r, i_xyz_t);
42,833,450✔
784
  } else {
785
    const array<int, 3> i_xyz_t {i_xyz[0] - 1, i_xyz[1] + 1, i_xyz[2]};
42,669,847✔
786
    r_t = get_local_position(r, i_xyz_t);
42,669,847✔
787
  }
788
  double gamma;
789
  if (orientation_ == Orientation::y) {
85,503,297✔
790
    gamma = r_t.x * std::sqrt(3.0) / 2.0 - r_t.y / 2.0;
70,869,348✔
791
  } else {
792
    gamma = r_t.x / 2.0 - r_t.y * std::sqrt(3.0) / 2.0;
14,633,949✔
793
  }
794
  if ((std::abs(gamma - edge) > FP_PRECISION) && gamma_dir != 0) {
85,503,297!
795
    double this_d = (edge - gamma) / gamma_dir;
85,503,297✔
796
    if (this_d < d) {
85,503,297✔
797
      if (gamma_dir > 0) {
42,791,749✔
798
        lattice_trans = {1, -1, 0};
21,484,342✔
799
      } else {
800
        lattice_trans = {-1, 1, 0};
21,307,407✔
801
      }
802
      d = this_d;
42,791,749✔
803
    }
804
  }
805

806
  // delta direction
807
  edge = -copysign(0.5 * pitch_[0], delta_dir);
85,503,297✔
808
  if (delta_dir > 0) {
85,503,297✔
809
    const array<int, 3> i_xyz_t {i_xyz[0], i_xyz[1] + 1, i_xyz[2]};
42,674,093✔
810
    r_t = get_local_position(r, i_xyz_t);
42,674,093✔
811
  } else {
812
    const array<int, 3> i_xyz_t {i_xyz[0], i_xyz[1] - 1, i_xyz[2]};
42,829,204✔
813
    r_t = get_local_position(r, i_xyz_t);
42,829,204✔
814
  }
815
  double delta;
816
  if (orientation_ == Orientation::y) {
85,503,297✔
817
    delta = r_t.y;
70,869,348✔
818
  } else {
819
    delta = r_t.x / 2.0 + r_t.y * std::sqrt(3.0) / 2.0;
14,633,949✔
820
  }
821
  if ((std::abs(delta - edge) > FP_PRECISION) && delta_dir != 0) {
85,503,297!
822
    double this_d = (edge - delta) / delta_dir;
85,503,297✔
823
    if (this_d < d) {
85,503,297✔
824
      if (delta_dir > 0) {
28,519,040✔
825
        lattice_trans = {0, 1, 0};
14,234,682✔
826
      } else {
827
        lattice_trans = {0, -1, 0};
14,284,358✔
828
      }
829
      d = this_d;
28,519,040✔
830
    }
831
  }
832

833
  // Top and bottom sides
834
  if (is_3d_) {
85,503,297✔
835
    double z = r.z;
20,213,028✔
836
    double z0 {copysign(0.5 * pitch_[1], u.z)};
20,213,028✔
837
    if ((std::abs(z - z0) > FP_PRECISION) && u.z != 0) {
20,213,028!
838
      double this_d = (z0 - z) / u.z;
20,213,028✔
839
      if (this_d < d) {
20,213,028✔
840
        d = this_d;
3,062,268✔
841
        if (u.z > 0) {
3,062,268✔
842
          lattice_trans = {0, 0, 1};
1,527,053✔
843
        } else {
844
          lattice_trans = {0, 0, -1};
1,535,215✔
845
        }
846
        d = this_d;
3,062,268✔
847
      }
848
    }
849
  }
850

851
  return {d, lattice_trans};
171,006,594✔
852
}
853

854
//==============================================================================
855

856
void HexLattice::get_indices(
11,900,471✔
857
  Position r, Direction u, array<int, 3>& result) const
858
{
859
  // Offset the xyz by the lattice center.
860
  Position r_o {r.x - center_.x, r.y - center_.y, r.z};
11,900,471✔
861
  if (is_3d_) {
11,900,471✔
862
    r_o.z -= center_.z;
2,484,878✔
863
  }
864

865
  // Index the z direction, accounting for coincidence
866
  result[2] = 0;
11,900,471✔
867
  if (is_3d_) {
11,900,471✔
868
    double iz_ {r_o.z / pitch_[1] + 0.5 * n_axial_};
2,484,878✔
869
    long iz_close {std::lround(iz_)};
2,484,878✔
870
    if (coincident(iz_, iz_close)) {
2,484,878✔
871
      result[2] = (u.z > 0) ? iz_close : iz_close - 1;
765,699✔
872
    } else {
873
      result[2] = std::floor(iz_);
1,719,179✔
874
    }
875
  }
876

877
  if (orientation_ == Orientation::y) {
11,900,471✔
878
    // Convert coordinates into skewed bases.  The (x, alpha) basis is used to
879
    // find the index of the global coordinates to within 4 cells.
880
    double alpha = r_o.y - r_o.x / std::sqrt(3.0);
11,078,199✔
881
    result[0] = std::floor(r_o.x / (0.5 * std::sqrt(3.0) * pitch_[0]));
11,078,199✔
882
    result[1] = std::floor(alpha / pitch_[0]);
11,078,199✔
883
  } else {
884
    // Convert coordinates into skewed bases.  The (alpha, y) basis is used to
885
    // find the index of the global coordinates to within 4 cells.
886
    double alpha = r_o.y - r_o.x * std::sqrt(3.0);
822,272✔
887
    result[0] = std::floor(-alpha / (std::sqrt(3.0) * pitch_[0]));
822,272✔
888
    result[1] = std::floor(r_o.y / (0.5 * std::sqrt(3.0) * pitch_[0]));
822,272✔
889
  }
890

891
  // Add offset to indices (the center cell is (i1, i2) = (0, 0) but
892
  // the array is offset so that the indices never go below 0).
893
  result[0] += n_rings_ - 1;
11,900,471✔
894
  result[1] += n_rings_ - 1;
11,900,471✔
895

896
  // Calculate the (squared) distance between the particle and the centers of
897
  // the four possible cells.  Regular hexagonal tiles form a Voronoi
898
  // tessellation so the xyz should be in the hexagonal cell that it is closest
899
  // to the center of.  This method is used over a method that uses the
900
  // remainders of the floor divisions above because it provides better finite
901
  // precision performance.  Squared distances are used because they are more
902
  // computationally efficient than normal distances.
903

904
  // COINCIDENCE CHECK
905
  // if a distance to center, d, is within the coincidence tolerance of the
906
  // current minimum distance, d_min, the particle is on an edge or vertex.
907
  // In this case, the dot product of the position vector and direction vector
908
  // for the current indices, dp, and the dot product for the currently selected
909
  // indices, dp_min, are compared. The cell which the particle is moving into
910
  // is kept (i.e. the cell with the lowest dot product as the vectors will be
911
  // completely opposed if the particle is moving directly toward the center of
912
  // the cell).
913
  int i1_chg;
914
  int i2_chg;
915
  double d_min {INFTY};
11,900,471✔
916
  double dp_min {INFTY};
11,900,471✔
917
  for (int i = 0; i < 2; i++) {
35,701,413✔
918
    for (int j = 0; j < 2; j++) {
71,402,826✔
919
      // get local coordinates
920
      const array<int, 3> i_xyz {result[0] + j, result[1] + i, 0};
47,601,884✔
921
      Position r_t = get_local_position(r, i_xyz);
47,601,884✔
922
      // calculate distance
923
      double d = r_t.x * r_t.x + r_t.y * r_t.y;
47,601,884✔
924
      // check for coincidence. Because the numerical error incurred
925
      // in hex geometry is higher than other geometries, the relative
926
      // coincidence is checked here so that coincidence is successfully
927
      // detected on large hex lattice with particles far from the origin
928
      // which have rounding errors larger than the FP_COINCIDENT thresdhold.
929
      bool on_boundary = coincident(1.0, d_min / d);
47,601,884✔
930
      if (d < d_min || on_boundary) {
47,601,884✔
931
        // normalize r_t and find dot product
932
        r_t /= std::sqrt(d);
29,822,133✔
933
        double dp = u.x * r_t.x + u.y * r_t.y;
29,822,133✔
934
        // do not update values if particle is on a
935
        // boundary and not moving into this cell
936
        if (on_boundary && dp > dp_min)
29,822,133✔
937
          continue;
2,050,257✔
938
        // update values
939
        d_min = d;
27,771,876✔
940
        i1_chg = j;
27,771,876✔
941
        i2_chg = i;
27,771,876✔
942
        dp_min = dp;
27,771,876✔
943
      }
944
    }
945
  }
946

947
  // update outgoing indices
948
  result[0] += i1_chg;
11,900,471✔
949
  result[1] += i2_chg;
11,900,471✔
950
}
11,900,471✔
951

952
int HexLattice::get_flat_index(const array<int, 3>& i_xyz) const
29,285,960✔
953
{
954
  return (2 * n_rings_ - 1) * (2 * n_rings_ - 1) * i_xyz[2] +
29,285,960✔
955
         (2 * n_rings_ - 1) * i_xyz[1] + i_xyz[0];
29,285,960✔
956
}
957

958
//==============================================================================
959

960
Position HexLattice::get_local_position(
337,758,718✔
961
  Position r, const array<int, 3>& i_xyz) const
962
{
963
  if (orientation_ == Orientation::y) {
337,758,718✔
964
    // x_l = x_g - (center + pitch_x*cos(30)*index_x)
965
    r.x -=
286,269,500✔
966
      center_.x + std::sqrt(3.0) / 2.0 * (i_xyz[0] - n_rings_ + 1) * pitch_[0];
286,269,500✔
967
    // y_l = y_g - (center + pitch_x*index_x + pitch_y*sin(30)*index_y)
968
    r.y -= (center_.y + (i_xyz[1] - n_rings_ + 1) * pitch_[0] +
286,269,500✔
969
            (i_xyz[0] - n_rings_ + 1) * pitch_[0] / 2.0);
286,269,500✔
970
  } else {
971
    // x_l = x_g - (center + pitch_x*index_a + pitch_y*sin(30)*index_y)
972
    r.x -= (center_.x + (i_xyz[0] - n_rings_ + 1) * pitch_[0] +
51,489,218✔
973
            (i_xyz[1] - n_rings_ + 1) * pitch_[0] / 2.0);
51,489,218✔
974
    // y_l = y_g - (center + pitch_y*cos(30)*index_y)
975
    r.y -=
51,489,218✔
976
      center_.y + std::sqrt(3.0) / 2.0 * (i_xyz[1] - n_rings_ + 1) * pitch_[0];
51,489,218✔
977
  }
978

979
  if (is_3d_) {
337,758,718✔
980
    r.z -= center_.z - (0.5 * n_axial_ - i_xyz[2] - 0.5) * pitch_[1];
78,611,665✔
981
  }
982

983
  return r;
337,758,718✔
984
}
985

986
//==============================================================================
987

988
bool HexLattice::is_valid_index(int indx) const
387,424✔
989
{
990
  int nx {2 * n_rings_ - 1};
387,424✔
991
  int ny {2 * n_rings_ - 1};
387,424✔
992
  int iz = indx / (nx * ny);
387,424✔
993
  int iy = (indx - nx * ny * iz) / nx;
387,424✔
994
  int ix = indx - nx * ny * iz - nx * iy;
387,424✔
995
  array<int, 3> i_xyz {ix, iy, iz};
387,424✔
996
  return are_valid_indices(i_xyz);
774,848✔
997
}
998

999
//==============================================================================
1000

1001
int32_t& HexLattice::offset(int map, const array<int, 3>& i_xyz)
77,204,831✔
1002
{
1003
  int nx {2 * n_rings_ - 1};
77,204,831✔
1004
  int ny {2 * n_rings_ - 1};
77,204,831✔
1005
  int nz {n_axial_};
77,204,831✔
1006
  return offsets_[nx * ny * nz * map + nx * ny * i_xyz[2] + nx * i_xyz[1] +
77,204,831✔
1007
                  i_xyz[0]];
154,409,662✔
1008
}
1009

UNCOV
1010
int32_t HexLattice::offset(int map, int indx) const
×
1011
{
UNCOV
1012
  int nx {2 * n_rings_ - 1};
×
1013
  int ny {2 * n_rings_ - 1};
×
1014
  int nz {n_axial_};
×
1015
  return offsets_[nx * ny * nz * map + indx];
×
1016
}
1017

1018
//==============================================================================
1019

1020
std::string HexLattice::index_to_string(int indx) const
231✔
1021
{
1022
  int nx {2 * n_rings_ - 1};
231✔
1023
  int ny {2 * n_rings_ - 1};
231✔
1024
  int iz {indx / (nx * ny)};
231✔
1025
  int iy {(indx - nx * ny * iz) / nx};
231✔
1026
  int ix {indx - nx * ny * iz - nx * iy};
231✔
1027
  std::string out {std::to_string(ix - n_rings_ + 1)};
231✔
1028
  out += ',';
231✔
1029
  out += std::to_string(iy - n_rings_ + 1);
231✔
1030
  if (is_3d_) {
231!
UNCOV
1031
    out += ',';
×
1032
    out += std::to_string(iz);
×
1033
  }
1034
  return out;
231✔
UNCOV
1035
}
×
1036

1037
//==============================================================================
1038

1039
void HexLattice::to_hdf5_inner(hid_t lat_group) const
55✔
1040
{
1041
  // Write basic lattice information.
1042
  write_string(lat_group, "type", "hexagonal", false);
55✔
1043
  write_dataset(lat_group, "n_rings", n_rings_);
55✔
1044
  write_dataset(lat_group, "n_axial", n_axial_);
55✔
1045
  if (orientation_ == Orientation::y) {
55✔
1046
    write_string(lat_group, "orientation", "y", false);
44✔
1047
  } else {
1048
    write_string(lat_group, "orientation", "x", false);
11✔
1049
  }
1050
  if (is_3d_) {
55✔
1051
    write_dataset(lat_group, "pitch", pitch_);
22✔
1052
    write_dataset(lat_group, "center", center_);
22✔
1053
  } else {
1054
    array<double, 1> pitch_short {{pitch_[0]}};
33✔
1055
    write_dataset(lat_group, "pitch", pitch_short);
33✔
1056
    array<double, 2> center_short {{center_[0], center_[1]}};
33✔
1057
    write_dataset(lat_group, "center", center_short);
33✔
1058
  }
1059

1060
  // Write the universe ids.
1061
  hsize_t nx {static_cast<hsize_t>(2 * n_rings_ - 1)};
55✔
1062
  hsize_t ny {static_cast<hsize_t>(2 * n_rings_ - 1)};
55✔
1063
  hsize_t nz {static_cast<hsize_t>(n_axial_)};
55✔
1064
  vector<int> out(nx * ny * nz);
55✔
1065

1066
  for (int m = 0; m < nz; m++) {
143✔
1067
    for (int k = 0; k < ny; k++) {
1,364✔
1068
      for (int j = 0; j < nx; j++) {
26,004✔
1069
        int indx = nx * ny * m + nx * k + j;
24,728✔
1070
        if (j + k < n_rings_ - 1) {
24,728✔
1071
          // This array position is never used; put a -1 to indicate this.
1072
          out[indx] = -1;
3,080✔
1073
        } else if (j + k > 3 * n_rings_ - 3) {
21,648✔
1074
          // This array position is never used; put a -1 to indicate this.
1075
          out[indx] = -1;
3,080✔
1076
        } else {
1077
          out[indx] = model::universes[universes_[indx]]->id_;
18,568✔
1078
        }
1079
      }
1080
    }
1081
  }
1082

1083
  hsize_t dims[3] {nz, ny, nx};
55✔
1084
  write_int(lat_group, 3, dims, "universes", out.data(), false);
55✔
1085
}
55✔
1086

1087
//==============================================================================
1088
// Non-method functions
1089
//==============================================================================
1090

1091
void read_lattices(pugi::xml_node node)
7,619✔
1092
{
1093
  for (pugi::xml_node lat_node : node.children("lattice")) {
9,398✔
1094
    model::lattices.push_back(make_unique<RectLattice>(lat_node));
1,779✔
1095
  }
1096
  for (pugi::xml_node lat_node : node.children("hex_lattice")) {
7,710✔
1097
    model::lattices.push_back(make_unique<HexLattice>(lat_node));
91✔
1098
  }
1099

1100
  // Fill the lattice map.
1101
  for (int i_lat = 0; i_lat < model::lattices.size(); i_lat++) {
9,489✔
1102
    int id = model::lattices[i_lat]->id_;
1,870✔
1103
    auto in_map = model::lattice_map.find(id);
1,870✔
1104
    if (in_map == model::lattice_map.end()) {
1,870!
1105
      model::lattice_map[id] = i_lat;
1,870✔
1106
    } else {
UNCOV
1107
      fatal_error(
×
1108
        fmt::format("Two or more lattices use the same unique ID: {}", id));
×
1109
    }
1110
  }
1111
}
7,619✔
1112

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