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

openmc-dev / openmc / 14983111830

12 May 2025 09:35PM UTC coverage: 85.165% (-0.04%) from 85.2%
14983111830

Pull #3402

github

web-flow
Merge 2fc68aee2 into 7382b5d1c
Pull Request #3402: Add transformation boundary condition

67 of 116 new or added lines in 5 files covered. (57.76%)

95 existing lines in 6 files now uncovered.

52397 of 61524 relevant lines covered (85.17%)

36950707.46 hits per line

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

71.43
/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,099✔
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,099✔
42
  if (coeffs_file.size() != coeffs.size()) {
36,099✔
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,099✔
50
  for (auto c : coeffs) {
110,318✔
51
    *c = coeffs_file[i++];
74,219✔
52
  }
53
}
36,099✔
54

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

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

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

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

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

80
    if (surf_bc == "transmission" || surf_bc == "transmit" || surf_bc.empty()) {
21,240✔
81
      // Leave the bc_ a nullptr
82
    } else if (surf_bc == "vacuum") {
21,240✔
83
      bc_ = make_unique<VacuumBC>();
10,303✔
84
    } else if (surf_bc == "reflective" || surf_bc == "reflect" ||
11,361✔
85
               surf_bc == "reflecting") {
424✔
86
      bc_ = make_unique<ReflectiveBC>();
10,513✔
87
    } else if (surf_bc == "white") {
424✔
88
      bc_ = make_unique<WhiteBC>();
96✔
89
    } else if (surf_bc == "periodic") {
328✔
90
      // Periodic BCs are handled separately
91
    } else if (surf_bc == "transformation" || surf_bc == "transform") {
96✔
92
      int vector_size_exp = 12;
96✔
93
      vector<double> dir_trans(vector_size_exp);
96✔
94
      vector<double> pos_trans(vector_size_exp);
96✔
95

96
      if (check_for_node(surf_node, "direction_transformation")) {
96✔
97
        dir_trans =
98
          get_node_array<double>(surf_node, "direction_transformation");
96✔
99
        if (dir_trans.size() != vector_size_exp) {
96✔
NEW
100
          fatal_error(fmt::format(
×
101
            "Transformation on surface {} expects direction matrix size {} "
102
            "but was given size {}",
NEW
103
            id_, vector_size_exp, dir_trans.size()));
×
104
        }
105
      } else {
NEW
106
        dir_trans = {
×
NEW
107
          1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0};
×
108
      }
109
      if (check_for_node(surf_node, "position_transformation")) {
96✔
110
        pos_trans =
NEW
111
          get_node_array<double>(surf_node, "position_transformation");
×
NEW
112
        if (pos_trans.size() != vector_size_exp) {
×
NEW
113
          fatal_error(
×
NEW
114
            fmt::format("Transformation on surface {} expects position matrix "
×
115
                        "size {} but was given size {}",
NEW
116
              id_, vector_size_exp, pos_trans.size()));
×
117
        }
118
      } else {
119
        pos_trans = {
96✔
120
          1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0};
96✔
121
      }
122
      bc_ = make_unique<TransformationBC>(dir_trans, pos_trans);
96✔
123
    } else {
96✔
124
      fatal_error(fmt::format("Unknown boundary condition \"{}\" specified "
×
125
                              "on surface {}",
126
        surf_bc, id_));
×
127
    }
128

129
    if (check_for_node(surf_node, "albedo") && bc_) {
21,240✔
130
      double surf_alb = std::stod(get_node_value(surf_node, "albedo"));
96✔
131

132
      if (surf_alb < 0.0)
96✔
133
        fatal_error(fmt::format("Surface {} has an albedo of {}. "
×
134
                                "Albedo values must be positive.",
135
          id_, surf_alb));
×
136

137
      if (surf_alb > 1.0)
96✔
138
        warning(fmt::format("Surface {} has an albedo of {}. "
×
139
                            "Albedos greater than 1 may cause "
140
                            "unphysical behaviour.",
141
          id_, surf_alb));
×
142

143
      bc_->set_albedo(surf_alb);
96✔
144
    }
145
  }
21,240✔
146
}
36,099✔
147

148
bool Surface::sense(Position r, Direction u) const
2,147,483,647✔
149
{
150
  // Evaluate the surface equation at the particle's coordinates to determine
151
  // which side the particle is on.
152
  const double f = evaluate(r);
2,147,483,647✔
153

154
  // Check which side of surface the point is on.
155
  if (std::abs(f) < FP_COINCIDENT) {
2,147,483,647✔
156
    // Particle may be coincident with this surface. To determine the sense, we
157
    // look at the direction of the particle relative to the surface normal (by
158
    // default in the positive direction) via their dot product.
159
    return u.dot(normal(r)) > 0.0;
1,310,784✔
160
  }
161
  return f > 0.0;
2,147,483,647✔
162
}
163

164
Direction Surface::reflect(Position r, Direction u, GeometryState* p) const
603,069,970✔
165
{
166
  // Determine projection of direction onto normal and squared magnitude of
167
  // normal.
168
  Direction n = normal(r);
603,069,970✔
169

170
  // Reflect direction according to normal.
171
  return u.reflect(n);
1,206,139,940✔
172
}
173

174
Direction Surface::diffuse_reflect(
1,016,983✔
175
  Position r, Direction u, uint64_t* seed) const
176
{
177
  // Diffuse reflect direction according to the normal.
178
  // cosine distribution
179

180
  Direction n = this->normal(r);
1,016,983✔
181
  n /= n.norm();
1,016,983✔
182
  const double projection = n.dot(u);
1,016,983✔
183

184
  // sample from inverse function, u=sqrt(rand) since p(u)=2u, so F(u)=u^2
185
  const double mu =
186
    (projection >= 0.0) ? -std::sqrt(prn(seed)) : std::sqrt(prn(seed));
1,016,983✔
187

188
  // sample azimuthal distribution uniformly
189
  u = rotate_angle(n, mu, nullptr, seed);
1,016,983✔
190

191
  // normalize the direction
192
  return u / u.norm();
2,033,966✔
193
}
194

195
void Surface::to_hdf5(hid_t group_id) const
28,593✔
196
{
197
  hid_t surf_group = create_group(group_id, fmt::format("surface {}", id_));
57,186✔
198

199
  if (geom_type() == GeometryType::DAG) {
28,593✔
200
    write_string(surf_group, "geom_type", "dagmc", false);
594✔
201
  } else if (geom_type() == GeometryType::CSG) {
27,999✔
202
    write_string(surf_group, "geom_type", "csg", false);
27,999✔
203

204
    if (bc_) {
27,999✔
205
      write_string(surf_group, "boundary_type", bc_->type(), false);
16,684✔
206
      bc_->to_hdf5(surf_group);
16,684✔
207
    } else {
208
      write_string(surf_group, "boundary_type", "transmission", false);
11,315✔
209
    }
210
  }
211

212
  if (!name_.empty()) {
28,593✔
213
    write_string(surf_group, "name", name_, false);
6,209✔
214
  }
215

216
  to_hdf5_inner(surf_group);
28,593✔
217

218
  close_group(surf_group);
28,593✔
219
}
28,593✔
220

221
//==============================================================================
222
// Generic functions for x-, y-, and z-, planes.
223
//==============================================================================
224

225
// The template parameter indicates the axis normal to the plane.
226
template<int i>
227
double axis_aligned_plane_distance(
2,147,483,647✔
228
  Position r, Direction u, bool coincident, double offset)
229
{
230
  const double f = offset - r[i];
2,147,483,647✔
231
  if (coincident || std::abs(f) < FP_COINCIDENT || u[i] == 0.0)
2,147,483,647✔
232
    return INFTY;
602,735,210✔
233
  const double d = f / u[i];
2,147,483,647✔
234
  if (d < 0.0)
2,147,483,647✔
235
    return INFTY;
2,147,483,647✔
236
  return d;
2,147,483,647✔
237
}
238

