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

openmc-dev / openmc / 13591584831

28 Feb 2025 03:46PM UTC coverage: 85.051% (+0.3%) from 84.722%
13591584831

Pull #3067

github

web-flow
Merge 08055e996 into c26fde666
Pull Request #3067: Implement user-configurable random number stride

36 of 44 new or added lines in 8 files covered. (81.82%)

3588 existing lines in 111 files now uncovered.

51062 of 60037 relevant lines covered (85.05%)

32650986.73 hits per line

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

71.77
/src/surface.cpp
1
#include "openmc/surface.h"
2

3
#include <cmath>
4
#include <complex>
5
#include <initializer_list>
6
#include <set>
7
#include <utility>
8

9
#include <fmt/core.h>
10

11
#include "openmc/array.h"
12
#include "openmc/container_util.h"
13
#include "openmc/error.h"
14
#include "openmc/external/quartic_solver.h"
15
#include "openmc/hdf5_interface.h"
16
#include "openmc/math_functions.h"
17
#include "openmc/random_lcg.h"
18
#include "openmc/settings.h"
19
#include "openmc/string_utils.h"
20
#include "openmc/xml_interface.h"
21

22
namespace openmc {
23

24
//==============================================================================
25
// Global variables
26
//==============================================================================
27

28
namespace model {
29
std::unordered_map<int, int> surface_map;
30
vector<unique_ptr<Surface>> surfaces;
31
} // namespace model
32

33
//==============================================================================
34
// Helper functions for reading the "coeffs" node of an XML surface element
35
//==============================================================================
36

37
void read_coeffs(
36,955✔
38
  pugi::xml_node surf_node, int surf_id, std::initializer_list<double*> coeffs)
39
{
40
  // Check the given number of coefficients.
41
  auto coeffs_file = get_node_array<double>(surf_node, "coeffs");
36,955✔
42
  if (coeffs_file.size() != coeffs.size()) {
36,955✔
UNCOV
43
    fatal_error(
×
44
      fmt::format("Surface {} expects {} coefficient but was given {}", surf_id,
×
45
        coeffs.size(), coeffs_file.size()));
×
46
  }
47

48
  // Copy the coefficients
49
  int i = 0;
36,955✔
50
  for (auto c : coeffs) {
113,360✔
51
    *c = coeffs_file[i++];
76,405✔
52
  }
53
}
36,955✔
54

55
//==============================================================================
56
// Surface implementation
57
//==============================================================================
58

59
Surface::Surface() {} // empty constructor
789✔
60

61
Surface::Surface(pugi::xml_node surf_node)
36,955✔
62
{
63
  if (check_for_node(surf_node, "id")) {
36,955✔
64
    id_ = std::stoi(get_node_value(surf_node, "id"));
36,955✔
65
    if (contains(settings::source_write_surf_id, id_) ||
73,058✔
66
        settings::source_write_surf_id.empty()) {
36,103✔
67
      surf_source_ = true;
35,848✔
68
    }
69
  } else {
UNCOV
70
    fatal_error("Must specify id of surface in geometry XML file.");
×
71
  }
72

73
  if (check_for_node(surf_node, "name")) {
36,955✔
74
    name_ = get_node_value(surf_node, "name", false);
8,108✔
75
  }
76

77
  if (check_for_node(surf_node, "boundary")) {
36,955✔
78
    std::string surf_bc = get_node_value(surf_node, "boundary", true, true);
21,485✔
79

80
    if (surf_bc == "transmission" || surf_bc == "transmit" || surf_bc.empty()) {
21,485✔
81
      // Leave the bc_ a nullptr
82
    } else if (surf_bc == "vacuum") {
21,485✔
83
      bc_ = make_unique<VacuumBC>();
10,755✔
84
    } else if (surf_bc == "reflective" || surf_bc == "reflect" ||
11,080✔
85
               surf_bc == "reflecting") {
350✔
86
      bc_ = make_unique<ReflectiveBC>();
10,380✔
87
    } else if (surf_bc == "white") {
350✔
88
      bc_ = make_unique<WhiteBC>();
102✔
89
    } else if (surf_bc == "periodic") {
248✔
90
      // Periodic BCs are handled separately
91
    } else {
UNCOV
92
      fatal_error(fmt::format("Unknown boundary condition \"{}\" specified "
×
93
                              "on surface {}",
UNCOV
94
        surf_bc, id_));
×
95
    }
96

97
    if (check_for_node(surf_node, "albedo") && bc_) {
21,485✔
98
      double surf_alb = std::stod(get_node_value(surf_node, "albedo"));
102✔
99

100
      if (surf_alb < 0.0)
102✔
UNCOV
101
        fatal_error(fmt::format("Surface {} has an albedo of {}. "
×
102
                                "Albedo values must be positive.",
UNCOV
103
          id_, surf_alb));
×
104

105
      if (surf_alb > 1.0)
102✔
UNCOV
106
        warning(fmt::format("Surface {} has an albedo of {}. "
×
107
                            "Albedos greater than 1 may cause "
108
                            "unphysical behaviour.",
UNCOV
109
          id_, surf_alb));
×
110

111
      bc_->set_albedo(surf_alb);
102✔
112
    }
113
  }
21,485✔
114
}
36,955✔
115

116
bool Surface::sense(Position r, Direction u) const
2,147,483,647✔
117
{
118
  // Evaluate the surface equation at the particle's coordinates to determine
119
  // which side the particle is on.
120
  const double f = evaluate(r);
2,147,483,647✔
121

122
  // Check which side of surface the point is on.
123
  if (std::abs(f) < FP_COINCIDENT) {
2,147,483,647✔
124
    // Particle may be coincident with this surface. To determine the sense, we
125
    // look at the direction of the particle relative to the surface normal (by
126
    // default in the positive direction) via their dot product.
127
    return u.dot(normal(r)) > 0.0;
1,440,847✔
128
  }
129
  return f > 0.0;
2,147,483,647✔
130
}
131

132
Direction Surface::reflect(Position r, Direction u, GeometryState* p) const
607,495,085✔
133
{
134
  // Determine projection of direction onto normal and squared magnitude of
135
  // normal.
136
  Direction n = normal(r);
607,495,085✔
137

138
  // Reflect direction according to normal.
139
  return u.reflect(n);
1,214,990,170✔
140
}
141

142
Direction Surface::diffuse_reflect(
1,109,436✔
143
  Position r, Direction u, uint64_t* seed) const
144
{
145
  // Diffuse reflect direction according to the normal.
146
  // cosine distribution
147

148
  Direction n = this->normal(r);
1,109,436✔
149
  n /= n.norm();
1,109,436✔
150
  const double projection = n.dot(u);
1,109,436✔
151

152
  // sample from inverse function, u=sqrt(rand) since p(u)=2u, so F(u)=u^2
153
  const double mu =
154
    (projection >= 0.0) ? -std::sqrt(prn(seed)) : std::sqrt(prn(seed));
1,109,436✔
155

156
  // sample azimuthal distribution uniformly
157
  u = rotate_angle(n, mu, nullptr, seed);
1,109,436✔
158

159
  // normalize the direction
160
  return u / u.norm();
2,218,872✔
161
}
162

163
void Surface::to_hdf5(hid_t group_id) const
29,712✔
164
{
165
  hid_t surf_group = create_group(group_id, fmt::format("surface {}", id_));
59,424✔
166

167
  if (geom_type() == GeometryType::DAG) {
29,712✔
168
    write_string(surf_group, "geom_type", "dagmc", false);
594✔
169
  } else if (geom_type() == GeometryType::CSG) {
29,118✔
170
    write_string(surf_group, "geom_type", "csg", false);
29,118✔
171

172
    if (bc_) {
29,118✔
173
      write_string(surf_group, "boundary_type", bc_->type(), false);
17,122✔
174
      bc_->to_hdf5(surf_group);
17,122✔
175
    } else {
176
      write_string(surf_group, "boundary_type", "transmission", false);
11,996✔
177
    }
178
  }
179

180
  if (!name_.empty()) {
29,712✔
181
    write_string(surf_group, "name", name_, false);
6,058✔
182
  }
183

184
  to_hdf5_inner(surf_group);
29,712✔
185

186
  close_group(surf_group);
29,712✔
187
}
29,712✔
188

189
//==============================================================================
190
// Generic functions for x-, y-, and z-, planes.
191
//==============================================================================
192

193
// The template parameter indicates the axis normal to the plane.
194
template<int i>
195
double axis_aligned_plane_distance(
2,147,483,647✔
196
  Position r, Direction u, bool coincident, double offset)
197
{
198
  const double f = offset - r[i];
2,147,483,647✔
199
  if (coincident || std::abs(f) < FP_COINCIDENT || u[i] == 0.0)
2,147,483,647✔
200
    return INFTY;
606,382,187✔
201
  const double d = f / u[i];
2,147,483,647✔
202
  if (d < 0.0)
2,147,483,647✔
203
    return INFTY;
2,147,483,647✔
204
  return d;
2,147,483,647✔
205
}
206

1,760,590,694✔
207
//==============================================================================
208
// SurfaceXPlane implementation
209
//==============================================================================
1,760,590,694✔
210

1,760,590,694✔
211
SurfaceXPlane::SurfaceXPlane(pugi::xml_node surf_node) : Surface(surf_node)
48,053,175✔
212
{
1,712,537,519✔
213
  read_coeffs(surf_node, id_, {&x0_});
1,712,537,519✔
214
}
821,214,802✔
215

891,322,717✔
216
double SurfaceXPlane::evaluate(Position r) const
217
{
2,147,483,647✔
218
  return r.x - x0_;
219
}
220

2,147,483,647✔
221
double SurfaceXPlane::distance(Position r, Direction u, bool coincident) const
2,147,483,647✔
222
{
290,838,819✔
223
  return axis_aligned_plane_distance<0>(r, u, coincident, x0_);
2,147,483,647✔
224
}
2,147,483,647✔
225

2,147,483,647✔
226
Direction SurfaceXPlane::normal(Position r) const
2,147,483,647✔
227
{
228
  return {1., 0., 0.};
2,147,483,647✔
229
}
230

231
void SurfaceXPlane::to_hdf5_inner(hid_t group_id) const
2,147,483,647✔
232
{
2,147,483,647✔
233
  write_string(group_id, "type", "x-plane", false);
267,490,193✔
234
  array<double, 1> coeffs {{x0_}};
2,147,483,647✔
235
  write_dataset(group_id, "coefficients", coeffs);
2,147,483,647✔
236
}
2,147,483,647✔
237

2,147,483,647✔
238
BoundingBox SurfaceXPlane::bounding_box(bool pos_side) const
239
{
240
  if (pos_side) {
241
    return {x0_, INFTY, -INFTY, INFTY, -INFTY, INFTY};
242
  } else {
243
    return {-INFTY, x0_, -INFTY, INFTY, -INFTY, INFTY};
244
  }
8,739✔
245
}
246

8,739✔
247
//==============================================================================
8,739✔
248
// SurfaceYPlane implementation
249
//==============================================================================
1,536,624,965✔
250

251
SurfaceYPlane::SurfaceYPlane(pugi::xml_node surf_node) : Surface(surf_node)
1,536,624,965✔
252
{
253
  read_coeffs(surf_node, id_, {&y0_});
254
}
2,147,483,647✔
255

256
double SurfaceYPlane::evaluate(Position r) const
2,147,483,647✔
257
{
258
  return r.y - y0_;
259
}
268,116,813✔
260

261
double SurfaceYPlane::distance(Position r, Direction u, bool coincident) const
268,116,813✔
262
{
263
  return axis_aligned_plane_distance<1>(r, u, coincident, y0_);
264
}
6,806✔
265

266
Direction SurfaceYPlane::normal(Position r) const
6,806✔
267
{
6,806✔
268
  return {0., 1., 0.};
6,806✔
269
}
6,806✔
270

271
void SurfaceYPlane::to_hdf5_inner(hid_t group_id) const
264✔
272
{
273
  write_string(group_id, "type", "y-plane", false);
264✔
274
  array<double, 1> coeffs {{y0_}};
132✔
275
  write_dataset(group_id, "coefficients", coeffs);
276
}
132✔
277

278
BoundingBox SurfaceYPlane::bounding_box(bool pos_side) const
279
{
280
  if (pos_side) {
281
    return {-INFTY, INFTY, y0_, INFTY, -INFTY, INFTY};
282
  } else {
283
    return {-INFTY, INFTY, -INFTY, y0_, -INFTY, INFTY};
284
  }
7,724✔
285
}
286

7,724✔
287
//==============================================================================
7,724✔
288
// SurfaceZPlane implementation
289
//==============================================================================
1,536,195,826✔
290

291
SurfaceZPlane::SurfaceZPlane(pugi::xml_node surf_node) : Surface(surf_node)
1,536,195,826✔
292
{
293
  read_coeffs(surf_node, id_, {&z0_});
294
}
2,147,483,647✔
295

296
double SurfaceZPlane::evaluate(Position r) const
2,147,483,647✔
297
{
298
  return r.z - z0_;
299
}
292,317,095✔
300

301
double SurfaceZPlane::distance(Position r, Direction u, bool coincident) const
292,317,095✔
302
{
303
  return axis_aligned_plane_distance<2>(r, u, coincident, z0_);
304
}
6,160✔
305

306
Direction SurfaceZPlane::normal(Position r) const
6,160✔
307
{
6,160✔
308
  return {0., 0., 1.};
6,160✔
309
}
6,160✔
310

311
void SurfaceZPlane::to_hdf5_inner(hid_t group_id) const
264✔
312
{
313
  write_string(group_id, "type", "z-plane", false);
264✔
314
  array<double, 1> coeffs {{z0_}};
132✔
315
  write_dataset(group_id, "coefficients", coeffs);
316
}
132✔
317

318
BoundingBox SurfaceZPlane::bounding_box(bool pos_side) const
319
{
320
  if (pos_side) {
321
    return {-INFTY, INFTY, -INFTY, INFTY, z0_, INFTY};
322
  } else {
323
    return {-INFTY, INFTY, -INFTY, INFTY, -INFTY, z0_};
324
  }
5,998✔
325
}
326

5,998✔
327
//==============================================================================
5,998✔
328
// SurfacePlane implementation
329
//==============================================================================
456,127,624✔
330

331
SurfacePlane::SurfacePlane(pugi::xml_node surf_node) : Surface(surf_node)
456,127,624✔
332
{
333
  read_coeffs(surf_node, id_, {&A_, &B_, &C_, &D_});
334
}
1,760,590,694✔
335

336
double SurfacePlane::evaluate(Position r) const
1,760,590,694✔
337
{
338
  return A_ * r.x + B_ * r.y + C_ * r.z - D_;
339
}
48,660,701✔
340

341
double SurfacePlane::distance(Position r, Direction u, bool coincident) const
48,660,701✔
342
{
343
  const double f = A_ * r.x + B_ * r.y + C_ * r.z - D_;
344
  const double projection = A_ * u.x + B_ * u.y + C_ * u.z;
5,112✔
345
  if (coincident || std::abs(f) < FP_COINCIDENT || projection == 0.0) {
346
    return INFTY;
5,112✔
347
  } else {
5,112✔
348
    const double d = -f / projection;
5,112✔
349
    if (d < 0.0)
5,112✔
350
      return INFTY;
UNCOV
351
    return d;
×
352
  }
UNCOV
353
}
×
UNCOV
354

×
355
Direction SurfacePlane::normal(Position r) const
UNCOV
356
{
×
357
  return {A_, B_, C_};
358
}
359

360
void SurfacePlane::to_hdf5_inner(hid_t group_id) const
361
{
362
  write_string(group_id, "type", "plane", false);
363
  array<double, 4> coeffs {{A_, B_, C_, D_}};
364
  write_dataset(group_id, "coefficients", coeffs);
1,474✔
365
}
366

1,474✔
367
//==============================================================================
1,474✔
368
// Generic functions for x-, y-, and z-, cylinders
369
//==============================================================================
209,096,701✔
370

371
// The template parameters indicate the axes perpendicular to the axis of the
209,096,701✔
372
// cylinder.  offset1 and offset2 should correspond with i1 and i2,
373
// respectively.
374
template<int i1, int i2>
616,072,968✔
375
double axis_aligned_cylinder_evaluate(
376
  Position r, double offset1, double offset2, double radius)
616,072,968✔
377
{
616,072,968✔
378
  const double r1 = r.get<i1>() - offset1;
616,072,968✔
379
  const double r2 = r.get<i2>() - offset2;
36,826,862✔
380
  return r1 * r1 + r2 * r2 - radius * radius;
381
}
579,246,106✔
382

579,246,106✔
383
// The first template parameter indicates which axis the cylinder is aligned to.
271,132,362✔
384
// The other two parameters indicate the other two axes.  offset1 and offset2
308,113,744✔
385
// should correspond with i2 and i3, respectively.
386
template<int i1, int i2, int i3>
387
double axis_aligned_cylinder_distance(Position r, Direction u, bool coincident,
388
  double offset1, double offset2, double radius)
6,322,260✔
389
{
390
  const double a = 1.0 - u.get<i1>() * u.get<i1>(); // u^2 + v^2
6,322,260✔
391
  if (a == 0.0)
392
    return INFTY;
393

1,036✔
394
  const double r2 = r.get<i2>() - offset1;
395
  const double r3 = r.get<i3>() - offset2;
1,036✔
396
  const double k = r2 * u.get<i2>() + r3 * u.get<i3>();
1,036✔
397
  const double c = r2 * r2 + r3 * r3 - radius * radius;
1,036✔
398
  const double quad = k * k - a * c;
1,036✔
399

400
  if (quad < 0.0) {
401
    // No intersection with cylinder.
402
    return INFTY;
403

404
  } else if (coincident || std::abs(c) < FP_COINCIDENT) {
405
    // Particle is on the cylinder, thus one distance is positive/negative
406
    // and the other is zero. The sign of k determines if we are facing in or
407
    // out.
408
    if (k >= 0.0) {
1,482,633,401✔
409
      return INFTY;
410
    } else {
411
      return (-k + sqrt(quad)) / a;
1,482,633,401✔
412
    }
1,482,633,401✔
413

1,482,633,401✔
414
  } else if (c < 0.0) {
415
    // Particle is inside the cylinder, thus one distance must be negative
1,481,137,715✔
416
    // and one must be positive. The positive distance will be the one with
417
    // negative sign on sqrt(quad).
418
    return (-k + sqrt(quad)) / a;
1,481,137,715✔
419

1,481,137,715✔
420
  } else {
1,481,137,715✔
421
    // Particle is outside the cylinder, thus both distances are either
UNCOV
422
    // positive or negative. If positive, the smaller distance is the one
×
423
    // with positive sign on sqrt(quad).
424
    const double d = (-k - sqrt(quad)) / a;
UNCOV
425
    if (d < 0.0)
×
UNCOV
426
      return INFTY;
×
UNCOV
427
    return d;
×
428
  }
429
}
1,495,686✔
430

431
// The first template parameter indicates which axis the cylinder is aligned to.
432
// The other two parameters indicate the other two axes.  offset1 and offset2
1,495,686✔
433
// should correspond with i2 and i3, respectively.
1,495,686✔
434
template<int i1, int i2, int i3>
1,495,686✔
435
Direction axis_aligned_cylinder_normal(
436
  Position r, double offset1, double offset2)
437
{
438
  Direction u;
439
  u.get<i2>() = 2.0 * (r.get<i2>() - offset1);
440
  u.get<i3>() = 2.0 * (r.get<i3>() - offset2);
441
  u.get<i1>() = 0.0;
1,709,566,355✔
442
  return u;
443
}
444

1,709,566,355✔
445
//==============================================================================
1,709,566,355✔
446
// SurfaceXCylinder implementation
883,632✔
447
//==============================================================================
448

1,708,682,723✔
449
SurfaceXCylinder::SurfaceXCylinder(pugi::xml_node surf_node)
1,708,682,723✔
450
  : Surface(surf_node)
1,708,682,723✔
451
{
1,708,682,723✔
452
  read_coeffs(surf_node, id_, {&y0_, &z0_, &radius_});
1,708,682,723✔
453
}
454

1,708,682,723✔
455
double SurfaceXCylinder::evaluate(Position r) const
456
{
285,611,407✔
457
  return axis_aligned_cylinder_evaluate<1, 2>(r, y0_, z0_, radius_);
458
}
1,423,071,316✔
459

460
double SurfaceXCylinder::distance(
461
  Position r, Direction u, bool coincident) const
462
{
643,652,773✔
463
  return axis_aligned_cylinder_distance<0, 1, 2>(
323,005,300✔
464
    r, u, coincident, y0_, z0_, radius_);
465
}
320,647,473✔
466

467
Direction SurfaceXCylinder::normal(Position r) const
468
{
779,418,543✔
469
  return axis_aligned_cylinder_normal<0, 1, 2>(r, y0_, z0_);
470
}
471

472
void SurfaceXCylinder::to_hdf5_inner(hid_t group_id) const
315,698,733✔
473
{
474
  write_string(group_id, "type", "x-cylinder", false);
475
  array<double, 3> coeffs {{y0_, z0_, radius_}};
476
  write_dataset(group_id, "coefficients", coeffs);
477
}
478

463,719,810✔
479
BoundingBox SurfaceXCylinder::bounding_box(bool pos_side) const
463,719,810✔
480
{
68,505,162✔
481
  if (!pos_side) {
395,214,648✔
482
    return {-INFTY, INFTY, y0_ - radius_, y0_ + radius_, z0_ - radius_,
483
      z0_ + radius_};
484
  } else {
1,708,379,123✔
485
    return {};
486
  }
487
}
1,708,379,123✔
488
//==============================================================================
1,708,379,123✔
489
// SurfaceYCylinder implementation
403,632✔
490
//==============================================================================
491

1,707,975,491✔
492
SurfaceYCylinder::SurfaceYCylinder(pugi::xml_node surf_node)
1,707,975,491✔
493
  : Surface(surf_node)
1,707,975,491✔
494
{
1,707,975,491✔
495
  read_coeffs(surf_node, id_, {&x0_, &z0_, &radius_});
1,707,975,491✔
496
}
497

1,707,975,491✔
498
double SurfaceYCylinder::evaluate(Position r) const
499
{
285,611,407✔
500
  return axis_aligned_cylinder_evaluate<0, 2>(r, x0_, z0_, radius_);
501
}
1,422,364,084✔
502

503
double SurfaceYCylinder::distance(
504
  Position r, Direction u, bool coincident) const
505
{
643,652,773✔
506
  return axis_aligned_cylinder_distance<1, 0, 2>(
323,005,300✔
507
    r, u, coincident, x0_, z0_, radius_);
508
}
320,647,473✔
509

510
Direction SurfaceYCylinder::normal(Position r) const
511
{
778,711,311✔
512
  return axis_aligned_cylinder_normal<1, 0, 2>(r, x0_, z0_);
513
}
514

515
void SurfaceYCylinder::to_hdf5_inner(hid_t group_id) const
314,991,501✔
516
{
517
  write_string(group_id, "type", "y-cylinder", false);
518
  array<double, 3> coeffs {{x0_, z0_, radius_}};
519
  write_dataset(group_id, "coefficients", coeffs);
520
}
521

463,719,810✔
522
BoundingBox SurfaceYCylinder::bounding_box(bool pos_side) const
463,719,810✔
523
{
68,505,162✔
524
  if (!pos_side) {
395,214,648✔
525
    return {x0_ - radius_, x0_ + radius_, -INFTY, INFTY, z0_ - radius_,
526
      z0_ + radius_};
UNCOV
527
  } else {
×
528
    return {};
529
  }
UNCOV
530
}
×
UNCOV
531

×
UNCOV
532
//==============================================================================
×
533
// SurfaceZCylinder implementation
UNCOV
534
//==============================================================================
×
UNCOV
535

×
UNCOV
536
SurfaceZCylinder::SurfaceZCylinder(pugi::xml_node surf_node)
×
537
  : Surface(surf_node)
×
UNCOV
538
{
×
539
  read_coeffs(surf_node, id_, {&x0_, &y0_, &radius_});
540
}
×
541

542
double SurfaceZCylinder::evaluate(Position r) const
×
543
{
544
  return axis_aligned_cylinder_evaluate<0, 1>(r, x0_, y0_, radius_);
×
545
}
546

547
double SurfaceZCylinder::distance(
548
  Position r, Direction u, bool coincident) const
×
UNCOV
549
{
×
550
  return axis_aligned_cylinder_distance<2, 0, 1>(
UNCOV
551
    r, u, coincident, x0_, y0_, radius_);
×
552
}
553

554
Direction SurfaceZCylinder::normal(Position r) const
×
555
{
556
  return axis_aligned_cylinder_normal<2, 0, 1>(r, x0_, y0_);
557
}
558

×
559
void SurfaceZCylinder::to_hdf5_inner(hid_t group_id) const
560
{
561
  write_string(group_id, "type", "z-cylinder", false);
562
  array<double, 3> coeffs {{x0_, y0_, radius_}};
563
  write_dataset(group_id, "coefficients", coeffs);
564
}
×
UNCOV
565

×
UNCOV
566
BoundingBox SurfaceZCylinder::bounding_box(bool pos_side) const
×
UNCOV
567
{
×
568
  if (!pos_side) {
569
    return {x0_ - radius_, x0_ + radius_, y0_ - radius_, y0_ + radius_, -INFTY,
570
      INFTY};
1,187,232✔
571
  } else {
572
    return {};
573
  }
1,187,232✔
574
}
1,187,232✔
575

480,000✔
576
//==============================================================================
577
// SurfaceSphere implementation
707,232✔
578
//==============================================================================
707,232✔
579

707,232✔
580
SurfaceSphere::SurfaceSphere(pugi::xml_node surf_node) : Surface(surf_node)
707,232✔
581
{
707,232✔
582
  read_coeffs(surf_node, id_, {&x0_, &y0_, &z0_, &radius_});
583
}
707,232✔
584

UNCOV
585
double SurfaceSphere::evaluate(Position r) const
×
586
{
587
  const double x = r.x - x0_;
707,232✔
588
  const double y = r.y - y0_;
589
  const double z = r.z - z0_;
590
  return x * x + y * y + z * z - radius_ * radius_;
UNCOV
591
}
×
UNCOV
592

×
593
double SurfaceSphere::distance(Position r, Direction u, bool coincident) const
UNCOV
594
{
×
595
  const double x = r.x - x0_;
596
  const double y = r.y - y0_;
597
  const double z = r.z - z0_;
707,232✔
598
  const double k = x * u.x + y * u.y + z * u.z;
599
  const double c = x * x + y * y + z * z - radius_ * radius_;
600
  const double quad = k * k - c;
601

707,232✔
602
  if (quad < 0.0) {
603
    // No intersection with sphere.
604
    return INFTY;
605

606
  } else if (coincident || std::abs(c) < FP_COINCIDENT) {
UNCOV
607
    // Particle is on the sphere, thus one distance is positive/negative and
×
UNCOV
608
    // the other is zero. The sign of k determines if we are facing in or out.
×
UNCOV
609
    if (k >= 0.0) {
×
UNCOV
610
      return INFTY;
×
611
    } else {
612
      return -k + sqrt(quad);
613
    }
614

615
  } else if (c < 0.0) {
616
    // Particle is inside the sphere, thus one distance must be negative and
617
    // one must be positive. The positive distance will be the one with
618
    // negative sign on sqrt(quad)
1,519,490✔
619
    return -k + sqrt(quad);
620

621
  } else {
1,519,490✔
622
    // Particle is outside the sphere, thus both distances are either positive
1,519,490✔
623
    // or negative. If positive, the smaller distance is the one with positive
1,519,490✔
624
    // sign on sqrt(quad).
1,519,490✔
625
    const double d = -k - sqrt(quad);
1,519,490✔
626
    if (d < 0.0)
627
      return INFTY;
1,519,490✔
628
    return d;
629
  }
630
}
1,519,490✔
631

1,519,490✔
632
Direction SurfaceSphere::normal(Position r) const
1,519,490✔
633
{
1,519,490✔
634
  return {2.0 * (r.x - x0_), 2.0 * (r.y - y0_), 2.0 * (r.z - z0_)};
1,519,490✔
635
}
UNCOV
636

×
637
void SurfaceSphere::to_hdf5_inner(hid_t group_id) const
638
{
UNCOV
639
  write_string(group_id, "type", "sphere", false);
×
UNCOV
640
  array<double, 4> coeffs {{x0_, y0_, z0_, radius_}};
×
UNCOV
641
  write_dataset(group_id, "coefficients", coeffs);
×
UNCOV
642
}
×
UNCOV
643

×
644
BoundingBox SurfaceSphere::bounding_box(bool pos_side) const
UNCOV
645
{
×
646
  if (!pos_side) {
647
    return {x0_ - radius_, x0_ + radius_, y0_ - radius_, y0_ + radius_,
UNCOV
648
      z0_ - radius_, z0_ + radius_};
×
649
  } else {
×
650
    return {};
×
651
  }
×
652
}
×
653

654
//==============================================================================
655
// Generic functions for x-, y-, and z-, cones
656
//==============================================================================
657

658
// The first template parameter indicates which axis the cone is aligned to.
659
// The other two parameters indicate the other two axes.  offset1, offset2,
34✔
660
// and offset3 should correspond with i1, i2, and i3, respectively.
34✔
661
template<int i1, int i2, int i3>
662
double axis_aligned_cone_evaluate(
34✔
663
  Position r, double offset1, double offset2, double offset3, double radius_sq)
34✔
664
{
665
  const double r1 = r.get<i1>() - offset1;
1,495,686✔
666
  const double r2 = r.get<i2>() - offset2;
667
  const double r3 = r.get<i3>() - offset3;
1,495,686✔
668
  return r2 * r2 + r3 * r3 - radius_sq * r1 * r1;
669
}
670

1,187,232✔
671
// The first template parameter indicates which axis the cone is aligned to.
672
// The other two parameters indicate the other two axes.  offset1, offset2,
673
// and offset3 should correspond with i1, i2, and i3, respectively.
1,187,232✔
674
template<int i1, int i2, int i3>
1,187,232✔
675
double axis_aligned_cone_distance(Position r, Direction u, bool coincident,
676
  double offset1, double offset2, double offset3, double radius_sq)
UNCOV
677
{
×
678
  const double r1 = r.get<i1>() - offset1;
UNCOV
679
  const double r2 = r.get<i2>() - offset2;
×
680
  const double r3 = r.get<i3>() - offset3;
681
  const double a = u.get<i2>() * u.get<i2>() + u.get<i3>() * u.get<i3>() -
682
                   radius_sq * u.get<i1>() * u.get<i1>();
24✔
683
  const double k =
684
    r2 * u.get<i2>() + r3 * u.get<i3>() - radius_sq * r1 * u.get<i1>();
24✔
685
  const double c = r2 * r2 + r3 * r3 - radius_sq * r1 * r1;
24✔
686
  double quad = k * k - a * c;
24✔
687

24✔
688
  double d;
689

×
690
  if (quad < 0.0) {
UNCOV
691
    // No intersection with cone.
×
UNCOV
692
    return INFTY;
×
UNCOV
693

×
694
  } else if (coincident || std::abs(c) < FP_COINCIDENT) {
UNCOV
695
    // Particle is on the cone, thus one distance is positive/negative
×
696
    // and the other is zero. The sign of k determines if we are facing in or
697
    // out.
698
    if (k >= 0.0) {
699
      d = (-k - sqrt(quad)) / a;
700
    } else {
701
      d = (-k + sqrt(quad)) / a;
702
    }
×
703

×
704
  } else {
705
    // Calculate both solutions to the quadratic.
×
706
    quad = sqrt(quad);
707
    d = (-k - quad) / a;
UNCOV
708
    const double b = (-k + quad) / a;
×
709

UNCOV
710
    // Determine the smallest positive solution.
×
711
    if (d < 0.0) {
712
      if (b > 0.0)
713
        d = b;
×
714
    } else {
715
      if (b > 0.0) {
UNCOV
716
        if (b < d)
×
UNCOV
717
          d = b;
×
718
      }
719
    }
720
  }
×
721

UNCOV
722
  // If the distance was negative, set boundary distance to infinity.
×
723
  if (d <= 0.0)
724
    return INFTY;
UNCOV
725
  return d;
×
726
}
727

×
UNCOV
728
// The first template parameter indicates which axis the cone is aligned to.
×
UNCOV
729
// The other two parameters indicate the other two axes.  offset1, offset2,
×
730
// and offset3 should correspond with i1, i2, and i3, respectively.
731
template<int i1, int i2, int i3>
732
Direction axis_aligned_cone_normal(
×
733
  Position r, double offset1, double offset2, double offset3, double radius_sq)
UNCOV
734
{
×
735
  Direction u;
×
UNCOV
736
  u.get<i1>() = -2.0 * radius_sq * (r.get<i1>() - offset1);
×
737
  u.get<i2>() = 2.0 * (r.get<i2>() - offset2);
738
  u.get<i3>() = 2.0 * (r.get<i3>() - offset3);
×
739
  return u;
740
}
741

742
//==============================================================================
743
// SurfaceXCone implementation
744
//==============================================================================
745

746
SurfaceXCone::SurfaceXCone(pugi::xml_node surf_node) : Surface(surf_node)
4,592✔
747
{
4,592✔
748
  read_coeffs(surf_node, id_, {&x0_, &y0_, &z0_, &radius_sq_});
749
}
4,592✔
750

4,592✔
751
double SurfaceXCone::evaluate(Position r) const
752
{
1,481,137,715✔
753
  return axis_aligned_cone_evaluate<0, 1, 2>(r, x0_, y0_, z0_, radius_sq_);
754
}
1,481,137,715✔
755

756
double SurfaceXCone::distance(Position r, Direction u, bool coincident) const
757
{
1,708,379,123✔
758
  return axis_aligned_cone_distance<0, 1, 2>(
759
    r, u, coincident, x0_, y0_, z0_, radius_sq_);
760
}
1,708,379,123✔
761

1,708,379,123✔
762
Direction SurfaceXCone::normal(Position r) const
763
{
764
  return axis_aligned_cone_normal<0, 1, 2>(r, x0_, y0_, z0_, radius_sq_);
1,519,490✔
765
}
766

1,519,490✔
767
void SurfaceXCone::to_hdf5_inner(hid_t group_id) const
768
{
769
  write_string(group_id, "type", "x-cone", false);
3,377✔
770
  array<double, 4> coeffs {{x0_, y0_, z0_, radius_sq_}};
771
  write_dataset(group_id, "coefficients", coeffs);
3,377✔
772
}
3,377✔
773

3,377✔
774
//==============================================================================
3,377✔
775
// SurfaceYCone implementation
776
//==============================================================================
48✔
777

778
SurfaceYCone::SurfaceYCone(pugi::xml_node surf_node) : Surface(surf_node)
48✔
779
{
24✔
780
  read_coeffs(surf_node, id_, {&x0_, &y0_, &z0_, &radius_sq_});
24✔
781
}
782

24✔
783
double SurfaceYCone::evaluate(Position r) const
784
{
785
  return axis_aligned_cone_evaluate<1, 0, 2>(r, y0_, x0_, z0_, radius_sq_);
786
}
787

788
double SurfaceYCone::distance(Position r, Direction u, bool coincident) const
789
{
790
  return axis_aligned_cone_distance<1, 0, 2>(
8,114✔
791
    r, u, coincident, y0_, x0_, z0_, radius_sq_);
792
}
8,114✔
793

8,114✔
794
Direction SurfaceYCone::normal(Position r) const
795
{
514,145,873✔
796
  return axis_aligned_cone_normal<1, 0, 2>(r, y0_, x0_, z0_, radius_sq_);
797
}
514,145,873✔
798

514,145,873✔
799
void SurfaceYCone::to_hdf5_inner(hid_t group_id) const
514,145,873✔
800
{
514,145,873✔
801
  write_string(group_id, "type", "y-cone", false);
802
  array<double, 4> coeffs {{x0_, y0_, z0_, radius_sq_}};
803
  write_dataset(group_id, "coefficients", coeffs);
595,643,786✔
804
}
805

595,643,786✔
806
//==============================================================================
595,643,786✔
807
// SurfaceZCone implementation
595,643,786✔
808
//==============================================================================
595,643,786✔
809

595,643,786✔
810
SurfaceZCone::SurfaceZCone(pugi::xml_node surf_node) : Surface(surf_node)
595,643,786✔
811
{
812
  read_coeffs(surf_node, id_, {&x0_, &y0_, &z0_, &radius_sq_});
595,643,786✔
813
}
814

127,378,085✔
815
double SurfaceZCone::evaluate(Position r) const
816
{
468,265,701✔
817
  return axis_aligned_cone_evaluate<2, 0, 1>(r, z0_, x0_, y0_, radius_sq_);
818
}
819

50,509,506✔
820
double SurfaceZCone::distance(Position r, Direction u, bool coincident) const
24,770,526✔
821
{
822
  return axis_aligned_cone_distance<2, 0, 1>(
25,738,980✔
823
    r, u, coincident, z0_, x0_, y0_, radius_sq_);
824
}
825

417,756,195✔
826
Direction SurfaceZCone::normal(Position r) const
827
{
828
  return axis_aligned_cone_normal<2, 0, 1>(r, z0_, x0_, y0_, radius_sq_);
829
}
382,076,519✔
830

831
void SurfaceZCone::to_hdf5_inner(hid_t group_id) const
832
{
833
  write_string(group_id, "type", "z-cone", false);
834
  array<double, 4> coeffs {{x0_, y0_, z0_, radius_sq_}};
835
  write_dataset(group_id, "coefficients", coeffs);
35,679,676✔
836
}
35,679,676✔
837

10,203,685✔
838
//==============================================================================
25,475,991✔
839
// SurfaceQuadric implementation
840
//==============================================================================
841

842
SurfaceQuadric::SurfaceQuadric(pugi::xml_node surf_node) : Surface(surf_node)
7,341,664✔
843
{
844
  read_coeffs(
7,341,664✔
845
    surf_node, id_, {&A_, &B_, &C_, &D_, &E_, &F_, &G_, &H_, &J_, &K_});
846
}
847

6,363✔
848
double SurfaceQuadric::evaluate(Position r) const
849
{
6,363✔
850
  const double x = r.x;
6,363✔
851
  const double y = r.y;
6,363✔
852
  const double z = r.z;
6,363✔
853
  return x * (A_ * x + D_ * y + G_) + y * (B_ * y + E_ * z + H_) +
UNCOV
854
         z * (C_ * z + F_ * x + J_) + K_;
×
855
}
UNCOV
856

×
UNCOV
857
double SurfaceQuadric::distance(
×
UNCOV
858
  Position r, Direction ang, bool coincident) const
×
859
{
UNCOV
860
  const double& x = r.x;
×
861
  const double& y = r.y;
862
  const double& z = r.z;
863
  const double& u = ang.x;
864
  const double& v = ang.y;
865
  const double& w = ang.z;
866

867
  const double a =
868
    A_ * u * u + B_ * v * v + C_ * w * w + D_ * u * v + E_ * v * w + F_ * u * w;
869
  const double k = A_ * u * x + B_ * v * y + C_ * w * z +
870
                   0.5 * (D_ * (u * y + v * x) + E_ * (v * z + w * y) +
871
                           F_ * (w * x + u * z) + G_ * u + H_ * v + J_ * w);
872
  const double c = A_ * x * x + B_ * y * y + C_ * z * z + D_ * x * y +
351,912✔
873
                   E_ * y * z + F_ * x * z + G_ * x + H_ * y + J_ * z + K_;
874
  double quad = k * k - a * c;
875

351,912✔
876
  double d;
351,912✔
877

351,912✔
878
  if (quad < 0.0) {
351,912✔
879
    // No intersection with surface.
880
    return INFTY;
351,912✔
881

882
  } else if (coincident || std::abs(c) < FP_COINCIDENT) {
883
    // Particle is on the surface, thus one distance is positive/negative and
351,912✔
884
    // the other is zero. The sign of k determines which distance is zero and
351,912✔
885
    // which is not. Additionally, if a is zero, it means the particle is on
351,912✔
886
    // a plane-like surface.
351,912✔
887
    if (a == 0.0) {
UNCOV
888
      d = INFTY; // see the below explanation
×
889
    } else if (k >= 0.0) {
890
      d = (-k - sqrt(quad)) / a;
UNCOV
891
    } else {
×
UNCOV
892
      d = (-k + sqrt(quad)) / a;
×
UNCOV
893
    }
×
UNCOV
894

×
895
  } else if (a == 0.0) {
UNCOV
896
    // Given the orientation of the particle, the quadric looks like a plane in
×
897
    // this case, and thus we have only one solution despite potentially having
898
    // quad > 0.0. While the term under the square root may be real, in one
UNCOV
899
    // case of the +/- of the quadratic formula, 0/0 results, and in another, a
×
UNCOV
900
    // finite value over 0 results. Applying L'Hopital's to the 0/0 case gives
×
901
    // the below. Alternatively this can be found by simply putting a=0 in the
×
902
    // equation ax^2 + bx + c = 0.
×
903
    d = -0.5 * c / k;
904
  } else {
905
    // Calculate both solutions to the quadratic.
906
    quad = sqrt(quad);
907
    d = (-k - quad) / a;
908
    double b = (-k + quad) / a;
909

1,050,936✔
910
    // Determine the smallest positive solution.
911
    if (d < 0.0) {
912
      if (b > 0.0)
1,050,936✔
913
        d = b;
1,050,936✔
914
    } else {
1,050,936✔
915
      if (b > 0.0) {
1,050,936✔
916
        if (b < d)
1,050,936✔
917
          d = b;
1,050,936✔
918
      }
1,050,936✔
919
    }
1,050,936✔
920
  }
1,050,936✔
921

922
  // If the distance was negative, set boundary distance to infinity.
923
  if (d <= 0.0)
924
    return INFTY;
1,050,936✔
925
  return d;
UNCOV
926
}
×
927

928
Direction SurfaceQuadric::normal(Position r) const
1,050,936✔
929
{
930
  const double& x = r.x;
931
  const double& y = r.y;
932
  const double& z = r.z;
66,072✔
UNCOV
933
  return {2.0 * A_ * x + D_ * y + F_ * z + G_,
×
934
    2.0 * B_ * y + D_ * x + E_ * z + H_, 2.0 * C_ * z + E_ * y + F_ * x + J_};
935
}
66,072✔
936

937
void SurfaceQuadric::to_hdf5_inner(hid_t group_id) const
938
{
939
  write_string(group_id, "type", "quadric", false);
940
  array<double, 10> coeffs {{A_, B_, C_, D_, E_, F_, G_, H_, J_, K_}};
984,864✔
941
  write_dataset(group_id, "coefficients", coeffs);
984,864✔
942
}
984,864✔
943

944
//==============================================================================
945
// Torus helper functions
984,864✔
946
//==============================================================================
836,208✔
947

709,644✔
948
double torus_distance(double x1, double x2, double x3, double u1, double u2,
949
  double u3, double A, double B, double C, bool coincident)
148,656✔
950
{
148,656✔
951
  // Coefficients for equation: (c2 t^2 + c1 t + c0)^2 = c2' t^2 + c1' t + c0'
148,656✔
952
  double D = (C * C) / (B * B);
953
  double c2 = u1 * u1 + u2 * u2 + D * u3 * u3;
954
  double c1 = 2 * (u1 * x1 + u2 * x2 + D * u3 * x3);
955
  double c0 = x1 * x1 + x2 * x2 + D * x3 * x3 + A * A - C * C;
956
  double four_A2 = 4 * A * A;
957
  double c2p = four_A2 * (u1 * u1 + u2 * u2);
1,050,936✔
958
  double c1p = 2 * four_A2 * (u1 * x1 + u2 * x2);
149,964✔
959
  double c0p = four_A2 * (x1 * x1 + x2 * x2);
900,972✔
960

961
  // Coefficient for equation: a t^4 + b t^3 + c t^2 + d t + e = 0. If the point
1,050,936✔
962
  // is coincident, the 'e' coefficient should be zero. Explicitly setting it to
963
  // zero helps avoid numerical issues below with root finding.
964
  double coeff[5];
1,050,936✔
965
  coeff[0] = coincident ? 0.0 : c0 * c0 - c0p;
1,050,936✔
966
  coeff[1] = 2 * c0 * c1 - c1p;
1,050,936✔
967
  coeff[2] = c1 * c1 + 2 * c0 * c2 - c2p;
1,050,936✔
968
  coeff[3] = 2 * c1 * c2;
1,050,936✔
969
  coeff[4] = c2 * c2;
1,050,936✔
970

1,050,936✔
971
  std::complex<double> roots[4];
1,050,936✔
972
  oqs::quartic_solver(coeff, roots);
1,050,936✔
973

974
  // Find smallest positive, real root. In the case where the particle is
975
  // coincident with the surface, we are sure to have one root very close to
976
  // zero but possibly small and positive. A tolerance is set to discard that
1,050,936✔
977
  // zero.
UNCOV
978
  double distance = INFTY;
×
979
  double cutoff = coincident ? TORUS_TOL : 0.0;
980
  for (int i = 0; i < 4; ++i) {
1,050,936✔
981
    if (roots[i].imag() == 0) {
982
      double root = roots[i].real();
983
      if (root > cutoff && root < distance) {
984
        // Avoid roots corresponding to internal surfaces
66,072✔
UNCOV
985
        double s1 = x1 + u1 * root;
×
986
        double s2 = x2 + u2 * root;
987
        double s3 = x3 + u3 * root;
66,072✔
988
        double check = D * s3 * s3 + s1 * s1 + s2 * s2 + A * A - C * C;
989
        if (check >= 0) {
990
          distance = root;
991
        }
992
      }
984,864✔
993
    }
984,864✔
994
  }
984,864✔
995
  return distance;
996
}
997

984,864✔
998
//==============================================================================
836,208✔
999
// SurfaceXTorus implementation
709,644✔
1000
//==============================================================================
1001

148,656✔
1002
SurfaceXTorus::SurfaceXTorus(pugi::xml_node surf_node) : Surface(surf_node)
148,656✔
1003
{
148,656✔
1004
  read_coeffs(surf_node, id_, {&x0_, &y0_, &z0_, &A_, &B_, &C_});
1005
}
1006

1007
void SurfaceXTorus::to_hdf5_inner(hid_t group_id) const
1008
{
1009
  write_string(group_id, "type", "x-torus", false);
1,050,936✔
1010
  std::array<double, 6> coeffs {{x0_, y0_, z0_, A_, B_, C_}};
149,964✔
1011
  write_dataset(group_id, "coefficients", coeffs);
900,972✔
1012
}
UNCOV
1013

×
1014
double SurfaceXTorus::evaluate(Position r) const
1015
{
UNCOV
1016
  double x = r.x - x0_;
×
UNCOV
1017
  double y = r.y - y0_;
×
UNCOV
1018
  double z = r.z - z0_;
×
UNCOV
1019
  return (x * x) / (B_ * B_) +
×
UNCOV
1020
         std::pow(std::sqrt(y * y + z * z) - A_, 2) / (C_ * C_) - 1.;
×
UNCOV
1021
}
×
UNCOV
1022

×
1023
double SurfaceXTorus::distance(Position r, Direction u, bool coincident) const
×
UNCOV
1024
{
×
1025
  double x = r.x - x0_;
1026
  double y = r.y - y0_;
1027
  double z = r.z - z0_;
1028
  return torus_distance(y, z, x, u.y, u.z, u.x, A_, B_, C_, coincident);
×
1029
}
1030

×
1031
Direction SurfaceXTorus::normal(Position r) const
1032
{
×
1033
  // reduce the expansion of the full form for torus
1034
  double x = r.x - x0_;
1035
  double y = r.y - y0_;
UNCOV
1036
  double z = r.z - z0_;
×
UNCOV
1037

×
1038
  // f(x,y,z) = x^2/B^2 + (sqrt(y^2 + z^2) - A)^2/C^2 - 1
UNCOV
1039
  // ∂f/∂x = 2x/B^2
×
1040
  // ∂f/∂y = 2y(g - A)/(g*C^2) where g = sqrt(y^2 + z^2)
1041
  // ∂f/∂z = 2z(g - A)/(g*C^2)
1042
  // Multiplying by g*C^2*B^2 / 2 gives:
1043
  double g = std::sqrt(y * y + z * z);
UNCOV
1044
  double nx = C_ * C_ * g * x;
×
UNCOV
1045
  double ny = y * (g - A_) * B_ * B_;
×
1046
  double nz = z * (g - A_) * B_ * B_;
×
1047
  Direction n(nx, ny, nz);
1048
  return n / n.norm();
1049
}
×
UNCOV
1050

×
UNCOV
1051
//==============================================================================
×
1052
// SurfaceYTorus implementation
UNCOV
1053
//==============================================================================
×
1054

×
1055
SurfaceYTorus::SurfaceYTorus(pugi::xml_node surf_node) : Surface(surf_node)
×
1056
{
1057
  read_coeffs(surf_node, id_, {&x0_, &y0_, &z0_, &A_, &B_, &C_});
1058
}
1059

1060
void SurfaceYTorus::to_hdf5_inner(hid_t group_id) const
1061
{
×
UNCOV
1062
  write_string(group_id, "type", "y-torus", false);
×
1063
  std::array<double, 6> coeffs {{x0_, y0_, z0_, A_, B_, C_}};
×
1064
  write_dataset(group_id, "coefficients", coeffs);
1065
}
×
1066

1067
double SurfaceYTorus::evaluate(Position r) const
UNCOV
1068
{
×
UNCOV
1069
  double x = r.x - x0_;
×
UNCOV
1070
  double y = r.y - y0_;
×
1071
  double z = r.z - z0_;
×
1072
  return (y * y) / (B_ * B_) +
×
1073
         std::pow(std::sqrt(x * x + z * z) - A_, 2) / (C_ * C_) - 1.;
×
UNCOV
1074
}
×
1075

×
UNCOV
1076
double SurfaceYTorus::distance(Position r, Direction u, bool coincident) const
×
1077
{
1078
  double x = r.x - x0_;
1079
  double y = r.y - y0_;
1080
  double z = r.z - z0_;
×
1081
  return torus_distance(x, z, y, u.x, u.z, u.y, A_, B_, C_, coincident);
1082
}
×
1083

1084
Direction SurfaceYTorus::normal(Position r) const
×
1085
{
1086
  // reduce the expansion of the full form for torus
1087
  double x = r.x - x0_;
UNCOV
1088
  double y = r.y - y0_;
×
UNCOV
1089
  double z = r.z - z0_;
×
1090

UNCOV
1091
  // f(x,y,z) = y^2/B^2 + (sqrt(x^2 + z^2) - A)^2/C^2 - 1
×
1092
  // ∂f/∂x = 2x(g - A)/(g*C^2) where g = sqrt(x^2 + z^2)
1093
  // ∂f/∂y = 2y/B^2
1094
  // ∂f/∂z = 2z(g - A)/(g*C^2)
1095
  // Multiplying by g*C^2*B^2 / 2 gives:
UNCOV
1096
  double g = std::sqrt(x * x + z * z);
×
UNCOV
1097
  double nx = x * (g - A_) * B_ * B_;
×
1098
  double ny = C_ * C_ * g * y;
×
1099
  double nz = z * (g - A_) * B_ * B_;
1100
  Direction n(nx, ny, nz);
1101
  return n / n.norm();
×
UNCOV
1102
}
×
UNCOV
1103

×
1104
//==============================================================================
UNCOV
1105
// SurfaceZTorus implementation
×
1106
//==============================================================================
×
1107

×
1108
SurfaceZTorus::SurfaceZTorus(pugi::xml_node surf_node) : Surface(surf_node)
1109
{
1110
  read_coeffs(surf_node, id_, {&x0_, &y0_, &z0_, &A_, &B_, &C_});
1111
}
1112

1113
void SurfaceZTorus::to_hdf5_inner(hid_t group_id) const
×
UNCOV
1114
{
×
1115
  write_string(group_id, "type", "z-torus", false);
×
1116
  std::array<double, 6> coeffs {{x0_, y0_, z0_, A_, B_, C_}};
1117
  write_dataset(group_id, "coefficients", coeffs);
1118
}
1119

1120
double SurfaceZTorus::evaluate(Position r) const
1121
{
1122
  double x = r.x - x0_;
66,072✔
1123
  double y = r.y - y0_;
1124
  double z = r.z - z0_;
1125
  return (z * z) / (B_ * B_) +
66,072✔
1126
         std::pow(std::sqrt(x * x + y * y) - A_, 2) / (C_ * C_) - 1.;
66,072✔
1127
}
66,072✔
1128

66,072✔
1129
double SurfaceZTorus::distance(Position r, Direction u, bool coincident) const
66,072✔
1130
{
1131
  double x = r.x - x0_;
66,072✔
1132
  double y = r.y - y0_;
1133
  double z = r.z - z0_;
1134
  return torus_distance(x, y, z, u.x, u.y, u.z, A_, B_, C_, coincident);
66,072✔
1135
}
66,072✔
1136

66,072✔
1137
Direction SurfaceZTorus::normal(Position r) const
66,072✔
1138
{
66,072✔
1139
  // reduce the expansion of the full form for torus
UNCOV
1140
  double x = r.x - x0_;
×
1141
  double y = r.y - y0_;
1142
  double z = r.z - z0_;
UNCOV
1143

×
UNCOV
1144
  // f(x,y,z) = z^2/B^2 + (sqrt(x^2 + y^2) - A)^2/C^2 - 1
×
UNCOV
1145
  // ∂f/∂x = 2x(g - A)/(g*C^2) where g = sqrt(x^2 + y^2)
×
UNCOV
1146
  // ∂f/∂y = 2y(g - A)/(g*C^2)
×
UNCOV
1147
  // ∂f/∂z = 2z/B^2
×
1148
  // Multiplying by g*C^2*B^2 / 2 gives:
UNCOV
1149
  double g = std::sqrt(x * x + y * y);
×
1150
  double nx = x * (g - A_) * B_ * B_;
1151
  double ny = y * (g - A_) * B_ * B_;
UNCOV
1152
  double nz = C_ * C_ * g * z;
×
1153
  Position n(nx, ny, nz);
×
1154
  return n / n.norm();
×
1155
}
×
1156

×
1157
//==============================================================================
1158

1159
void read_surfaces(pugi::xml_node node)
1160
{
1161
  // Count the number of surfaces
1162
  int n_surfaces = 0;
1163
  for (pugi::xml_node surf_node : node.children("surface")) {
×
1164
    n_surfaces++;
1165
  }
×
1166

1167
  // Loop over XML surface elements and populate the array.  Keep track of
UNCOV
1168
  // periodic surfaces and their albedos.
×
1169
  model::surfaces.reserve(n_surfaces);
UNCOV
1170
  std::set<std::pair<int, int>> periodic_pairs;
×
1171
  std::unordered_map<int, double> albedo_map;
1172
  {
1173
    pugi::xml_node surf_node;
×
1174
    int i_surf;
1175
    for (surf_node = node.child("surface"), i_surf = 0; surf_node;
×
UNCOV
1176
         surf_node = surf_node.next_sibling("surface"), i_surf++) {
×
1177
      std::string surf_type = get_node_value(surf_node, "type", true, true);
1178

UNCOV
1179
      // Allocate and initialize the new surface
×
1180

UNCOV
1181
      if (surf_type == "x-plane") {
×
1182
        model::surfaces.push_back(make_unique<SurfaceXPlane>(surf_node));
1183

UNCOV
1184
      } else if (surf_type == "y-plane") {
×
1185
        model::surfaces.push_back(make_unique<SurfaceYPlane>(surf_node));
1186

×
UNCOV
1187
      } else if (surf_type == "z-plane") {
×
UNCOV
1188
        model::surfaces.push_back(make_unique<SurfaceZPlane>(surf_node));
×
1189

1190
      } else if (surf_type == "plane") {
1191
        model::surfaces.push_back(make_unique<SurfacePlane>(surf_node));
1192

1193
      } else if (surf_type == "x-cylinder") {
1194
        model::surfaces.push_back(make_unique<SurfaceXCylinder>(surf_node));
UNCOV
1195

×
1196
      } else if (surf_type == "y-cylinder") {
1197
        model::surfaces.push_back(make_unique<SurfaceYCylinder>(surf_node));
×
1198

1199
      } else if (surf_type == "z-cylinder") {
UNCOV
1200
        model::surfaces.push_back(make_unique<SurfaceZCylinder>(surf_node));
×
1201

UNCOV
1202
      } else if (surf_type == "sphere") {
×
1203
        model::surfaces.push_back(make_unique<SurfaceSphere>(surf_node));
1204

1205
      } else if (surf_type == "x-cone") {
×
1206
        model::surfaces.push_back(make_unique<SurfaceXCone>(surf_node));
1207

×
UNCOV
1208
      } else if (surf_type == "y-cone") {
×
1209
        model::surfaces.push_back(make_unique<SurfaceYCone>(surf_node));
1210

UNCOV
1211
      } else if (surf_type == "z-cone") {
×
1212
        model::surfaces.push_back(make_unique<SurfaceZCone>(surf_node));
UNCOV
1213

×
1214
      } else if (surf_type == "quadric") {
1215
        model::surfaces.push_back(make_unique<SurfaceQuadric>(surf_node));
UNCOV
1216

×
1217
      } else if (surf_type == "x-torus") {
1218
        model::surfaces.push_back(std::make_unique<SurfaceXTorus>(surf_node));
×
UNCOV
1219

×
UNCOV
1220
      } else if (surf_type == "y-torus") {
×
1221
        model::surfaces.push_back(std::make_unique<SurfaceYTorus>(surf_node));
1222

1223
      } else if (surf_type == "z-torus") {
1224
        model::surfaces.push_back(std::make_unique<SurfaceZTorus>(surf_node));
1225

1226
      } else {
1227
        fatal_error(fmt::format("Invalid surface type, \"{}\"", surf_type));
17✔
1228
      }
1229

17✔
1230
      // Check for a periodic surface
17✔
1231
      if (check_for_node(surf_node, "boundary")) {
1232
        std::string surf_bc = get_node_value(surf_node, "boundary", true, true);
351,912✔
1233
        if (surf_bc == "periodic") {
1234
          // Check for surface albedo. Skip sanity check as it is already done
351,912✔
1235
          // in the Surface class's constructor.
1236
          if (check_for_node(surf_node, "albedo")) {
1237
            albedo_map[model::surfaces.back()->id_] =
1,050,936✔
1238
              std::stod(get_node_value(surf_node, "albedo"));
1239
          }
1,050,936✔
1240
          if (check_for_node(surf_node, "periodic_surface_id")) {
1,050,936✔
1241
            int i_periodic =
1242
              std::stoi(get_node_value(surf_node, "periodic_surface_id"));
1243
            int lo_id = std::min(model::surfaces.back()->id_, i_periodic);
66,072✔
1244
            int hi_id = std::max(model::surfaces.back()->id_, i_periodic);
1245
            periodic_pairs.insert({lo_id, hi_id});
66,072✔
1246
          } else {
1247
            periodic_pairs.insert({model::surfaces.back()->id_, -1});
1248
          }
12✔
1249
        }
1250
      }
12✔
1251
    }
12✔
1252
  }
12✔
1253

12✔
1254
  // Fill the surface map
1255
  for (int i_surf = 0; i_surf < model::surfaces.size(); i_surf++) {
1256
    int id = model::surfaces[i_surf]->id_;
1257
    auto in_map = model::surface_map.find(id);
1258
    if (in_map == model::surface_map.end()) {
1259
      model::surface_map[id] = i_surf;
17✔
1260
    } else {
UNCOV
1261
      fatal_error(
×
1262
        fmt::format("Two or more surfaces use the same unique ID: {}", id));
17✔
1263
    }
17✔
1264
  }
1265

191,344✔
1266
  // Resolve unpaired periodic surfaces.  A lambda function is used with
1267
  // std::find_if to identify the unpaired surfaces.
191,344✔
1268
  auto is_unresolved_pair = [](const std::pair<int, int> p) {
191,344✔
1269
    return p.second == -1;
191,344✔
1270
  };
191,344✔
1271
  auto first_unresolved = std::find_if(
191,344✔
1272
    periodic_pairs.begin(), periodic_pairs.end(), is_unresolved_pair);
1273
  if (first_unresolved != periodic_pairs.end()) {
1274
    // Found one unpaired surface; search for a second one
340,920✔
1275
    auto next_elem = first_unresolved;
1276
    next_elem++;
1277
    auto second_unresolved =
340,920✔
1278
      std::find_if(next_elem, periodic_pairs.end(), is_unresolved_pair);
340,920✔
1279
    if (second_unresolved == periodic_pairs.end()) {
340,920✔
1280
      fatal_error("Found only one periodic surface without a specified partner."
340,920✔
1281
                  " Please specify the partner for each periodic surface.");
340,920✔
1282
    }
340,920✔
1283

1284
    // Make sure there isn't a third unpaired surface
340,920✔
1285
    next_elem = second_unresolved;
340,920✔
1286
    next_elem++;
340,920✔
1287
    auto third_unresolved =
340,920✔
1288
      std::find_if(next_elem, periodic_pairs.end(), is_unresolved_pair);
340,920✔
1289
    if (third_unresolved != periodic_pairs.end()) {
340,920✔
1290
      fatal_error(
340,920✔
1291
        "Found at least three periodic surfaces without a specified "
340,920✔
1292
        "partner. Please specify the partner for each periodic surface.");
1293
    }
1294

1295
    // Add the completed pair and remove the old, unpaired entries
340,920✔
1296
    int lo_id = std::min(first_unresolved->first, second_unresolved->first);
UNCOV
1297
    int hi_id = std::max(first_unresolved->first, second_unresolved->first);
×
1298
    periodic_pairs.insert({lo_id, hi_id});
1299
    periodic_pairs.erase(first_unresolved);
340,920✔
1300
    periodic_pairs.erase(second_unresolved);
1301
  }
1302

1303
  // Assign the periodic boundary conditions with albedos
1304
  for (auto periodic_pair : periodic_pairs) {
37,680✔
UNCOV
1305
    int i_surf = model::surface_map[periodic_pair.first];
×
1306
    int j_surf = model::surface_map[periodic_pair.second];
37,680✔
1307
    Surface& surf1 {*model::surfaces[i_surf]};
×
1308
    Surface& surf2 {*model::surfaces[j_surf]};
1309

37,680✔
1310
    // Compute the dot product of the surface normals
1311
    Direction norm1 = surf1.normal({0, 0, 0});
1312
    Direction norm2 = surf2.normal({0, 0, 0});
303,240✔
1313
    norm1 /= norm1.norm();
1314
    norm2 /= norm2.norm();
1315
    double dot_prod = norm1.dot(norm2);
1316

1317
    // If the dot product is 1 (to within floating point precision) then the
1318
    // planes are parallel which indicates a translational periodic boundary
1319
    // condition.  Otherwise, it is a rotational periodic BC.
UNCOV
1320
    if (std::abs(1.0 - dot_prod) < FP_PRECISION) {
×
1321
      surf1.bc_ = make_unique<TranslationalPeriodicBC>(i_surf, j_surf);
1322
      surf2.bc_ = make_unique<TranslationalPeriodicBC>(i_surf, j_surf);
1323
    } else {
303,240✔
1324
      surf1.bc_ = make_unique<RotationalPeriodicBC>(i_surf, j_surf);
303,240✔
1325
      surf2.bc_ = make_unique<RotationalPeriodicBC>(i_surf, j_surf);
303,240✔
1326
    }
1327

1328
    // If albedo data is present in albedo map, set the boundary albedo.
303,240✔
1329
    if (albedo_map.count(surf1.id_)) {
303,240✔
1330
      surf1.bc_->set_albedo(albedo_map[surf1.id_]);
303,240✔
1331
    }
UNCOV
1332
    if (albedo_map.count(surf2.id_)) {
×
UNCOV
1333
      surf2.bc_->set_albedo(albedo_map[surf2.id_]);
×
UNCOV
1334
    }
×
1335
  }
1336
}
1337

1338
void free_memory_surfaces()
1339
{
1340
  model::surfaces.clear();
340,920✔
UNCOV
1341
  model::surface_map.clear();
×
1342
}
340,920✔
1343

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

© 2025 Coveralls, Inc