2,147,483,647✔
239
//==============================================================================
240
// SurfaceXPlane implementation
241
//==============================================================================
2,147,483,647✔
242

2,147,483,647✔
243
SurfaceXPlane::SurfaceXPlane(pugi::xml_node surf_node) : Surface(surf_node)
55,446,558✔
244
{
2,147,483,647✔
245
  read_coeffs(surf_node, id_, {&x0_});
2,147,483,647✔
246
}
1,137,465,516✔
247

1,214,930,585✔
248
double SurfaceXPlane::evaluate(Position r) const
249
{
2,147,483,647✔
250
  return r.x - x0_;
251
}
252

2,147,483,647✔
253
double SurfaceXPlane::distance(Position r, Direction u, bool coincident) const
2,147,483,647✔
254
{
284,572,344✔
255
  return axis_aligned_plane_distance<0>(r, u, coincident, x0_);
2,147,483,647✔
256
}
2,147,483,647✔
257

2,147,483,647✔
258
Direction SurfaceXPlane::normal(Position r) const
2,147,483,647✔
259
{
260
  return {1., 0., 0.};
2,147,483,647✔
261
}
262

263
void SurfaceXPlane::to_hdf5_inner(hid_t group_id) const
2,147,483,647✔
264
{
2,147,483,647✔
265
  write_string(group_id, "type", "x-plane", false);
262,716,308✔
266
  array<double, 1> coeffs {{x0_}};
2,147,483,647✔
267
  write_dataset(group_id, "coefficients", coeffs);
2,147,483,647✔
268
}
2,147,483,647✔
269

2,147,483,647✔
270
BoundingBox SurfaceXPlane::bounding_box(bool pos_side) const
271
{
272
  if (pos_side) {
273
    return {x0_, INFTY, -INFTY, INFTY, -INFTY, INFTY};
274
  } else {
275
    return {-INFTY, x0_, -INFTY, INFTY, -INFTY, INFTY};
276
  }
8,607✔
277
}
278

8,607✔
279
//==============================================================================
8,607✔
280
// SurfaceYPlane implementation
281
//==============================================================================
1,514,147,982✔
282

283
SurfaceYPlane::SurfaceYPlane(pugi::xml_node surf_node) : Surface(surf_node)
1,514,147,982✔
284
{
285
  read_coeffs(surf_node, id_, {&y0_});
286
}
2,147,483,647✔
287

288
double SurfaceYPlane::evaluate(Position r) const
2,147,483,647✔
289
{
290
  return r.y - y0_;
291
}
263,072,176✔
292

293
double SurfaceYPlane::distance(Position r, Direction u, bool coincident) const
263,072,176✔
294
{
295
  return axis_aligned_plane_distance<1>(r, u, coincident, y0_);
296
}
6,621✔
297

298
Direction SurfaceYPlane::normal(Position r) const
6,621✔
299
{
6,621✔
300
  return {0., 1., 0.};
6,621✔
301
}
6,621✔
302

303
void SurfaceYPlane::to_hdf5_inner(hid_t group_id) const
242✔
304
{
305
  write_string(group_id, "type", "y-plane", false);
242✔
306
  array<double, 1> coeffs {{y0_}};
121✔
307
  write_dataset(group_id, "coefficients", coeffs);
308
}
121✔
309

310
BoundingBox SurfaceYPlane::bounding_box(bool pos_side) const
311
{
312
  if (pos_side) {
313
    return {-INFTY, INFTY, y0_, INFTY, -INFTY, INFTY};
314
  } else {
315
    return {-INFTY, INFTY, -INFTY, y0_, -INFTY, INFTY};
316
  }
7,663✔
317
}
318

7,663✔
319
//==============================================================================
7,663✔
320
// SurfaceZPlane implementation
321
//==============================================================================
1,513,458,264✔
322

323
SurfaceZPlane::SurfaceZPlane(pugi::xml_node surf_node) : Surface(surf_node)
1,513,458,264✔
324
{
325
  read_coeffs(surf_node, id_, {&z0_});
326
}
2,147,483,647✔
327

328
double SurfaceZPlane::evaluate(Position r) const
2,147,483,647✔
329
{
330
  return r.z - z0_;
331
}
285,708,553✔
332

333
double SurfaceZPlane::distance(Position r, Direction u, bool coincident) const
285,708,553✔
334
{
335
  return axis_aligned_plane_distance<2>(r, u, coincident, z0_);
336
}
6,029✔
337

338
Direction SurfaceZPlane::normal(Position r) const
6,029✔
339
{
6,029✔
340
  return {0., 0., 1.};
6,029✔
341
}
6,029✔
342

343
void SurfaceZPlane::to_hdf5_inner(hid_t group_id) const
242✔
344
{
345
  write_string(group_id, "type", "z-plane", false);
242✔
346
  array<double, 1> coeffs {{z0_}};
121✔
347
  write_dataset(group_id, "coefficients", coeffs);
348
}
121✔
349

350
BoundingBox SurfaceZPlane::bounding_box(bool pos_side) const
351
{
352
  if (pos_side) {
353
    return {-INFTY, INFTY, -INFTY, INFTY, z0_, INFTY};
354
  } else {
355
    return {-INFTY, INFTY, -INFTY, INFTY, -INFTY, z0_};
356
  }
5,787✔
357
}
358

5,787✔
359
//==============================================================================
5,787✔
360
// SurfacePlane implementation
361
//==============================================================================
488,819,969✔
362

363
SurfacePlane::SurfacePlane(pugi::xml_node surf_node) : Surface(surf_node)
488,819,969✔
364
{
365
  read_coeffs(surf_node, id_, {&A_, &B_, &C_, &D_});
366
}
2,147,483,647✔
367

368
double SurfacePlane::evaluate(Position r) const
2,147,483,647✔
369
{
370
  return A_ * r.x + B_ * r.y + C_ * r.z - D_;
371
}
55,779,829✔
372

373
double SurfacePlane::distance(Position r, Direction u, bool coincident) const
55,779,829✔
374
{
375
  const double f = A_ * r.x + B_ * r.y + C_ * r.z - D_;
376
  const double projection = A_ * u.x + B_ * u.y + C_ * u.z;
4,845✔
377
  if (coincident || std::abs(f) < FP_COINCIDENT || projection == 0.0) {
378
    return INFTY;
4,845✔
379
  } else {
4,845✔
380
    const double d = -f / projection;
4,845✔
381
    if (d < 0.0)
4,845✔
382
      return INFTY;
383
    return d;
×
384
  }
385
}
×
386

×
387
Direction SurfacePlane::normal(Position r) const
388
{
×
389
  return {A_, B_, C_};
390
}
391

392
void SurfacePlane::to_hdf5_inner(hid_t group_id) const
393
{
394
  write_string(group_id, "type", "plane", false);
395
  array<double, 4> coeffs {{A_, B_, C_, D_}};
396
  write_dataset(group_id, "coefficients", coeffs);
1,508✔
397
}
398

1,508✔
399
//==============================================================================
1,508✔
400
// Generic functions for x-, y-, and z-, cylinders
401
//==============================================================================
201,607,767✔
402

403
// The template parameters indicate the axes perpendicular to the axis of the
201,607,767✔
404
// cylinder.  offset1 and offset2 should correspond with i1 and i2,
405
// respectively.
406
template<int i1, int i2>
578,660,990✔
407
double axis_aligned_cylinder_evaluate(
408
  Position r, double offset1, double offset2, double radius)
578,660,990✔
409
{
578,660,990✔
410
  const double r1 = r.get<i1>() - offset1;
578,660,990✔
411
  const double r2 = r.get<i2>() - offset2;
35,616,614✔
412
  return r1 * r1 + r2 * r2 - radius * radius;
413
}
543,044,376✔
414

543,044,376✔
415
// The first template parameter indicates which axis the cylinder is aligned to.
253,639,474✔
416
// The other two parameters indicate the other two axes.  offset1 and offset2
289,404,902✔
417
// should correspond with i2 and i3, respectively.
418
template<int i1, int i2, int i3>
419
double axis_aligned_cylinder_distance(Position r, Direction u, bool coincident,
420
  double offset1, double offset2, double radius)
5,787,329✔
421
{
422
  const double a = 1.0 - u.get<i1>() * u.get<i1>(); // u^2 + v^2
5,787,329✔
423
  if (a == 0.0)
424
    return INFTY;
425

1,034✔
426
  const double r2 = r.get<i2>() - offset1;
427
  const double r3 = r.get<i3>() - offset2;
1,034✔
428
  const double k = r2 * u.get<i2>() + r3 * u.get<i3>();
1,034✔
429
  const double c = r2 * r2 + r3 * r3 - radius * radius;
1,034✔
430
  const double quad = k * k - a * c;
1,034✔
431

432
  if (quad < 0.0) {
433
    // No intersection with cylinder.
434
    return INFTY;
435

436
  } else if (coincident || std::abs(c) < FP_COINCIDENT) {
437
    // Particle is on the cylinder, thus one distance is positive/negative
438
    // and the other is zero. The sign of k determines if we are facing in or
439
    // out.
440
    if (k >= 0.0) {
1,408,351,780✔
441
      return INFTY;
442
    } else {
443
      return (-k + sqrt(quad)) / a;
1,408,351,780✔
444
    }
1,408,351,780✔
445

1,408,351,780✔
446
  } else if (c < 0.0) {
447
    // Particle is inside the cylinder, thus one distance must be negative
1,406,991,294✔
448
    // and one must be positive. The positive distance will be the one with
449
    // negative sign on sqrt(quad).
450
    return (-k + sqrt(quad)) / a;
1,406,991,294✔
451

1,406,991,294✔
452
  } else {
1,406,991,294✔
453
    // Particle is outside the cylinder, thus both distances are either
454
    // positive or negative. If positive, the smaller distance is the one
×
455
    // with positive sign on sqrt(quad).
456
    const double d = (-k - sqrt(quad)) / a;
457
    if (d < 0.0)
×
458
      return INFTY;
×
459
    return d;
×
460
  }
461
}
1,360,486✔
462

463
// The first template parameter indicates which axis the cylinder is aligned to.
464
// The other two parameters indicate the other two axes.  offset1 and offset2
1,360,486✔
465
// should correspond with i2 and i3, respectively.
1,360,486✔
466
template<int i1, int i2, int i3>
1,360,486✔
467
Direction axis_aligned_cylinder_normal(
468
  Position r, double offset1, double offset2)
469
{
470
  Direction u;
471
  u.get<i2>() = 2.0 * (r.get<i2>() - offset1);
472
  u.get<i3>() = 2.0 * (r.get<i3>() - offset2);
473
  u.get<i1>() = 0.0;
1,634,729,675✔
474
  return u;
475
}
476

1,634,729,675✔
477
//==============================================================================
1,634,729,675✔
478
// SurfaceXCylinder implementation
809,996✔
479
//==============================================================================
480

1,633,919,679✔
481
SurfaceXCylinder::SurfaceXCylinder(pugi::xml_node surf_node)
1,633,919,679✔
482
  : Surface(surf_node)
1,633,919,679✔
483
{
1,633,919,679✔
484
  read_coeffs(surf_node, id_, {&y0_, &z0_, &radius_});
1,633,919,679✔
485
}
486

1,633,919,679✔
487
double SurfaceXCylinder::evaluate(Position r) const
488
{
269,307,807✔
489
  return axis_aligned_cylinder_evaluate<1, 2>(r, y0_, z0_, radius_);
490
}
1,364,611,872✔
491

492
double SurfaceXCylinder::distance(
493
  Position r, Direction u, bool coincident) const
494
{
621,102,857✔
495
  return axis_aligned_cylinder_distance<0, 1, 2>(
311,631,141✔
496
    r, u, coincident, y0_, z0_, radius_);
497
}
309,471,716✔
498

499
Direction SurfaceXCylinder::normal(Position r) const
500
{
743,509,015✔
501
  return axis_aligned_cylinder_normal<0, 1, 2>(r, y0_, z0_);
502
}
503

504
void SurfaceXCylinder::to_hdf5_inner(hid_t group_id) const
300,480,297✔
505
{
506
  write_string(group_id, "type", "x-cylinder", false);
507
  array<double, 3> coeffs {{y0_, z0_, radius_}};
508
  write_dataset(group_id, "coefficients", coeffs);
509
}
510

443,028,718✔
511
BoundingBox SurfaceXCylinder::bounding_box(bool pos_side) const
443,028,718✔
512
{
63,740,278✔
513
  if (!pos_side) {
379,288,440✔
514
    return {-INFTY, INFTY, y0_ - radius_, y0_ + radius_, z0_ - radius_,
515
      z0_ + radius_};
516
  } else {
1,633,646,439✔
517
    return {};
518
  }
519
}
1,633,646,439✔
520
//==============================================================================
1,633,646,439✔
521
// SurfaceYCylinder implementation
369,996✔
522
//==============================================================================
523

1,633,276,443✔
524
SurfaceYCylinder::SurfaceYCylinder(pugi::xml_node surf_node)
1,633,276,443✔
525
  : Surface(surf_node)
1,633,276,443✔
526
{
1,633,276,443✔
527
  read_coeffs(surf_node, id_, {&x0_, &z0_, &radius_});
1,633,276,443✔
528
}
529

1,633,276,443✔
530
double SurfaceYCylinder::evaluate(Position r) const
531
{
269,307,807✔
532
  return axis_aligned_cylinder_evaluate<0, 2>(r, x0_, z0_, radius_);
533
}
1,363,968,636✔
534

535
double SurfaceYCylinder::distance(
536
  Position r, Direction u, bool coincident) const
537
{
621,102,857✔
538
  return axis_aligned_cylinder_distance<1, 0, 2>(
311,631,141✔
539
    r, u, coincident, x0_, z0_, radius_);
540
}
309,471,716✔
541

542
Direction SurfaceYCylinder::normal(Position r) const
543
{
742,865,779✔
544
  return axis_aligned_cylinder_normal<1, 0, 2>(r, x0_, z0_);
545
}
546

547
void SurfaceYCylinder::to_hdf5_inner(hid_t group_id) const
299,837,061✔
548
{
549
  write_string(group_id, "type", "y-cylinder", false);
550
  array<double, 3> coeffs {{x0_, z0_, radius_}};
551
  write_dataset(group_id, "coefficients", coeffs);
552
}
553

443,028,718✔
554
BoundingBox SurfaceYCylinder::bounding_box(bool pos_side) const
443,028,718✔
555
{
63,740,278✔
556
  if (!pos_side) {
379,288,440✔
557
    return {x0_ - radius_, x0_ + radius_, -INFTY, INFTY, z0_ - radius_,
558
      z0_ + radius_};
559
  } else {
×
560
    return {};
561
  }
562
}
×
563

×
564
//==============================================================================
×
565
// SurfaceZCylinder implementation
566
//==============================================================================
×
567

×
568
SurfaceZCylinder::SurfaceZCylinder(pugi::xml_node surf_node)
×
569
  : Surface(surf_node)
×
570
{
×
571
  read_coeffs(surf_node, id_, {&x0_, &y0_, &radius_});
572
}
×
573

574
double SurfaceZCylinder::evaluate(Position r) const
×
575
{
576
  return axis_aligned_cylinder_evaluate<0, 1>(r, x0_, y0_, radius_);
×
577
}
578

579
double SurfaceZCylinder::distance(
580
  Position r, Direction u, bool coincident) const
×
581
{
×
582
  return axis_aligned_cylinder_distance<2, 0, 1>(
583
    r, u, coincident, x0_, y0_, radius_);
×
584
}
585

586
Direction SurfaceZCylinder::normal(Position r) const
×
587
{
588
  return axis_aligned_cylinder_normal<2, 0, 1>(r, x0_, y0_);
589
}
590

×
591
void SurfaceZCylinder::to_hdf5_inner(hid_t group_id) const
592
{
593
  write_string(group_id, "type", "z-cylinder", false);
594
  array<double, 3> coeffs {{x0_, y0_, radius_}};
595
  write_dataset(group_id, "coefficients", coeffs);
596
}
×
597

×
598
BoundingBox SurfaceZCylinder::bounding_box(bool pos_side) const
×
599
{
×
600
  if (!pos_side) {
601
    return {x0_ - radius_, x0_ + radius_, y0_ - radius_, y0_ + radius_, -INFTY,
602
      INFTY};
1,083,236✔
603
  } else {
604
    return {};
605
  }
1,083,236✔
606
}
1,083,236✔
607

440,000✔
608
//==============================================================================
609
// SurfaceSphere implementation
643,236✔
610
//==============================================================================
643,236✔
611

643,236✔
612
SurfaceSphere::SurfaceSphere(pugi::xml_node surf_node) : Surface(surf_node)
643,236✔
613
{
643,236✔
614
  read_coeffs(surf_node, id_, {&x0_, &y0_, &z0_, &radius_});
615
}
643,236✔
616

617
double SurfaceSphere::evaluate(Position r) const
×
618
{
619
  const double x = r.x - x0_;
643,236✔
620
  const double y = r.y - y0_;
621
  const double z = r.z - z0_;
622
  return x * x + y * y + z * z - radius_ * radius_;
623
}
×
624

×
625
double SurfaceSphere::distance(Position r, Direction u, bool coincident) const
626
{
×
627
  const double x = r.x - x0_;
628
  const double y = r.y - y0_;
629
  const double z = r.z - z0_;
643,236✔
630
  const double k = x * u.x + y * u.y + z * u.z;
631
  const double c = x * x + y * y + z * z - radius_ * radius_;
632
  const double quad = k * k - c;
633

643,236✔
634
  if (quad < 0.0) {
635
    // No intersection with sphere.
636
    return INFTY;
637

638
  } else if (coincident || std::abs(c) < FP_COINCIDENT) {
639
    // Particle is on the sphere, thus one distance is positive/negative and
×
640
    // the other is zero. The sign of k determines if we are facing in or out.
×
641
    if (k >= 0.0) {
×
642
      return INFTY;
×
643
    } else {
644
      return -k + sqrt(quad);
645
    }
646

647
  } else if (c < 0.0) {
648
    // Particle is inside the sphere, thus one distance must be negative and
649
    // one must be positive. The positive distance will be the one with
650
    // negative sign on sqrt(quad)
1,387,667✔
651
    return -k + sqrt(quad);
652

653
  } else {
1,387,667✔
654
    // Particle is outside the sphere, thus both distances are either positive
1,387,667✔
655
    // or negative. If positive, the smaller distance is the one with positive
1,387,667✔
656
    // sign on sqrt(quad).
1,387,667✔
657
    const double d = -k - sqrt(quad);
1,387,667✔
658
    if (d < 0.0)
659
      return INFTY;
1,387,667✔
660
    return d;
661
  }
662
}
1,387,667✔
663

1,387,667✔
664
Direction SurfaceSphere::normal(Position r) const
1,387,667✔
665
{
1,387,667✔
666
  return {2.0 * (r.x - x0_), 2.0 * (r.y - y0_), 2.0 * (r.z - z0_)};
1,387,667✔
667
}
668

×
669
void SurfaceSphere::to_hdf5_inner(hid_t group_id) const
670
{
671
  write_string(group_id, "type", "sphere", false);
×
672
  array<double, 4> coeffs {{x0_, y0_, z0_, radius_}};
×
673
  write_dataset(group_id, "coefficients", coeffs);
×
674
}
×
675

×
676
BoundingBox SurfaceSphere::bounding_box(bool pos_side) const
677
{
×
678
  if (!pos_side) {
679
    return {x0_ - radius_, x0_ + radius_, y0_ - radius_, y0_ + radius_,
680
      z0_ - radius_, z0_ + radius_};
×
681
  } else {
×
682
    return {};
×
683
  }
×
684
}
×
685

686
//==============================================================================
687
// Generic functions for x-, y-, and z-, cones
688
//==============================================================================
689

690
// The first template parameter indicates which axis the cone is aligned to.
691
// The other two parameters indicate the other two axes.  offset1, offset2,
32✔
692
// and offset3 should correspond with i1, i2, and i3, respectively.
32✔
693
template<int i1, int i2, int i3>
694
double axis_aligned_cone_evaluate(
32✔
695
  Position r, double offset1, double offset2, double offset3, double radius_sq)
32✔
696
{
697
  const double r1 = r.get<i1>() - offset1;
1,360,486✔
698
  const double r2 = r.get<i2>() - offset2;
699
  const double r3 = r.get<i3>() - offset3;
1,360,486✔
700
  return r2 * r2 + r3 * r3 - radius_sq * r1 * r1;
701
}
702

1,083,236✔
703
// The first template parameter indicates which axis the cone is aligned to.
704
// The other two parameters indicate the other two axes.  offset1, offset2,
705
// and offset3 should correspond with i1, i2, and i3, respectively.
1,083,236✔
706
template<int i1, int i2, int i3>
1,083,236✔
707
double axis_aligned_cone_distance(Position r, Direction u, bool coincident,
708
  double offset1, double offset2, double offset3, double radius_sq)
709
{
×
710
  const double r1 = r.get<i1>() - offset1;
711
  const double r2 = r.get<i2>() - offset2;
×
712
  const double r3 = r.get<i3>() - offset3;
713
  const double a = u.get<i2>() * u.get<i2>() + u.get<i3>() * u.get<i3>() -
714
                   radius_sq * u.get<i1>() * u.get<i1>();
22✔
715
  const double k =
716
    r2 * u.get<i2>() + r3 * u.get<i3>() - radius_sq * r1 * u.get<i1>();
22✔
717
  const double c = r2 * r2 + r3 * r3 - radius_sq * r1 * r1;
22✔
718
  double quad = k * k - a * c;
22✔
719

22✔
720
  double d;
721

×
722
  if (quad < 0.0) {
723
    // No intersection with cone.
×
724
    return INFTY;
×
725

×
726
  } else if (coincident || std::abs(c) < FP_COINCIDENT) {
727
    // Particle is on the cone, thus one distance is positive/negative
×
728
    // and the other is zero. The sign of k determines if we are facing in or
729
    // out.
730
    if (k >= 0.0) {
731
      d = (-k - sqrt(quad)) / a;
732
    } else {
733
      d = (-k + sqrt(quad)) / a;
734
    }
×
735

×
736
  } else {
737
    // Calculate both solutions to the quadratic.
×
738
    quad = sqrt(quad);
739
    d = (-k - quad) / a;
740
    const double b = (-k + quad) / a;
×
741

742
    // Determine the smallest positive solution.
×
743
    if (d < 0.0) {
744
      if (b > 0.0)
745
        d = b;
×
746
    } else {
747
      if (b > 0.0) {
748
        if (b < d)
×
749
          d = b;
×
750
      }
751
    }
752
  }
×
753

754
  // If the distance was negative, set boundary distance to infinity.
×
755
  if (d <= 0.0)
756
    return INFTY;
757
  return d;
×
758
}
759

×
760
// The first template parameter indicates which axis the cone is aligned to.
×
761
// The other two parameters indicate the other two axes.  offset1, offset2,
×
762
// and offset3 should correspond with i1, i2, and i3, respectively.
763
template<int i1, int i2, int i3>
764
Direction axis_aligned_cone_normal(
×
765
  Position r, double offset1, double offset2, double offset3, double radius_sq)
766
{
×
767
  Direction u;
×
768
  u.get<i1>() = -2.0 * radius_sq * (r.get<i1>() - offset1);
×
769
  u.get<i2>() = 2.0 * (r.get<i2>() - offset2);
770
  u.get<i3>() = 2.0 * (r.get<i3>() - offset3);
×
771
  return u;
772
}
773

774
//==============================================================================
775
// SurfaceXCone implementation
776
//==============================================================================
777

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

4,526✔
783
double SurfaceXCone::evaluate(Position r) const
784
{
1,406,991,294✔
785
  return axis_aligned_cone_evaluate<0, 1, 2>(r, x0_, y0_, z0_, radius_sq_);
786
}
1,406,991,294✔
787

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

1,633,646,439✔
794
Direction SurfaceXCone::normal(Position r) const
795
{
796
  return axis_aligned_cone_normal<0, 1, 2>(r, x0_, y0_, z0_, radius_sq_);
1,387,667✔
797
}
798

1,387,667✔
799
void SurfaceXCone::to_hdf5_inner(hid_t group_id) const
800
{
801
  write_string(group_id, "type", "x-cone", false);
3,278✔
802
  array<double, 4> coeffs {{x0_, y0_, z0_, radius_sq_}};
803
  write_dataset(group_id, "coefficients", coeffs);
3,278✔
804
}
3,278✔
805

3,278✔
806
//==============================================================================
3,278✔
807
// SurfaceYCone implementation
808
//==============================================================================
44✔
809

810
SurfaceYCone::SurfaceYCone(pugi::xml_node surf_node) : Surface(surf_node)
44✔
811
{
22✔
812
  read_coeffs(surf_node, id_, {&x0_, &y0_, &z0_, &radius_sq_});
22✔
813
}
814

22✔
815
double SurfaceYCone::evaluate(Position r) const
816
{
817
  return axis_aligned_cone_evaluate<1, 0, 2>(r, y0_, x0_, z0_, radius_sq_);
818
}
819

820
double SurfaceYCone::distance(Position r, Direction u, bool coincident) const
821
{
822
  return axis_aligned_cone_distance<1, 0, 2>(
7,716✔
823
    r, u, coincident, y0_, x0_, z0_, radius_sq_);
824
}
7,716✔
825

7,716✔
826
Direction SurfaceYCone::normal(Position r) const
827
{
542,216,225✔
828
  return axis_aligned_cone_normal<1, 0, 2>(r, y0_, x0_, z0_, radius_sq_);
829
}
542,216,225✔
830

542,216,225✔
831
void SurfaceYCone::to_hdf5_inner(hid_t group_id) const
542,216,225✔
832
{
542,216,225✔
833
  write_string(group_id, "type", "y-cone", false);
834
  array<double, 4> coeffs {{x0_, y0_, z0_, radius_sq_}};
835
  write_dataset(group_id, "coefficients", coeffs);
599,351,686✔
836
}
837

599,351,686✔
838
//==============================================================================
599,351,686✔
839
// SurfaceZCone implementation
599,351,686✔
840
//==============================================================================
599,351,686✔
841

599,351,686✔
842
SurfaceZCone::SurfaceZCone(pugi::xml_node surf_node) : Surface(surf_node)
599,351,686✔
843
{
844
  read_coeffs(surf_node, id_, {&x0_, &y0_, &z0_, &radius_sq_});
599,351,686✔
845
}
846

116,819,827✔
847
double SurfaceZCone::evaluate(Position r) const
848
{
482,531,859✔
849
  return axis_aligned_cone_evaluate<2, 0, 1>(r, z0_, x0_, y0_, radius_sq_);
850
}
851

67,687,142✔
852
double SurfaceZCone::distance(Position r, Direction u, bool coincident) const
33,153,405✔
853
{
854
  return axis_aligned_cone_distance<2, 0, 1>(
34,533,737✔
855
    r, u, coincident, z0_, x0_, y0_, radius_sq_);
856
}
857

414,844,717✔
858
Direction SurfaceZCone::normal(Position r) const
859
{
860
  return axis_aligned_cone_normal<2, 0, 1>(r, z0_, x0_, y0_, radius_sq_);
861
}
371,613,978✔
862

863
void SurfaceZCone::to_hdf5_inner(hid_t group_id) const
864
{
865
  write_string(group_id, "type", "z-cone", false);
866
  array<double, 4> coeffs {{x0_, y0_, z0_, radius_sq_}};
867
  write_dataset(group_id, "coefficients", coeffs);
43,230,739✔
868
}
43,230,739✔
869

9,370,940✔
870
//==============================================================================
33,859,799✔
871
// SurfaceQuadric implementation
872
//==============================================================================
873

874
SurfaceQuadric::SurfaceQuadric(pugi::xml_node surf_node) : Surface(surf_node)
7,180,932✔
875
{
876
  read_coeffs(
7,180,932✔
877
    surf_node, id_, {&A_, &B_, &C_, &D_, &E_, &F_, &G_, &H_, &J_, &K_});
878
}
879

5,950✔
880
double SurfaceQuadric::evaluate(Position r) const
881
{
5,950✔
882
  const double x = r.x;
5,950✔
883
  const double y = r.y;
5,950✔
884
  const double z = r.z;
5,950✔
885
  return x * (A_ * x + D_ * y + G_) + y * (B_ * y + E_ * z + H_) +
886
         z * (C_ * z + F_ * x + J_) + K_;
×
887
}
888

×
889
double SurfaceQuadric::distance(
×
890
  Position r, Direction ang, bool coincident) const
×
891
{
892
  const double& x = r.x;
×
893
  const double& y = r.y;
894
  const double& z = r.z;
895
  const double& u = ang.x;
896
  const double& v = ang.y;
897
  const double& w = ang.z;
898

899
  const double a =
900
    A_ * u * u + B_ * v * v + C_ * w * w + D_ * u * v + E_ * v * w + F_ * u * w;
901
  const double k = A_ * u * x + B_ * v * y + C_ * w * z +
902
                   0.5 * (D_ * (u * y + v * x) + E_ * (v * z + w * y) +
903
                           F_ * (w * x + u * z) + G_ * u + H_ * v + J_ * w);
904
  const double c = A_ * x * x + B_ * y * y + C_ * z * z + D_ * x * y +
322,586✔
905
                   E_ * y * z + F_ * x * z + G_ * x + H_ * y + J_ * z + K_;
906
  double quad = k * k - a * c;
907

322,586✔
908
  double d;
322,586✔
909

322,586✔
910
  if (quad < 0.0) {
322,586✔
911
    // No intersection with surface.
912
    return INFTY;
322,586✔
913

914
  } else if (coincident || std::abs(c) < FP_COINCIDENT) {
915
    // Particle is on the surface, thus one distance is positive/negative and
322,586✔
916
    // the other is zero. The sign of k determines which distance is zero and
322,586✔
917
    // which is not. Additionally, if a is zero, it means the particle is on
322,586✔
918
    // a plane-like surface.
322,586✔
919
    if (a == 0.0) {
920
      d = INFTY; // see the below explanation
×
921
    } else if (k >= 0.0) {
922
      d = (-k - sqrt(quad)) / a;
923
    } else {
×
924
      d = (-k + sqrt(quad)) / a;
×
925
    }
×
926

×
927
  } else if (a == 0.0) {
928
    // Given the orientation of the particle, the quadric looks like a plane in
×
929
    // this case, and thus we have only one solution despite potentially having
930
    // quad > 0.0. While the term under the square root may be real, in one
931
    // case of the +/- of the quadratic formula, 0/0 results, and in another, a
×
932
    // finite value over 0 results. Applying L'Hopital's to the 0/0 case gives
×
933
    // the below. Alternatively this can be found by simply putting a=0 in the
×
934
    // equation ax^2 + bx + c = 0.
×
935
    d = -0.5 * c / k;
936
  } else {
937
    // Calculate both solutions to the quadratic.
938
    quad = sqrt(quad);
939
    d = (-k - quad) / a;
940
    double b = (-k + quad) / a;
941

963,358✔
942
    // Determine the smallest positive solution.
943
    if (d < 0.0) {
944
      if (b > 0.0)
963,358✔
945
        d = b;
963,358✔
946
    } else {
963,358✔
947
      if (b > 0.0) {
963,358✔
948
        if (b < d)
963,358✔
949
          d = b;
963,358✔
950
      }
963,358✔
951
    }
963,358✔
952
  }
963,358✔
953

954
  // If the distance was negative, set boundary distance to infinity.
955
  if (d <= 0.0)
956
    return INFTY;
963,358✔
957
  return d;
958
}
×
959

960
Direction SurfaceQuadric::normal(Position r) const
963,358✔
961
{
962
  const double& x = r.x;
963
  const double& y = r.y;
964
  const double& z = r.z;
60,566✔
965
  return {2.0 * A_ * x + D_ * y + F_ * z + G_,
×
966
    2.0 * B_ * y + D_ * x + E_ * z + H_, 2.0 * C_ * z + E_ * y + F_ * x + J_};
967
}
60,566✔
968

969
void SurfaceQuadric::to_hdf5_inner(hid_t group_id) const
970
{
971
  write_string(group_id, "type", "quadric", false);
972
  array<double, 10> coeffs {{A_, B_, C_, D_, E_, F_, G_, H_, J_, K_}};
902,792✔
973
  write_dataset(group_id, "coefficients", coeffs);
902,792✔
974
}
902,792✔
975

976
//==============================================================================
977
// Torus helper functions
902,792✔
978
//==============================================================================
766,524✔
979

650,507✔
980
double torus_distance(double x1, double x2, double x3, double u1, double u2,
981
  double u3, double A, double B, double C, bool coincident)
136,268✔
982
{
136,268✔
983
  // Coefficients for equation: (c2 t^2 + c1 t + c0)^2 = c2' t^2 + c1' t + c0'
136,268✔
984
  double D = (C * C) / (B * B);
985
  double c2 = u1 * u1 + u2 * u2 + D * u3 * u3;
986
  double c1 = 2 * (u1 * x1 + u2 * x2 + D * u3 * x3);
987
  double c0 = x1 * x1 + x2 * x2 + D * x3 * x3 + A * A - C * C;
988
  double four_A2 = 4 * A * A;
989
  double c2p = four_A2 * (u1 * u1 + u2 * u2);
963,358✔
990
  double c1p = 2 * four_A2 * (u1 * x1 + u2 * x2);
137,467✔
991
  double c0p = four_A2 * (x1 * x1 + x2 * x2);
825,891✔
992

993
  // Coefficient for equation: a t^4 + b t^3 + c t^2 + d t + e = 0. If the point
963,358✔
994
  // is coincident, the 'e' coefficient should be zero. Explicitly setting it to
995
  // zero helps avoid numerical issues below with root finding.
996
  double coeff[5];
963,358✔
997
  coeff[0] = coincident ? 0.0 : c0 * c0 - c0p;
963,358✔
998
  coeff[1] = 2 * c0 * c1 - c1p;
963,358✔
999
  coeff[2] = c1 * c1 + 2 * c0 * c2 - c2p;
963,358✔
1000
  coeff[3] = 2 * c1 * c2;
963,358✔
1001
  coeff[4] = c2 * c2;
963,358✔
1002

963,358✔
1003
  std::complex<double> roots[4];
963,358✔
1004
  oqs::quartic_solver(coeff, roots);
963,358✔
1005

1006
  // Find smallest positive, real root. In the case where the particle is
1007
  // coincident with the surface, we are sure to have one root very close to
1008
  // zero but possibly small and positive. A tolerance is set to discard that
963,358✔
1009
  // zero.
1010
  double distance = INFTY;
×
1011
  double cutoff = coincident ? TORUS_TOL : 0.0;
1012
  for (int i = 0; i < 4; ++i) {
963,358✔
1013
    if (roots[i].imag() == 0) {
1014
      double root = roots[i].real();
1015
      if (root > cutoff && root < distance) {
1016
        // Avoid roots corresponding to internal surfaces
60,566✔
1017
        double s1 = x1 + u1 * root;
×
1018
        double s2 = x2 + u2 * root;
1019
        double s3 = x3 + u3 * root;
60,566✔
1020
        double check = D * s3 * s3 + s1 * s1 + s2 * s2 + A * A - C * C;
1021
        if (check >= 0) {
1022
          distance = root;
1023
        }
1024
      }
902,792✔
1025
    }
902,792✔
1026
  }
902,792✔
1027
  return distance;
1028
}
1029

902,792✔
1030
//==============================================================================
766,524✔
1031
// SurfaceXTorus implementation
650,507✔
1032
//==============================================================================
1033

136,268✔
1034
SurfaceXTorus::SurfaceXTorus(pugi::xml_node surf_node) : Surface(surf_node)
136,268✔
1035
{
136,268✔
1036
  read_coeffs(surf_node, id_, {&x0_, &y0_, &z0_, &A_, &B_, &C_});
1037
}
1038

1039
void SurfaceXTorus::to_hdf5_inner(hid_t group_id) const
1040
{
1041
  write_string(group_id, "type", "x-torus", false);
963,358✔
1042
  std::array<double, 6> coeffs {{x0_, y0_, z0_, A_, B_, C_}};
137,467✔
1043
  write_dataset(group_id, "coefficients", coeffs);
825,891✔
1044
}
1045

×
1046
double SurfaceXTorus::evaluate(Position r) const
1047
{
1048
  double x = r.x - x0_;
×
1049
  double y = r.y - y0_;
×
1050
  double z = r.z - z0_;
×
1051
  return (x * x) / (B_ * B_) +
×
1052
         std::pow(std::sqrt(y * y + z * z) - A_, 2) / (C_ * C_) - 1.;
×
1053
}
×
1054

×
1055
double SurfaceXTorus::distance(Position r, Direction u, bool coincident) const
×
1056
{
×
1057
  double x = r.x - x0_;
1058
  double y = r.y - y0_;
1059
  double z = r.z - z0_;
1060
  return torus_distance(y, z, x, u.y, u.z, u.x, A_, B_, C_, coincident);
×
1061
}
1062

×
1063
Direction SurfaceXTorus::normal(Position r) const
1064
{
×
1065
  // reduce the expansion of the full form for torus
1066
  double x = r.x - x0_;
1067
  double y = r.y - y0_;
1068
  double z = r.z - z0_;
×
1069

×
1070
  // f(x,y,z) = x^2/B^2 + (sqrt(y^2 + z^2) - A)^2/C^2 - 1
1071
  // ∂f/∂x = 2x/B^2
×
1072
  // ∂f/∂y = 2y(g - A)/(g*C^2) where g = sqrt(y^2 + z^2)
1073
  // ∂f/∂z = 2z(g - A)/(g*C^2)
1074
  // Multiplying by g*C^2*B^2 / 2 gives:
1075
  double g = std::sqrt(y * y + z * z);
1076
  double nx = C_ * C_ * g * x;
×
1077
  double ny = y * (g - A_) * B_ * B_;
×
1078
  double nz = z * (g - A_) * B_ * B_;
×
1079
  Direction n(nx, ny, nz);
1080
  return n / n.norm();
1081
}
×
1082

×
1083
//==============================================================================
×
1084
// SurfaceYTorus implementation
1085
//==============================================================================
×
1086

×
1087
SurfaceYTorus::SurfaceYTorus(pugi::xml_node surf_node) : Surface(surf_node)
×
1088
{
1089
  read_coeffs(surf_node, id_, {&x0_, &y0_, &z0_, &A_, &B_, &C_});
1090
}
1091

1092
void SurfaceYTorus::to_hdf5_inner(hid_t group_id) const
1093
{
×
1094
  write_string(group_id, "type", "y-torus", false);
×
1095
  std::array<double, 6> coeffs {{x0_, y0_, z0_, A_, B_, C_}};
×
1096
  write_dataset(group_id, "coefficients", coeffs);
1097
}
×
1098

1099
double SurfaceYTorus::evaluate(Position r) const
1100
{
×
1101
  double x = r.x - x0_;
×
1102
  double y = r.y - y0_;
×
1103
  double z = r.z - z0_;
×
1104
  return (y * y) / (B_ * B_) +
×
1105
         std::pow(std::sqrt(x * x + z * z) - A_, 2) / (C_ * C_) - 1.;
×
1106
}
×
1107

×
1108
double SurfaceYTorus::distance(Position r, Direction u, bool coincident) const
×
1109
{
1110
  double x = r.x - x0_;
1111
  double y = r.y - y0_;
1112
  double z = r.z - z0_;
×
1113
  return torus_distance(x, z, y, u.x, u.z, u.y, A_, B_, C_, coincident);
1114
}
×
1115

1116
Direction SurfaceYTorus::normal(Position r) const
×
1117
{
1118
  // reduce the expansion of the full form for torus
1119
  double x = r.x - x0_;
1120
  double y = r.y - y0_;
×
1121
  double z = r.z - z0_;
×
1122

1123
  // f(x,y,z) = y^2/B^2 + (sqrt(x^2 + z^2) - A)^2/C^2 - 1
×
1124
  // ∂f/∂x = 2x(g - A)/(g*C^2) where g = sqrt(x^2 + z^2)
1125
  // ∂f/∂y = 2y/B^2
1126
  // ∂f/∂z = 2z(g - A)/(g*C^2)
1127
  // Multiplying by g*C^2*B^2 / 2 gives:
1128
  double g = std::sqrt(x * x + z * z);
×
1129
  double nx = x * (g - A_) * B_ * B_;
×
1130
  double ny = C_ * C_ * g * y;
×
1131
  double nz = z * (g - A_) * B_ * B_;
1132
  Direction n(nx, ny, nz);
1133
  return n / n.norm();
×
1134
}
×
1135

×
1136
//==============================================================================
1137
// SurfaceZTorus implementation
×
1138
//==============================================================================
×
1139

×
1140
SurfaceZTorus::SurfaceZTorus(pugi::xml_node surf_node) : Surface(surf_node)
1141
{
1142
  read_coeffs(surf_node, id_, {&x0_, &y0_, &z0_, &A_, &B_, &C_});
1143
}
1144

1145
void SurfaceZTorus::to_hdf5_inner(hid_t group_id) const
×
1146
{
×
1147
  write_string(group_id, "type", "z-torus", false);
×
1148
  std::array<double, 6> coeffs {{x0_, y0_, z0_, A_, B_, C_}};
1149
  write_dataset(group_id, "coefficients", coeffs);
1150
}
1151

1152
double SurfaceZTorus::evaluate(Position r) const
1153
{
1154
  double x = r.x - x0_;
60,566✔
1155
  double y = r.y - y0_;
1156
  double z = r.z - z0_;
1157
  return (z * z) / (B_ * B_) +
60,566✔
1158
         std::pow(std::sqrt(x * x + y * y) - A_, 2) / (C_ * C_) - 1.;
60,566✔
1159
}
60,566✔
1160

60,566✔
1161
double SurfaceZTorus::distance(Position r, Direction u, bool coincident) const
60,566✔
1162
{
1163
  double x = r.x - x0_;
60,566✔
1164
  double y = r.y - y0_;
1165
  double z = r.z - z0_;
1166
  return torus_distance(x, y, z, u.x, u.y, u.z, A_, B_, C_, coincident);
60,566✔
1167
}
60,566✔
1168

60,566✔
1169
Direction SurfaceZTorus::normal(Position r) const
60,566✔
1170
{
60,566✔
1171
  // reduce the expansion of the full form for torus
1172
  double x = r.x - x0_;
×
1173
  double y = r.y - y0_;
1174
  double z = r.z - z0_;
1175

×
1176
  // f(x,y,z) = z^2/B^2 + (sqrt(x^2 + y^2) - A)^2/C^2 - 1
×
1177
  // ∂f/∂x = 2x(g - A)/(g*C^2) where g = sqrt(x^2 + y^2)
×
1178
  // ∂f/∂y = 2y(g - A)/(g*C^2)
×
1179
  // ∂f/∂z = 2z/B^2
×
1180
  // Multiplying by g*C^2*B^2 / 2 gives:
1181
  double g = std::sqrt(x * x + y * y);
×
1182
  double nx = x * (g - A_) * B_ * B_;
1183
  double ny = y * (g - A_) * B_ * B_;
1184
  double nz = C_ * C_ * g * z;
×
1185
  Position n(nx, ny, nz);
×
1186
  return n / n.norm();
×
1187
}
×
1188

×
1189
//==============================================================================
1190

1191
void read_surfaces(pugi::xml_node node)
1192
{
1193
  // Count the number of surfaces
1194
  int n_surfaces = 0;
1195
  for (pugi::xml_node surf_node : node.children("surface")) {
×
1196
    n_surfaces++;
1197
  }
×
1198

1199
  // Loop over XML surface elements and populate the array.  Keep track of
1200
  // periodic surfaces and their albedos.
×
1201
  model::surfaces.reserve(n_surfaces);
1202
  std::set<std::pair<int, int>> periodic_pairs;
×
1203
  std::unordered_map<int, double> albedo_map;
1204
  {
1205
    pugi::xml_node surf_node;
×
1206
    int i_surf;
1207
    for (surf_node = node.child("surface"), i_surf = 0; surf_node;
×
1208
         surf_node = surf_node.next_sibling("surface"), i_surf++) {
×
1209
      std::string surf_type = get_node_value(surf_node, "type", true, true);
1210

1211
      // Allocate and initialize the new surface
×
1212

1213
      if (surf_type == "x-plane") {
×
1214
        model::surfaces.push_back(make_unique<SurfaceXPlane>(surf_node));
1215

1216
      } else if (surf_type == "y-plane") {
×
1217
        model::surfaces.push_back(make_unique<SurfaceYPlane>(surf_node));
1218

×
1219
      } else if (surf_type == "z-plane") {
×
1220
        model::surfaces.push_back(make_unique<SurfaceZPlane>(surf_node));
×
1221

1222
      } else if (surf_type == "plane") {
1223
        model::surfaces.push_back(make_unique<SurfacePlane>(surf_node));
1224

1225
      } else if (surf_type == "x-cylinder") {
1226
        model::surfaces.push_back(make_unique<SurfaceXCylinder>(surf_node));
1227

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

1231
      } else if (surf_type == "z-cylinder") {
1232
        model::surfaces.push_back(make_unique<SurfaceZCylinder>(surf_node));
×
1233

1234
      } else if (surf_type == "sphere") {
×
1235
        model::surfaces.push_back(make_unique<SurfaceSphere>(surf_node));
1236

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

×
1240
      } else if (surf_type == "y-cone") {
×
1241
        model::surfaces.push_back(make_unique<SurfaceYCone>(surf_node));
1242

1243
      } else if (surf_type == "z-cone") {
×
1244
        model::surfaces.push_back(make_unique<SurfaceZCone>(surf_node));
1245

×
1246
      } else if (surf_type == "quadric") {
1247
        model::surfaces.push_back(make_unique<SurfaceQuadric>(surf_node));
1248

×
1249
      } else if (surf_type == "x-torus") {
1250
        model::surfaces.push_back(std::make_unique<SurfaceXTorus>(surf_node));
×
1251

×
1252
      } else if (surf_type == "y-torus") {
×
1253
        model::surfaces.push_back(std::make_unique<SurfaceYTorus>(surf_node));
1254

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

1258
      } else {
1259
        fatal_error(fmt::format("Invalid surface type, \"{}\"", surf_type));
16✔
1260
      }
1261

16✔
1262
      // Check for a periodic surface
16✔
1263
      if (check_for_node(surf_node, "boundary")) {
1264
        std::string surf_bc = get_node_value(surf_node, "boundary", true, true);
322,586✔
1265
        if (surf_bc == "periodic") {
1266
          // Check for surface albedo. Skip sanity check as it is already done
322,586✔
1267
          // in the Surface class's constructor.
1268
          if (check_for_node(surf_node, "albedo")) {
1269
            albedo_map[model::surfaces.back()->id_] =
963,358✔
1270
              std::stod(get_node_value(surf_node, "albedo"));
1271
          }
963,358✔
1272
          if (check_for_node(surf_node, "periodic_surface_id")) {
963,358✔
1273
            int i_periodic =
1274
              std::stoi(get_node_value(surf_node, "periodic_surface_id"));
1275
            int lo_id = std::min(model::surfaces.back()->id_, i_periodic);
60,566✔
1276
            int hi_id = std::max(model::surfaces.back()->id_, i_periodic);
1277
            periodic_pairs.insert({lo_id, hi_id});
60,566✔
1278
          } else {
1279
            periodic_pairs.insert({model::surfaces.back()->id_, -1});
1280
          }
11✔
1281
        }
1282
      }
11✔
1283
    }
11✔
1284
  }
11✔
1285

11✔
1286
  // Fill the surface map
1287
  for (int i_surf = 0; i_surf < model::surfaces.size(); i_surf++) {
1288
    int id = model::surfaces[i_surf]->id_;
1289
    auto in_map = model::surface_map.find(id);
1290
    if (in_map == model::surface_map.end()) {
1291
      model::surface_map[id] = i_surf;
16✔
1292
    } else {
1293
      fatal_error(
×
1294
        fmt::format("Two or more surfaces use the same unique ID: {}", id));
16✔
1295
    }
16✔
1296
  }
1297

177,572✔
1298
  // Resolve unpaired periodic surfaces.  A lambda function is used with
1299
  // std::find_if to identify the unpaired surfaces.
177,572✔
1300
  auto is_unresolved_pair = [](const std::pair<int, int> p) {
177,572✔
1301
    return p.second == -1;
177,572✔
1302
  };
177,572✔
1303
  auto first_unresolved = std::find_if(
177,572✔
1304
    periodic_pairs.begin(), periodic_pairs.end(), is_unresolved_pair);
1305
  if (first_unresolved != periodic_pairs.end()) {
1306
    // Found one unpaired surface; search for a second one
312,510✔
1307
    auto next_elem = first_unresolved;
1308
    next_elem++;
1309
    auto second_unresolved =
312,510✔
1310
      std::find_if(next_elem, periodic_pairs.end(), is_unresolved_pair);
312,510✔
1311
    if (second_unresolved == periodic_pairs.end()) {
312,510✔
1312
      fatal_error("Found only one periodic surface without a specified partner."
312,510✔
1313
                  " Please specify the partner for each periodic surface.");
312,510✔
1314
    }
312,510✔
1315

1316
    // Make sure there isn't a third unpaired surface
312,510✔
1317
    next_elem = second_unresolved;
312,510✔
1318
    next_elem++;
312,510✔
1319
    auto third_unresolved =
312,510✔
1320
      std::find_if(next_elem, periodic_pairs.end(), is_unresolved_pair);
312,510✔
1321
    if (third_unresolved != periodic_pairs.end()) {
312,510✔
1322
      fatal_error(
312,510✔
1323
        "Found at least three periodic surfaces without a specified "
312,510✔
1324
        "partner. Please specify the partner for each periodic surface.");
1325
    }
1326

1327
    // Add the completed pair and remove the old, unpaired entries
312,510✔
1328
    int lo_id = std::min(first_unresolved->first, second_unresolved->first);
1329
    int hi_id = std::max(first_unresolved->first, second_unresolved->first);
×
1330
    periodic_pairs.insert({lo_id, hi_id});
1331
    periodic_pairs.erase(first_unresolved);
312,510✔
1332
    periodic_pairs.erase(second_unresolved);
1333
  }
1334

1335
  // Assign the periodic boundary conditions with albedos
1336
  for (auto periodic_pair : periodic_pairs) {
34,540✔
1337
    int i_surf = model::surface_map[periodic_pair.first];
×
1338
    int j_surf = model::surface_map[periodic_pair.second];
34,540✔
1339
    Surface& surf1 {*model::surfaces[i_surf]};
×
1340
    Surface& surf2 {*model::surfaces[j_surf]};
1341

34,540✔
1342
    // Compute the dot product of the surface normals
1343
    Direction norm1 = surf1.normal({0, 0, 0});
1344
    Direction norm2 = surf2.normal({0, 0, 0});
277,970✔
1345
    norm1 /= norm1.norm();
1346
    norm2 /= norm2.norm();
1347
    double dot_prod = norm1.dot(norm2);
1348

1349
    // If the dot product is 1 (to within floating point precision) then the
1350
    // planes are parallel which indicates a translational periodic boundary
1351
    // condition.  Otherwise, it is a rotational periodic BC.
1352
    if (std::abs(1.0 - dot_prod) < FP_PRECISION) {
×
1353
      surf1.bc_ = make_unique<TranslationalPeriodicBC>(i_surf, j_surf);
1354
      surf2.bc_ = make_unique<TranslationalPeriodicBC>(i_surf, j_surf);
1355
    } else {
277,970✔
1356
      surf1.bc_ = make_unique<RotationalPeriodicBC>(i_surf, j_surf);
277,970✔
1357
      surf2.bc_ = make_unique<RotationalPeriodicBC>(i_surf, j_surf);
277,970✔
1358
    }
1359

1360
    // If albedo data is present in albedo map, set the boundary albedo.
277,970✔
1361
    if (albedo_map.count(surf1.id_)) {
277,970✔
1362
      surf1.bc_->set_albedo(albedo_map[surf1.id_]);
277,970✔
1363
    }
1364
    if (albedo_map.count(surf2.id_)) {
×
1365
      surf2.bc_->set_albedo(albedo_map[surf2.id_]);
×
1366
    }
×
1367
  }
1368
}
1369

1370
void free_memory_surfaces()
1371
{
1372
  model::surfaces.clear();
312,510✔
1373
  model::surface_map.clear();
×
1374
}
312,510✔
1375

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