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

openmc-dev / openmc / 6698575604

30 Oct 2023 08:51PM UTC coverage: 84.584% (-0.003%) from 84.587%
6698575604

push

github

web-flow
Leaky and Albedo Boundary Conditions Implementation (#2724)

Co-authored-by: Paul Romano <paul.k.romano@gmail.com>

86 of 98 new or added lines in 6 files covered. (87.76%)

61 existing lines in 1 file now uncovered.

46979 of 55541 relevant lines covered (84.58%)

90797222.01 hits per line

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

71.67
/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
#include <gsl/gsl-lite.hpp>
11

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

23
namespace openmc {
24

25
//==============================================================================
26
// Global variables
27
//==============================================================================
28

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

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

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

49
  // Copy the coefficients
50
  int i = 0;
39,129✔
51
  for (auto c : coeffs) {
117,387✔
52
    *c = coeffs_file[i++];
78,258✔
53
  }
54
}
39,129✔
55

56
//==============================================================================
57
// Surface implementation
58
//==============================================================================
59

60
Surface::Surface() {} // empty constructor
285✔
61

62
Surface::Surface(pugi::xml_node surf_node)
39,129✔
63
{
64
  if (check_for_node(surf_node, "id")) {
39,129✔
65
    id_ = std::stoi(get_node_value(surf_node, "id"));
39,129✔
66
    if (contains(settings::source_write_surf_id, id_)) {
39,129✔
67
      surf_source_ = true;
14✔
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")) {
39,129✔
74
    name_ = get_node_value(surf_node, "name", false);
9,351✔
75
  }
76

77
  if (check_for_node(surf_node, "boundary")) {
39,129✔
78
    std::string surf_bc = get_node_value(surf_node, "boundary", true, true);
25,165✔
79

80
    if (surf_bc == "transmission" || surf_bc == "transmit" || surf_bc.empty()) {
25,165✔
81
      // Leave the bc_ a nullptr
82
    } else if (surf_bc == "vacuum") {
25,165✔
83
      bc_ = make_unique<VacuumBC>();
9,971✔
84
    } else if (surf_bc == "reflective" || surf_bc == "reflect" ||
15,536✔
85
               surf_bc == "reflecting") {
342✔
86
      bc_ = make_unique<ReflectiveBC>();
14,852✔
87
    } else if (surf_bc == "white") {
342✔
88
      bc_ = make_unique<WhiteBC>();
114✔
89
    } else if (surf_bc == "periodic") {
228✔
90
      // Periodic BCs are handled separately
91
    } else {
92
      fatal_error(fmt::format("Unknown boundary condition \"{}\" specified "
×
93
                              "on surface {}",
94
        surf_bc, id_));
×
95
    }
96

97
    if (check_for_node(surf_node, "albedo") && bc_) {
25,165✔
98
      double surf_alb = std::stod(get_node_value(surf_node, "albedo"));
114✔
99

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

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

111
      bc_->set_albedo(surf_alb);
114✔
112
    }
113
  }
25,165✔
114
}
39,129✔
115

116
bool Surface::sense(Position r, Direction u) const
8,354,932,333✔
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);
8,354,932,333✔
121

122
  // Check which side of surface the point is on.
123
  if (std::abs(f) < FP_COINCIDENT) {
8,354,932,333✔
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,342,081✔
128
  }
129
  return f > 0.0;
8,353,590,252✔
130
}
131

132
Direction Surface::reflect(Position r, Direction u, Particle* p) const
1,035,168,445✔
133
{
134
  // Determine projection of direction onto normal and squared magnitude of
135
  // normal.
136
  Direction n = normal(r);
1,035,168,445✔
137

138
  // Reflect direction according to normal.
139
  return u.reflect(n);
2,070,336,890✔
140
}
141

142
Direction Surface::diffuse_reflect(
1,294,342✔
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,294,342✔
149
  n /= n.norm();
1,294,342✔
150
  const double projection = n.dot(u);
1,294,342✔
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,294,342✔
155

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

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

163
void Surface::to_hdf5(hid_t group_id) const
32,745✔
164
{
165
  hid_t surf_group = create_group(group_id, fmt::format("surface {}", id_));
65,490✔
166

167
  if (geom_type_ == GeometryType::DAG) {
32,745✔
168
    write_string(surf_group, "geom_type", "dagmc", false);
111✔
169
  } else if (geom_type_ == GeometryType::CSG) {
32,634✔
170
    write_string(surf_group, "geom_type", "csg", false);
32,634✔
171

172
    if (bc_) {
32,634✔
173
      write_string(surf_group, "boundary_type", bc_->type(), false);
21,611✔
174
      bc_->to_hdf5(surf_group);
21,611✔
175
    } else {
176
      write_string(surf_group, "boundary_type", "transmission", false);
11,023✔
177
    }
178
  }
179

180
  if (!name_.empty()) {
32,745✔
181
    write_string(surf_group, "name", name_, false);
7,768✔
182
  }
183

184
  to_hdf5_inner(surf_group);
32,745✔
185

186
  close_group(surf_group);
32,745✔
187
}
32,745✔
188

189
CSGSurface::CSGSurface() : Surface {}
×
190
{
191
  geom_type_ = GeometryType::CSG;
×
192
};
193
CSGSurface::CSGSurface(pugi::xml_node surf_node) : Surface {surf_node}
39,129✔
194
{
195
  geom_type_ = GeometryType::CSG;
39,129✔
196
};
39,129✔
197

198
//==============================================================================
199
// Generic functions for x-, y-, and z-, planes.
200
//==============================================================================
201

202
// The template parameter indicates the axis normal to the plane.
203
template<int i>
204
double axis_aligned_plane_distance(
20,902,087,280✔
205
  Position r, Direction u, bool coincident, double offset)
206
{
207
  const double f = offset - r[i];
20,902,087,280✔
208
  if (coincident || std::abs(f) < FP_COINCIDENT || u[i] == 0.0)
20,902,087,280✔
209
    return INFTY;
1,023,118,606✔
210
  const double d = f / u[i];
20,388,121,322✔
211
  if (d < 0.0)
20,388,121,322✔
212
    return INFTY;
10,630,266,056✔
213
  return d;
11,629,891,260✔
214
}
215

3,809,940,196✔
216
//==============================================================================
217
// SurfaceXPlane implementation
218
//==============================================================================
3,809,940,196✔
219

3,809,940,196✔
220
SurfaceXPlane::SurfaceXPlane(pugi::xml_node surf_node) : CSGSurface(surf_node)
218,512,921✔
221
{
3,591,427,275✔
222
  read_coeffs(surf_node, id_, {&x0_});
3,591,427,275✔
223
}
1,688,414,318✔
224

1,903,012,957✔
225
double SurfaceXPlane::evaluate(Position r) const
226
{
9,834,280,298✔
227
  return r.x - x0_;
228
}
229

9,834,280,298✔
230
double SurfaceXPlane::distance(Position r, Direction u, bool coincident) const
9,834,280,298✔
231
{
485,442,286✔
232
  return axis_aligned_plane_distance<0>(r, u, coincident, x0_);
9,348,838,012✔
233
}
9,348,838,012✔
234

4,439,912,597✔
235
Direction SurfaceXPlane::normal(Position r) const
4,908,925,415✔
236
{
237
  return {1., 0., 0.};
9,639,055,428✔
238
}
239

240
void SurfaceXPlane::to_hdf5_inner(hid_t group_id) const
9,639,055,428✔
241
{
9,639,055,428✔
242
  write_string(group_id, "type", "x-plane", false);
319,163,399✔
243
  array<double, 1> coeffs {{x0_}};
9,319,892,029✔
244
  write_dataset(group_id, "coefficients", coeffs);
9,319,892,029✔
245
}
4,501,939,141✔
246

4,817,952,888✔
247
BoundingBox SurfaceXPlane::bounding_box(bool pos_side) const
248
{
249
  if (pos_side) {
250
    return {x0_, INFTY, -INFTY, INFTY, -INFTY, INFTY};
251
  } else {
252
    return {-INFTY, x0_, -INFTY, INFTY, -INFTY, INFTY};
253
  }
9,619✔
254
}
255

9,619✔
256
//==============================================================================
9,619✔
257
// SurfaceYPlane implementation
258
//==============================================================================
2,407,192,654✔
259

260
SurfaceYPlane::SurfaceYPlane(pugi::xml_node surf_node) : CSGSurface(surf_node)
2,407,192,654✔
261
{
262
  read_coeffs(surf_node, id_, {&y0_});
263
}
9,639,055,428✔
264

265
double SurfaceYPlane::evaluate(Position r) const
9,639,055,428✔
266
{
267
  return r.y - y0_;
268
}
319,774,745✔
269

270
double SurfaceYPlane::distance(Position r, Direction u, bool coincident) const
319,774,745✔
271
{
272
  return axis_aligned_plane_distance<1>(r, u, coincident, y0_);
273
}
7,936✔
274

275
Direction SurfaceYPlane::normal(Position r) const
7,936✔
276
{
7,936✔
277
  return {0., 1., 0.};
7,936✔
278
}
7,936✔
279

280
void SurfaceYPlane::to_hdf5_inner(hid_t group_id) const
308✔
281
{
282
  write_string(group_id, "type", "y-plane", false);
308✔
283
  array<double, 1> coeffs {{y0_}};
154✔
284
  write_dataset(group_id, "coefficients", coeffs);
285
}
154✔
286

287
BoundingBox SurfaceYPlane::bounding_box(bool pos_side) const
288
{
289
  if (pos_side) {
290
    return {-INFTY, INFTY, y0_, INFTY, -INFTY, INFTY};
291
  } else {
292
    return {-INFTY, INFTY, -INFTY, y0_, -INFTY, INFTY};
293
  }
8,782✔
294
}
295

8,782✔
296
//==============================================================================
8,782✔
297
// SurfaceZPlane implementation
298
//==============================================================================
2,267,116,703✔
299

300
SurfaceZPlane::SurfaceZPlane(pugi::xml_node surf_node) : CSGSurface(surf_node)
2,267,116,703✔
301
{
302
  read_coeffs(surf_node, id_, {&z0_});
303
}
9,834,280,298✔
304

305
double SurfaceZPlane::evaluate(Position r) const
9,834,280,298✔
306
{
307
  return r.z - z0_;
308
}
486,417,554✔
309

310
double SurfaceZPlane::distance(Position r, Direction u, bool coincident) const
486,417,554✔
311
{
312
  return axis_aligned_plane_distance<2>(r, u, coincident, z0_);
313
}
7,488✔
314

315
Direction SurfaceZPlane::normal(Position r) const
7,488✔
316
{
7,488✔
317
  return {0., 0., 1.};
7,488✔
318
}
7,488✔
319

320
void SurfaceZPlane::to_hdf5_inner(hid_t group_id) const
308✔
321
{
322
  write_string(group_id, "type", "z-plane", false);
308✔
323
  array<double, 1> coeffs {{z0_}};
154✔
324
  write_dataset(group_id, "coefficients", coeffs);
325
}
154✔
326

327
BoundingBox SurfaceZPlane::bounding_box(bool pos_side) const
328
{
329
  if (pos_side) {
330
    return {-INFTY, INFTY, -INFTY, INFTY, z0_, INFTY};
331
  } else {
332
    return {-INFTY, INFTY, -INFTY, INFTY, -INFTY, z0_};
333
  }
6,304✔
334
}
335

6,304✔
336
//==============================================================================
6,304✔
337
// SurfacePlane implementation
338
//==============================================================================
1,032,098,440✔
339

340
SurfacePlane::SurfacePlane(pugi::xml_node surf_node) : CSGSurface(surf_node)
1,032,098,440✔
341
{
342
  read_coeffs(surf_node, id_, {&A_, &B_, &C_, &D_});
343
}
3,809,940,196✔
344

345
double SurfacePlane::evaluate(Position r) const
3,809,940,196✔
346
{
347
  return A_ * r.x + B_ * r.y + C_ * r.z - D_;
348
}
216,184,732✔
349

350
double SurfacePlane::distance(Position r, Direction u, bool coincident) const
216,184,732✔
351
{
352
  const double f = A_ * r.x + B_ * r.y + C_ * r.z - D_;
353
  const double projection = A_ * u.x + B_ * u.y + C_ * u.z;
5,620✔
354
  if (coincident || std::abs(f) < FP_COINCIDENT || projection == 0.0) {
355
    return INFTY;
5,620✔
356
  } else {
5,620✔
357
    const double d = -f / projection;
5,620✔
358
    if (d < 0.0)
5,620✔
359
      return INFTY;
360
    return d;
×
361
  }
362
}
×
363

×
364
Direction SurfacePlane::normal(Position r) const
365
{
×
366
  return {A_, B_, C_};
367
}
368

369
void SurfacePlane::to_hdf5_inner(hid_t group_id) const
370
{
371
  write_string(group_id, "type", "plane", false);
372
  array<double, 4> coeffs {{A_, B_, C_, D_}};
373
  write_dataset(group_id, "coefficients", coeffs);
598✔
374
}
375

598✔
376
//==============================================================================
598✔
377
// Generic functions for x-, y-, and z-, cylinders
378
//==============================================================================
84,425,108✔
379

380
// The template parameters indicate the axes perpendicular to the axis of the
84,425,108✔
381
// cylinder.  offset1 and offset2 should correspond with i1 and i2,
382
// respectively.
383
template<int i1, int i2>
474,668,728✔
384
double axis_aligned_cylinder_evaluate(
385
  Position r, double offset1, double offset2, double radius)
474,668,728✔
386
{
474,668,728✔
387
  const double r1 = r.get<i1>() - offset1;
474,668,728✔
388
  const double r2 = r.get<i2>() - offset2;
10,791,454✔
389
  return r1 * r1 + r2 * r2 - radius * radius;
390
}
463,877,274✔
391

463,877,274✔
392
// The first template parameter indicates which axis the cylinder is aligned to.
226,517,624✔
393
// The other two parameters indicate the other two axes.  offset1 and offset2
237,359,650✔
394
// should correspond with i2 and i3, respectively.
395
template<int i1, int i2, int i3>
396
double axis_aligned_cylinder_distance(Position r, Direction u, bool coincident,
397
  double offset1, double offset2, double radius)
7,384,246✔
398
{
399
  const double a = 1.0 - u.get<i1>() * u.get<i1>(); // u^2 + v^2
7,384,246✔
400
  if (a == 0.0)
401
    return INFTY;
402

432✔
403
  const double r2 = r.get<i2>() - offset1;
404
  const double r3 = r.get<i3>() - offset2;
432✔
405
  const double k = r2 * u.get<i2>() + r3 * u.get<i3>();
432✔
406
  const double c = r2 * r2 + r3 * r3 - radius * radius;
432✔
407
  const double quad = k * k - a * c;
432✔
408

409
  if (quad < 0.0) {
410
    // No intersection with cylinder.
411
    return INFTY;
412

413
  } else if (coincident || std::abs(c) < FP_COINCIDENT) {
414
    // Particle is on the cylinder, thus one distance is positive/negative
415
    // and the other is zero. The sign of k determines if we are facing in or
416
    // out.
417
    if (k >= 0.0) {
1,965,379,200✔
418
      return INFTY;
419
    } else {
420
      return (-k + sqrt(quad)) / a;
1,965,379,200✔
421
    }
1,965,379,200✔
422

1,965,379,200✔
423
  } else if (c < 0.0) {
424
    // Particle is inside the cylinder, thus one distance must be negative
1,963,634,250✔
425
    // and one must be positive. The positive distance will be the one with
426
    // negative sign on sqrt(quad).
427
    return (-k + sqrt(quad)) / a;
1,963,634,250✔
428

1,963,634,250✔
429
  } else {
1,963,634,250✔
430
    // Particle is outside the cylinder, thus both distances are either
431
    // positive or negative. If positive, the smaller distance is the one
×
432
    // with positive sign on sqrt(quad).
433
    const double d = (-k - sqrt(quad)) / a;
434
    if (d < 0.0)
×
435
      return INFTY;
×
436
    return d;
×
437
  }
438
}
1,744,950✔
439

440
// The first template parameter indicates which axis the cylinder is aligned to.
441
// The other two parameters indicate the other two axes.  offset1 and offset2
1,744,950✔
442
// should correspond with i2 and i3, respectively.
1,744,950✔
443
template<int i1, int i2, int i3>
1,744,950✔
444
Direction axis_aligned_cylinder_normal(
445
  Position r, double offset1, double offset2)
446
{
447
  Direction u;
448
  u.get<i2>() = 2.0 * (r.get<i2>() - offset1);
449
  u.get<i3>() = 2.0 * (r.get<i3>() - offset2);
450
  u.get<i1>() = 0.0;
2,654,163,460✔
451
  return u;
452
}
453

2,654,163,460✔
454
//==============================================================================
2,654,163,460✔
455
// SurfaceXCylinder implementation
769,874✔
456
//==============================================================================
457

2,653,393,586✔
458
SurfaceXCylinder::SurfaceXCylinder(pugi::xml_node surf_node)
2,653,393,586✔
459
  : CSGSurface(surf_node)
2,653,393,586✔
460
{
2,653,393,586✔
461
  read_coeffs(surf_node, id_, {&y0_, &z0_, &radius_});
2,653,393,586✔
462
}
463

2,653,393,586✔
464
double SurfaceXCylinder::evaluate(Position r) const
465
{
384,516,791✔
466
  return axis_aligned_cylinder_evaluate<1, 2>(r, y0_, z0_, radius_);
467
}
2,268,876,795✔
468

469
double SurfaceXCylinder::distance(
470
  Position r, Direction u, bool coincident) const
471
{
977,891,043✔
472
  return axis_aligned_cylinder_distance<0, 1, 2>(
491,156,633✔
473
    r, u, coincident, y0_, z0_, radius_);
474
}
486,734,410✔
475

476
Direction SurfaceXCylinder::normal(Position r) const
477
{
1,290,985,752✔
478
  return axis_aligned_cylinder_normal<0, 1, 2>(r, y0_, z0_);
479
}
480

481
void SurfaceXCylinder::to_hdf5_inner(hid_t group_id) const
602,690,529✔
482
{
483
  write_string(group_id, "type", "x-cylinder", false);
484
  array<double, 3> coeffs {{y0_, z0_, radius_}};
485
  write_dataset(group_id, "coefficients", coeffs);
486
}
487

688,295,223✔
488
BoundingBox SurfaceXCylinder::bounding_box(bool pos_side) const
688,295,223✔
489
{
100,763,060✔
490
  if (!pos_side) {
587,532,163✔
491
    return {-INFTY, INFTY, y0_ - radius_, y0_ + radius_, z0_ - radius_,
492
      z0_ + radius_};
493
  } else {
2,652,778,356✔
494
    return {};
495
  }
496
}
2,652,778,356✔
497
//==============================================================================
2,652,778,356✔
498
// SurfaceYCylinder implementation
209,874✔
499
//==============================================================================
500

2,652,568,482✔
501
SurfaceYCylinder::SurfaceYCylinder(pugi::xml_node surf_node)
2,652,568,482✔
502
  : CSGSurface(surf_node)
2,652,568,482✔
503
{
2,652,568,482✔
504
  read_coeffs(surf_node, id_, {&x0_, &z0_, &radius_});
2,652,568,482✔
505
}
506

2,652,568,482✔
507
double SurfaceYCylinder::evaluate(Position r) const
508
{
384,516,791✔
509
  return axis_aligned_cylinder_evaluate<0, 2>(r, x0_, z0_, radius_);
510
}
2,268,051,691✔
511

512
double SurfaceYCylinder::distance(
513
  Position r, Direction u, bool coincident) const
514
{
977,891,043✔
515
  return axis_aligned_cylinder_distance<1, 0, 2>(
491,156,633✔
516
    r, u, coincident, x0_, z0_, radius_);
517
}
486,734,410✔
518

519
Direction SurfaceYCylinder::normal(Position r) const
520
{
1,290,160,648✔
521
  return axis_aligned_cylinder_normal<1, 0, 2>(r, x0_, z0_);
522
}
523

524
void SurfaceYCylinder::to_hdf5_inner(hid_t group_id) const
601,865,425✔
525
{
526
  write_string(group_id, "type", "y-cylinder", false);
527
  array<double, 3> coeffs {{x0_, z0_, radius_}};
528
  write_dataset(group_id, "coefficients", coeffs);
529
}
530

688,295,223✔
531
BoundingBox SurfaceYCylinder::bounding_box(bool pos_side) const
688,295,223✔
532
{
100,763,060✔
533
  if (!pos_side) {
587,532,163✔
534
    return {x0_ - radius_, x0_ + radius_, -INFTY, INFTY, z0_ - radius_,
535
      z0_ + radius_};
536
  } else {
×
537
    return {};
538
  }
539
}
×
540

×
541
//==============================================================================
×
542
// SurfaceZCylinder implementation
543
//==============================================================================
×
544

×
545
SurfaceZCylinder::SurfaceZCylinder(pugi::xml_node surf_node)
×
546
  : CSGSurface(surf_node)
×
547
{
×
548
  read_coeffs(surf_node, id_, {&x0_, &y0_, &radius_});
549
}
×
550

551
double SurfaceZCylinder::evaluate(Position r) const
×
552
{
553
  return axis_aligned_cylinder_evaluate<0, 1>(r, x0_, y0_, radius_);
×
554
}
555

556
double SurfaceZCylinder::distance(
557
  Position r, Direction u, bool coincident) const
×
558
{
×
559
  return axis_aligned_cylinder_distance<2, 0, 1>(
560
    r, u, coincident, x0_, y0_, radius_);
×
561
}
562

563
Direction SurfaceZCylinder::normal(Position r) const
×
564
{
565
  return axis_aligned_cylinder_normal<2, 0, 1>(r, x0_, y0_);
566
}
567

×
568
void SurfaceZCylinder::to_hdf5_inner(hid_t group_id) const
569
{
570
  write_string(group_id, "type", "z-cylinder", false);
571
  array<double, 3> coeffs {{x0_, y0_, radius_}};
572
  write_dataset(group_id, "coefficients", coeffs);
573
}
×
574

×
575
BoundingBox SurfaceZCylinder::bounding_box(bool pos_side) const
×
576
{
×
577
  if (!pos_side) {
578
    return {x0_ - radius_, x0_ + radius_, y0_ - radius_, y0_ + radius_, -INFTY,
579
      INFTY};
1,385,104✔
580
  } else {
581
    return {};
582
  }
1,385,104✔
583
}
1,385,104✔
584

560,000✔
585
//==============================================================================
586
// SurfaceSphere implementation
825,104✔
587
//==============================================================================
825,104✔
588

825,104✔
589
SurfaceSphere::SurfaceSphere(pugi::xml_node surf_node) : CSGSurface(surf_node)
825,104✔
590
{
825,104✔
591
  read_coeffs(surf_node, id_, {&x0_, &y0_, &z0_, &radius_});
592
}
825,104✔
593

594
double SurfaceSphere::evaluate(Position r) const
×
595
{
596
  const double x = r.x - x0_;
825,104✔
597
  const double y = r.y - y0_;
598
  const double z = r.z - z0_;
599
  return x * x + y * y + z * z - radius_ * radius_;
600
}
×
601

×
602
double SurfaceSphere::distance(Position r, Direction u, bool coincident) const
603
{
×
604
  const double x = r.x - x0_;
605
  const double y = r.y - y0_;
606
  const double z = r.z - z0_;
825,104✔
607
  const double k = x * u.x + y * u.y + z * u.z;
608
  const double c = x * x + y * y + z * z - radius_ * radius_;
609
  const double quad = k * k - c;
610

825,104✔
611
  if (quad < 0.0) {
612
    // No intersection with sphere.
613
    return INFTY;
614

615
  } else if (coincident || std::abs(c) < FP_COINCIDENT) {
616
    // Particle is on the sphere, thus one distance is positive/negative and
×
617
    // the other is zero. The sign of k determines if we are facing in or out.
×
618
    if (k >= 0.0) {
×
619
      return INFTY;
×
620
    } else {
621
      return -k + sqrt(quad);
622
    }
623

624
  } else if (c < 0.0) {
625
    // Particle is inside the sphere, thus one distance must be negative and
626
    // one must be positive. The positive distance will be the one with
627
    // negative sign on sqrt(quad)
928,970✔
628
    return -k + sqrt(quad);
629

630
  } else {
928,970✔
631
    // Particle is outside the sphere, thus both distances are either positive
928,970✔
632
    // or negative. If positive, the smaller distance is the one with positive
928,970✔
633
    // sign on sqrt(quad).
928,970✔
634
    const double d = -k - sqrt(quad);
928,970✔
635
    if (d < 0.0)
636
      return INFTY;
928,970✔
637
    return d;
638
  }
639
}
928,970✔
640

928,970✔
641
Direction SurfaceSphere::normal(Position r) const
928,970✔
642
{
928,970✔
643
  return {2.0 * (r.x - x0_), 2.0 * (r.y - y0_), 2.0 * (r.z - z0_)};
928,970✔
644
}
645

×
646
void SurfaceSphere::to_hdf5_inner(hid_t group_id) const
647
{
648
  write_string(group_id, "type", "sphere", false);
×
649
  array<double, 4> coeffs {{x0_, y0_, z0_, radius_}};
×
650
  write_dataset(group_id, "coefficients", coeffs);
×
651
}
×
652

×
653
BoundingBox SurfaceSphere::bounding_box(bool pos_side) const
654
{
×
655
  if (!pos_side) {
656
    return {x0_ - radius_, x0_ + radius_, y0_ - radius_, y0_ + radius_,
657
      z0_ - radius_, z0_ + radius_};
×
658
  } else {
×
659
    return {};
×
660
  }
×
661
}
×
662

663
//==============================================================================
664
// Generic functions for x-, y-, and z-, cones
665
//==============================================================================
666

667
// The first template parameter indicates which axis the cone is aligned to.
668
// The other two parameters indicate the other two axes.  offset1, offset2,
38✔
669
// and offset3 should correspond with i1, i2, and i3, respectively.
38✔
670
template<int i1, int i2, int i3>
671
double axis_aligned_cone_evaluate(
38✔
672
  Position r, double offset1, double offset2, double offset3, double radius_sq)
38✔
673
{
674
  const double r1 = r.get<i1>() - offset1;
1,744,950✔
675
  const double r2 = r.get<i2>() - offset2;
676
  const double r3 = r.get<i3>() - offset3;
1,744,950✔
677
  return r2 * r2 + r3 * r3 - radius_sq * r1 * r1;
678
}
679

1,385,104✔
680
// The first template parameter indicates which axis the cone is aligned to.
681
// The other two parameters indicate the other two axes.  offset1, offset2,
682
// and offset3 should correspond with i1, i2, and i3, respectively.
1,385,104✔
683
template<int i1, int i2, int i3>
1,385,104✔
684
double axis_aligned_cone_distance(Position r, Direction u, bool coincident,
685
  double offset1, double offset2, double offset3, double radius_sq)
686
{
×
687
  const double r1 = r.get<i1>() - offset1;
688
  const double r2 = r.get<i2>() - offset2;
×
689
  const double r3 = r.get<i3>() - offset3;
690
  const double a = u.get<i2>() * u.get<i2>() + u.get<i3>() * u.get<i3>() -
691
                   radius_sq * u.get<i1>() * u.get<i1>();
28✔
692
  const double k =
693
    r2 * u.get<i2>() + r3 * u.get<i3>() - radius_sq * r1 * u.get<i1>();
28✔
694
  const double c = r2 * r2 + r3 * r3 - radius_sq * r1 * r1;
28✔
695
  double quad = k * k - a * c;
28✔
696

28✔
697
  double d;
698

×
699
  if (quad < 0.0) {
700
    // No intersection with cone.
×
701
    return INFTY;
×
702

×
703
  } else if (coincident || std::abs(c) < FP_COINCIDENT) {
704
    // Particle is on the cone, thus one distance is positive/negative
×
705
    // and the other is zero. The sign of k determines if we are facing in or
706
    // out.
707
    if (k >= 0.0) {
708
      d = (-k - sqrt(quad)) / a;
709
    } else {
710
      d = (-k + sqrt(quad)) / a;
711
    }
×
712

×
713
  } else {
714
    // Calculate both solutions to the quadratic.
×
715
    quad = sqrt(quad);
716
    d = (-k - quad) / a;
717
    const double b = (-k + quad) / a;
×
718

719
    // Determine the smallest positive solution.
×
720
    if (d < 0.0) {
721
      if (b > 0.0)
722
        d = b;
×
723
    } else {
724
      if (b > 0.0) {
725
        if (b < d)
×
726
          d = b;
×
727
      }
728
    }
729
  }
×
730

731
  // If the distance was negative, set boundary distance to infinity.
×
732
  if (d <= 0.0)
733
    return INFTY;
734
  return d;
×
735
}
736

×
737
// The first template parameter indicates which axis the cone is aligned to.
×
738
// The other two parameters indicate the other two axes.  offset1, offset2,
×
739
// and offset3 should correspond with i1, i2, and i3, respectively.
740
template<int i1, int i2, int i3>
741
Direction axis_aligned_cone_normal(
×
742
  Position r, double offset1, double offset2, double offset3, double radius_sq)
743
{
×
744
  Direction u;
×
745
  u.get<i1>() = -2.0 * radius_sq * (r.get<i1>() - offset1);
×
746
  u.get<i2>() = 2.0 * (r.get<i2>() - offset2);
747
  u.get<i3>() = 2.0 * (r.get<i3>() - offset3);
×
748
  return u;
749
}
750

751
//==============================================================================
752
// SurfaceXCone implementation
753
//==============================================================================
754

755
SurfaceXCone::SurfaceXCone(pugi::xml_node surf_node) : CSGSurface(surf_node)
4,783✔
756
{
4,783✔
757
  read_coeffs(surf_node, id_, {&x0_, &y0_, &z0_, &radius_sq_});
758
}
4,783✔
759

4,783✔
760
double SurfaceXCone::evaluate(Position r) const
761
{
1,963,634,250✔
762
  return axis_aligned_cone_evaluate<0, 1, 2>(r, x0_, y0_, z0_, radius_sq_);
763
}
1,963,634,250✔
764

765
double SurfaceXCone::distance(Position r, Direction u, bool coincident) const
766
{
2,652,778,356✔
767
  return axis_aligned_cone_distance<0, 1, 2>(
768
    r, u, coincident, x0_, y0_, z0_, radius_sq_);
769
}
2,652,778,356✔
770

2,652,778,356✔
771
Direction SurfaceXCone::normal(Position r) const
772
{
773
  return axis_aligned_cone_normal<0, 1, 2>(r, x0_, y0_, z0_, radius_sq_);
928,970✔
774
}
775

928,970✔
776
void SurfaceXCone::to_hdf5_inner(hid_t group_id) const
777
{
778
  write_string(group_id, "type", "x-cone", false);
3,774✔
779
  array<double, 4> coeffs {{x0_, y0_, z0_, radius_sq_}};
780
  write_dataset(group_id, "coefficients", coeffs);
3,774✔
781
}
3,774✔
782

3,774✔
783
//==============================================================================
3,774✔
784
// SurfaceYCone implementation
785
//==============================================================================
56✔
786

787
SurfaceYCone::SurfaceYCone(pugi::xml_node surf_node) : CSGSurface(surf_node)
56✔
788
{
28✔
789
  read_coeffs(surf_node, id_, {&x0_, &y0_, &z0_, &radius_sq_});
28✔
790
}
791

28✔
792
double SurfaceYCone::evaluate(Position r) const
793
{
794
  return axis_aligned_cone_evaluate<1, 0, 2>(r, y0_, x0_, z0_, radius_sq_);
795
}
796

797
double SurfaceYCone::distance(Position r, Direction u, bool coincident) const
798
{
799
  return axis_aligned_cone_distance<1, 0, 2>(
8,685✔
800
    r, u, coincident, y0_, x0_, z0_, radius_sq_);
801
}
8,685✔
802

8,685✔
803
Direction SurfaceYCone::normal(Position r) const
804
{
594,401,388✔
805
  return axis_aligned_cone_normal<1, 0, 2>(r, y0_, x0_, z0_, radius_sq_);
806
}
594,401,388✔
807

594,401,388✔
808
void SurfaceYCone::to_hdf5_inner(hid_t group_id) const
594,401,388✔
809
{
594,401,388✔
810
  write_string(group_id, "type", "y-cone", false);
811
  array<double, 4> coeffs {{x0_, y0_, z0_, radius_sq_}};
812
  write_dataset(group_id, "coefficients", coeffs);
672,727,430✔
813
}
814

672,727,430✔
815
//==============================================================================
672,727,430✔
816
// SurfaceZCone implementation
672,727,430✔
817
//==============================================================================
672,727,430✔
818

672,727,430✔
819
SurfaceZCone::SurfaceZCone(pugi::xml_node surf_node) : CSGSurface(surf_node)
672,727,430✔
820
{
821
  read_coeffs(surf_node, id_, {&x0_, &y0_, &z0_, &radius_sq_});
672,727,430✔
822
}
823

149,568,917✔
824
double SurfaceZCone::evaluate(Position r) const
825
{
523,158,513✔
826
  return axis_aligned_cone_evaluate<2, 0, 1>(r, z0_, x0_, y0_, radius_sq_);
827
}
828

76,652,899✔
829
double SurfaceZCone::distance(Position r, Direction u, bool coincident) const
34,600,698✔
830
{
831
  return axis_aligned_cone_distance<2, 0, 1>(
42,052,201✔
832
    r, u, coincident, z0_, x0_, y0_, radius_sq_);
833
}
834

446,505,614✔
835
Direction SurfaceZCone::normal(Position r) const
836
{
837
  return axis_aligned_cone_normal<2, 0, 1>(r, z0_, x0_, y0_, radius_sq_);
838
}
398,626,046✔
839

840
void SurfaceZCone::to_hdf5_inner(hid_t group_id) const
841
{
842
  write_string(group_id, "type", "z-cone", false);
843
  array<double, 4> coeffs {{x0_, y0_, z0_, radius_sq_}};
844
  write_dataset(group_id, "coefficients", coeffs);
47,879,568✔
845
}
47,879,568✔
846

12,172,826✔
847
//==============================================================================
35,706,742✔
848
// SurfaceQuadric implementation
849
//==============================================================================
850

851
SurfaceQuadric::SurfaceQuadric(pugi::xml_node surf_node) : CSGSurface(surf_node)
14,528,580✔
852
{
853
  read_coeffs(
14,528,580✔
854
    surf_node, id_, {&A_, &B_, &C_, &D_, &E_, &F_, &G_, &H_, &J_, &K_});
855
}
856

7,076✔
857
double SurfaceQuadric::evaluate(Position r) const
858
{
7,076✔
859
  const double x = r.x;
7,076✔
860
  const double y = r.y;
7,076✔
861
  const double z = r.z;
7,076✔
862
  return x * (A_ * x + D_ * y + G_) + y * (B_ * y + E_ * z + H_) +
863
         z * (C_ * z + F_ * x + J_) + K_;
×
864
}
865

×
866
double SurfaceQuadric::distance(
×
867
  Position r, Direction ang, bool coincident) const
×
868
{
869
  const double& x = r.x;
×
870
  const double& y = r.y;
871
  const double& z = r.z;
872
  const double& u = ang.x;
873
  const double& v = ang.y;
874
  const double& w = ang.z;
875

876
  const double a =
877
    A_ * u * u + B_ * v * v + C_ * w * w + D_ * u * v + E_ * v * w + F_ * u * w;
878
  const double k = A_ * u * x + B_ * v * y + C_ * w * z +
879
                   0.5 * (D_ * (u * y + v * x) + E_ * (v * z + w * y) +
880
                           F_ * (w * x + u * z) + G_ * u + H_ * v + J_ * w);
881
  const double c = A_ * x * x + B_ * y * y + C_ * z * z + D_ * x * y +
410,564✔
882
                   E_ * y * z + F_ * x * z + G_ * x + H_ * y + J_ * z + K_;
883
  double quad = k * k - a * c;
884

410,564✔
885
  double d;
410,564✔
886

410,564✔
887
  if (quad < 0.0) {
410,564✔
888
    // No intersection with surface.
889
    return INFTY;
410,564✔
890

891
  } else if (coincident || std::abs(c) < FP_COINCIDENT) {
892
    // Particle is on the surface, thus one distance is positive/negative and
410,564✔
893
    // the other is zero. The sign of k determines which distance is zero and
410,564✔
894
    // which is not. Additionally, if a is zero, it means the particle is on
410,564✔
895
    // a plane-like surface.
410,564✔
896
    if (a == 0.0) {
897
      d = INFTY; // see the below explanation
×
898
    } else if (k >= 0.0) {
899
      d = (-k - sqrt(quad)) / a;
900
    } else {
×
901
      d = (-k + sqrt(quad)) / a;
×
902
    }
×
903

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

1,226,092✔
919
    // Determine the smallest positive solution.
920
    if (d < 0.0) {
921
      if (b > 0.0)
1,226,092✔
922
        d = b;
1,226,092✔
923
    } else {
1,226,092✔
924
      if (b > 0.0) {
1,226,092✔
925
        if (b < d)
1,226,092✔
926
          d = b;
1,226,092✔
927
      }
1,226,092✔
928
    }
1,226,092✔
929
  }
1,226,092✔
930

931
  // If the distance was negative, set boundary distance to infinity.
932
  if (d <= 0.0)
933
    return INFTY;
1,226,092✔
934
  return d;
935
}
×
936

937
Direction SurfaceQuadric::normal(Position r) const
1,226,092✔
938
{
939
  const double& x = r.x;
940
  const double& y = r.y;
941
  const double& z = r.z;
77,084✔
942
  return {2.0 * A_ * x + D_ * y + F_ * z + G_,
×
943
    2.0 * B_ * y + D_ * x + E_ * z + H_, 2.0 * C_ * z + E_ * y + F_ * x + J_};
944
}
77,084✔
945

946
void SurfaceQuadric::to_hdf5_inner(hid_t group_id) const
947
{
948
  write_string(group_id, "type", "quadric", false);
949
  array<double, 10> coeffs {{A_, B_, C_, D_, E_, F_, G_, H_, J_, K_}};
1,149,008✔
950
  write_dataset(group_id, "coefficients", coeffs);
1,149,008✔
951
}
1,149,008✔
952

953
//==============================================================================
954
// Torus helper functions
1,149,008✔
955
//==============================================================================
975,576✔
956

827,918✔
957
double torus_distance(double x1, double x2, double x3, double u1, double u2,
958
  double u3, double A, double B, double C, bool coincident)
173,432✔
959
{
173,432✔
960
  // Coefficients for equation: (c2 t^2 + c1 t + c0)^2 = c2' t^2 + c1' t + c0'
173,432✔
961
  double D = (C * C) / (B * B);
962
  double c2 = u1 * u1 + u2 * u2 + D * u3 * u3;
963
  double c1 = 2 * (u1 * x1 + u2 * x2 + D * u3 * x3);
964
  double c0 = x1 * x1 + x2 * x2 + D * x3 * x3 + A * A - C * C;
965
  double four_A2 = 4 * A * A;
966
  double c2p = four_A2 * (u1 * u1 + u2 * u2);
1,226,092✔
967
  double c1p = 2 * four_A2 * (u1 * x1 + u2 * x2);
174,958✔
968
  double c0p = four_A2 * (x1 * x1 + x2 * x2);
1,051,134✔
969

970
  // Coefficient for equation: a t^4 + b t^3 + c t^2 + d t + e = 0. If the point
1,226,092✔
971
  // is coincident, the 'e' coefficient should be zero. Explicitly setting it to
972
  // zero helps avoid numerical issues below with root finding.
973
  double coeff[5];
1,226,092✔
974
  coeff[0] = coincident ? 0.0 : c0 * c0 - c0p;
1,226,092✔
975
  coeff[1] = 2 * c0 * c1 - c1p;
1,226,092✔
976
  coeff[2] = c1 * c1 + 2 * c0 * c2 - c2p;
1,226,092✔
977
  coeff[3] = 2 * c1 * c2;
1,226,092✔
978
  coeff[4] = c2 * c2;
1,226,092✔
979

1,226,092✔
980
  std::complex<double> roots[4];
1,226,092✔
981
  oqs::quartic_solver(coeff, roots);
1,226,092✔
982

983
  // Find smallest positive, real root. In the case where the particle is
984
  // coincident with the surface, we are sure to have one root very close to
985
  // zero but possibly small and positive. A tolerance is set to discard that
1,226,092✔
986
  // zero.
987
  double distance = INFTY;
×
988
  double cutoff = coincident ? TORUS_TOL : 0.0;
989
  for (int i = 0; i < 4; ++i) {
1,226,092✔
990
    if (roots[i].imag() == 0) {
991
      double root = roots[i].real();
992
      if (root > cutoff && root < distance) {
993
        // Avoid roots corresponding to internal surfaces
77,084✔
994
        double s1 = x1 + u1 * root;
×
995
        double s2 = x2 + u2 * root;
996
        double s3 = x3 + u3 * root;
77,084✔
997
        double check = D * s3 * s3 + s1 * s1 + s2 * s2 + A * A - C * C;
998
        if (check >= 0) {
999
          distance = root;
1000
        }
1001
      }
1,149,008✔
1002
    }
1,149,008✔
1003
  }
1,149,008✔
1004
  return distance;
1005
}
1006

1,149,008✔
1007
//==============================================================================
975,576✔
1008
// SurfaceXTorus implementation
827,918✔
1009
//==============================================================================
1010

173,432✔
1011
SurfaceXTorus::SurfaceXTorus(pugi::xml_node surf_node) : CSGSurface(surf_node)
173,432✔
1012
{
173,432✔
1013
  read_coeffs(surf_node, id_, {&x0_, &y0_, &z0_, &A_, &B_, &C_});
1014
}
1015

1016
void SurfaceXTorus::to_hdf5_inner(hid_t group_id) const
1017
{
1018
  write_string(group_id, "type", "x-torus", false);
1,226,092✔
1019
  std::array<double, 6> coeffs {{x0_, y0_, z0_, A_, B_, C_}};
174,958✔
1020
  write_dataset(group_id, "coefficients", coeffs);
1,051,134✔
1021
}
1022

×
1023
double SurfaceXTorus::evaluate(Position r) const
1024
{
1025
  double x = r.x - x0_;
×
1026
  double y = r.y - y0_;
×
1027
  double z = r.z - z0_;
×
1028
  return (x * x) / (B_ * B_) +
×
1029
         std::pow(std::sqrt(y * y + z * z) - A_, 2) / (C_ * C_) - 1.;
×
1030
}
×
1031

×
1032
double SurfaceXTorus::distance(Position r, Direction u, bool coincident) const
×
1033
{
×
1034
  double x = r.x - x0_;
1035
  double y = r.y - y0_;
1036
  double z = r.z - z0_;
1037
  return torus_distance(y, z, x, u.y, u.z, u.x, A_, B_, C_, coincident);
×
1038
}
1039

×
1040
Direction SurfaceXTorus::normal(Position r) const
1041
{
×
1042
  // reduce the expansion of the full form for torus
1043
  double x = r.x - x0_;
1044
  double y = r.y - y0_;
1045
  double z = r.z - z0_;
×
1046

×
1047
  // f(x,y,z) = x^2/B^2 + (sqrt(y^2 + z^2) - A)^2/C^2 - 1
1048
  // ∂f/∂x = 2x/B^2
×
1049
  // ∂f/∂y = 2y(g - A)/(g*C^2) where g = sqrt(y^2 + z^2)
1050
  // ∂f/∂z = 2z(g - A)/(g*C^2)
1051
  // Multiplying by g*C^2*B^2 / 2 gives:
1052
  double g = std::sqrt(y * y + z * z);
1053
  double nx = C_ * C_ * g * x;
×
1054
  double ny = y * (g - A_) * B_ * B_;
×
1055
  double nz = z * (g - A_) * B_ * B_;
×
1056
  Direction n(nx, ny, nz);
1057
  return n / n.norm();
1058
}
×
1059

×
1060
//==============================================================================
×
1061
// SurfaceYTorus implementation
1062
//==============================================================================
×
1063

×
1064
SurfaceYTorus::SurfaceYTorus(pugi::xml_node surf_node) : CSGSurface(surf_node)
×
1065
{
1066
  read_coeffs(surf_node, id_, {&x0_, &y0_, &z0_, &A_, &B_, &C_});
1067
}
1068

1069
void SurfaceYTorus::to_hdf5_inner(hid_t group_id) const
1070
{
×
1071
  write_string(group_id, "type", "y-torus", false);
×
1072
  std::array<double, 6> coeffs {{x0_, y0_, z0_, A_, B_, C_}};
×
1073
  write_dataset(group_id, "coefficients", coeffs);
1074
}
×
1075

1076
double SurfaceYTorus::evaluate(Position r) const
1077
{
×
1078
  double x = r.x - x0_;
×
1079
  double y = r.y - y0_;
×
1080
  double z = r.z - z0_;
×
1081
  return (y * y) / (B_ * B_) +
×
1082
         std::pow(std::sqrt(x * x + z * z) - A_, 2) / (C_ * C_) - 1.;
×
1083
}
×
1084

×
1085
double SurfaceYTorus::distance(Position r, Direction u, bool coincident) const
×
1086
{
1087
  double x = r.x - x0_;
1088
  double y = r.y - y0_;
1089
  double z = r.z - z0_;
×
1090
  return torus_distance(x, z, y, u.x, u.z, u.y, A_, B_, C_, coincident);
1091
}
×
1092

1093
Direction SurfaceYTorus::normal(Position r) const
×
1094
{
1095
  // reduce the expansion of the full form for torus
1096
  double x = r.x - x0_;
1097
  double y = r.y - y0_;
×
1098
  double z = r.z - z0_;
×
1099

1100
  // f(x,y,z) = y^2/B^2 + (sqrt(x^2 + z^2) - A)^2/C^2 - 1
×
1101
  // ∂f/∂x = 2x(g - A)/(g*C^2) where g = sqrt(x^2 + z^2)
1102
  // ∂f/∂y = 2y/B^2
1103
  // ∂f/∂z = 2z(g - A)/(g*C^2)
1104
  // Multiplying by g*C^2*B^2 / 2 gives:
1105
  double g = std::sqrt(x * x + z * z);
×
1106
  double nx = x * (g - A_) * B_ * B_;
×
1107
  double ny = C_ * C_ * g * y;
×
1108
  double nz = z * (g - A_) * B_ * B_;
1109
  Direction n(nx, ny, nz);
1110
  return n / n.norm();
×
1111
}
×
1112

×
1113
//==============================================================================
1114
// SurfaceZTorus implementation
×
1115
//==============================================================================
×
1116

×
1117
SurfaceZTorus::SurfaceZTorus(pugi::xml_node surf_node) : CSGSurface(surf_node)
1118
{
1119
  read_coeffs(surf_node, id_, {&x0_, &y0_, &z0_, &A_, &B_, &C_});
1120
}
1121

1122
void SurfaceZTorus::to_hdf5_inner(hid_t group_id) const
×
1123
{
×
1124
  write_string(group_id, "type", "z-torus", false);
×
1125
  std::array<double, 6> coeffs {{x0_, y0_, z0_, A_, B_, C_}};
1126
  write_dataset(group_id, "coefficients", coeffs);
1127
}
1128

1129
double SurfaceZTorus::evaluate(Position r) const
1130
{
1131
  double x = r.x - x0_;
77,084✔
1132
  double y = r.y - y0_;
1133
  double z = r.z - z0_;
1134
  return (z * z) / (B_ * B_) +
77,084✔
1135
         std::pow(std::sqrt(x * x + y * y) - A_, 2) / (C_ * C_) - 1.;
77,084✔
1136
}
77,084✔
1137

77,084✔
1138
double SurfaceZTorus::distance(Position r, Direction u, bool coincident) const
77,084✔
1139
{
1140
  double x = r.x - x0_;
77,084✔
1141
  double y = r.y - y0_;
1142
  double z = r.z - z0_;
1143
  return torus_distance(x, y, z, u.x, u.y, u.z, A_, B_, C_, coincident);
77,084✔
1144
}
77,084✔
1145

77,084✔
1146
Direction SurfaceZTorus::normal(Position r) const
77,084✔
1147
{
77,084✔
1148
  // reduce the expansion of the full form for torus
1149
  double x = r.x - x0_;
×
1150
  double y = r.y - y0_;
1151
  double z = r.z - z0_;
1152

×
1153
  // f(x,y,z) = z^2/B^2 + (sqrt(x^2 + y^2) - A)^2/C^2 - 1
×
1154
  // ∂f/∂x = 2x(g - A)/(g*C^2) where g = sqrt(x^2 + y^2)
×
1155
  // ∂f/∂y = 2y(g - A)/(g*C^2)
×
1156
  // ∂f/∂z = 2z/B^2
×
1157
  // Multiplying by g*C^2*B^2 / 2 gives:
1158
  double g = std::sqrt(x * x + y * y);
×
1159
  double nx = x * (g - A_) * B_ * B_;
1160
  double ny = y * (g - A_) * B_ * B_;
1161
  double nz = C_ * C_ * g * z;
×
1162
  Position n(nx, ny, nz);
×
1163
  return n / n.norm();
×
1164
}
×
1165

×
1166
//==============================================================================
1167

1168
void read_surfaces(pugi::xml_node node)
1169
{
1170
  // Count the number of surfaces
1171
  int n_surfaces = 0;
1172
  for (pugi::xml_node surf_node : node.children("surface")) {
×
1173
    n_surfaces++;
1174
  }
×
1175

1176
  // Loop over XML surface elements and populate the array.  Keep track of
NEW
1177
  // periodic surfaces and their albedos.
×
1178
  model::surfaces.reserve(n_surfaces);
1179
  std::set<std::pair<int, int>> periodic_pairs;
×
1180
  std::unordered_map<int, double> albedo_map;
1181
  {
UNCOV
1182
    pugi::xml_node surf_node;
×
1183
    int i_surf;
UNCOV
1184
    for (surf_node = node.child("surface"), i_surf = 0; surf_node;
×
1185
         surf_node = surf_node.next_sibling("surface"), i_surf++) {
×
1186
      std::string surf_type = get_node_value(surf_node, "type", true, true);
1187

UNCOV
1188
      // Allocate and initialize the new surface
×
1189

UNCOV
1190
      if (surf_type == "x-plane") {
×
1191
        model::surfaces.push_back(make_unique<SurfaceXPlane>(surf_node));
1192

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

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

1199
      } else if (surf_type == "plane") {
1200
        model::surfaces.push_back(make_unique<SurfacePlane>(surf_node));
1201

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

×
1205
      } else if (surf_type == "y-cylinder") {
UNCOV
1206
        model::surfaces.push_back(make_unique<SurfaceYCylinder>(surf_node));
×
1207

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

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

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

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

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

×
1223
      } else if (surf_type == "quadric") {
1224
        model::surfaces.push_back(make_unique<SurfaceQuadric>(surf_node));
UNCOV
1225

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

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

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

1235
      } else {
1236
        fatal_error(fmt::format("Invalid surface type, \"{}\"", surf_type));
19✔
1237
      }
1238

19✔
1239
      // Check for a periodic surface
19✔
1240
      if (check_for_node(surf_node, "boundary")) {
1241
        std::string surf_bc = get_node_value(surf_node, "boundary", true, true);
410,564✔
1242
        if (surf_bc == "periodic") {
1243
          // Check for surface albedo. Skip sanity check as it is already done
410,564✔
1244
          // in the Surface class's constructor.
1245
          if (check_for_node(surf_node, "albedo")) {
1246
            albedo_map[model::surfaces.back()->id_] =
1,226,092✔
1247
              std::stod(get_node_value(surf_node, "albedo"));
1248
          }
1,226,092✔
1249
          if (check_for_node(surf_node, "periodic_surface_id")) {
1,226,092✔
1250
            int i_periodic =
1251
              std::stoi(get_node_value(surf_node, "periodic_surface_id"));
1252
            int lo_id = std::min(model::surfaces.back()->id_, i_periodic);
77,084✔
1253
            int hi_id = std::max(model::surfaces.back()->id_, i_periodic);
1254
            periodic_pairs.insert({lo_id, hi_id});
77,084✔
1255
          } else {
1256
            periodic_pairs.insert({model::surfaces.back()->id_, -1});
1257
          }
14✔
1258
        }
1259
      }
14✔
1260
    }
14✔
1261
  }
14✔
1262

14✔
1263
  // Fill the surface map
1264
  for (int i_surf = 0; i_surf < model::surfaces.size(); i_surf++) {
1265
    int id = model::surfaces[i_surf]->id_;
1266
    auto in_map = model::surface_map.find(id);
1267
    if (in_map == model::surface_map.end()) {
1268
      model::surface_map[id] = i_surf;
19✔
1269
    } else {
UNCOV
1270
      fatal_error(
×
1271
        fmt::format("Two or more surfaces use the same unique ID: {}", id));
19✔
1272
    }
19✔
1273
  }
1274

218,981✔
1275
  // Resolve unpaired periodic surfaces.  A lambda function is used with
1276
  // std::find_if to identify the unpaired surfaces.
218,981✔
1277
  auto is_unresolved_pair = [](const std::pair<int, int> p) {
218,981✔
1278
    return p.second == -1;
218,981✔
1279
  };
218,981✔
1280
  auto first_unresolved = std::find_if(
218,981✔
1281
    periodic_pairs.begin(), periodic_pairs.end(), is_unresolved_pair);
1282
  if (first_unresolved != periodic_pairs.end()) {
1283
    // Found one unpaired surface; search for a second one
397,740✔
1284
    auto next_elem = first_unresolved;
1285
    next_elem++;
1286
    auto second_unresolved =
397,740✔
1287
      std::find_if(next_elem, periodic_pairs.end(), is_unresolved_pair);
397,740✔
1288
    if (second_unresolved == periodic_pairs.end()) {
397,740✔
1289
      fatal_error("Found only one periodic surface without a specified partner."
397,740✔
1290
                  " Please specify the partner for each periodic surface.");
397,740✔
1291
    }
397,740✔
1292

1293
    // Make sure there isn't a third unpaired surface
397,740✔
1294
    next_elem = second_unresolved;
397,740✔
1295
    next_elem++;
397,740✔
1296
    auto third_unresolved =
397,740✔
1297
      std::find_if(next_elem, periodic_pairs.end(), is_unresolved_pair);
397,740✔
1298
    if (third_unresolved != periodic_pairs.end()) {
397,740✔
1299
      fatal_error(
397,740✔
1300
        "Found at least three periodic surfaces without a specified "
397,740✔
1301
        "partner. Please specify the partner for each periodic surface.");
1302
    }
1303

1304
    // Add the completed pair and remove the old, unpaired entries
397,740✔
1305
    int lo_id = std::min(first_unresolved->first, second_unresolved->first);
UNCOV
1306
    int hi_id = std::max(first_unresolved->first, second_unresolved->first);
×
1307
    periodic_pairs.insert({lo_id, hi_id});
1308
    periodic_pairs.erase(first_unresolved);
397,740✔
1309
    periodic_pairs.erase(second_unresolved);
1310
  }
1311

1312
  // Assign the periodic boundary conditions with albedos
1313
  for (auto periodic_pair : periodic_pairs) {
43,960✔
UNCOV
1314
    int i_surf = model::surface_map[periodic_pair.first];
×
1315
    int j_surf = model::surface_map[periodic_pair.second];
43,960✔
UNCOV
1316
    Surface& surf1 {*model::surfaces[i_surf]};
×
1317
    Surface& surf2 {*model::surfaces[j_surf]};
1318

43,960✔
1319
    // Compute the dot product of the surface normals
1320
    Direction norm1 = surf1.normal({0, 0, 0});
1321
    Direction norm2 = surf2.normal({0, 0, 0});
353,780✔
1322
    norm1 /= norm1.norm();
1323
    norm2 /= norm2.norm();
1324
    double dot_prod = norm1.dot(norm2);
1325

1326
    // If the dot product is 1 (to within floating point precision) then the
1327
    // planes are parallel which indicates a translational periodic boundary
1328
    // condition.  Otherwise, it is a rotational periodic BC.
UNCOV
1329
    if (std::abs(1.0 - dot_prod) < FP_PRECISION) {
×
1330
      surf1.bc_ = make_unique<TranslationalPeriodicBC>(i_surf, j_surf);
1331
      surf2.bc_ = make_unique<TranslationalPeriodicBC>(i_surf, j_surf);
1332
    } else {
353,780✔
1333
      surf1.bc_ = make_unique<RotationalPeriodicBC>(i_surf, j_surf);
353,780✔
1334
      surf2.bc_ = make_unique<RotationalPeriodicBC>(i_surf, j_surf);
353,780✔
1335
    }
1336

1337
    // If albedo data is present in albedo map, set the boundary albedo.
353,780✔
1338
    if (albedo_map.count(surf1.id_)) {
353,780✔
1339
      surf1.bc_->set_albedo(albedo_map[surf1.id_]);
353,780✔
1340
    }
NEW
1341
    if (albedo_map.count(surf2.id_)) {
×
NEW
1342
      surf2.bc_->set_albedo(albedo_map[surf2.id_]);
×
1343
    }
×
1344
  }
1345
}
1346

1347
void free_memory_surfaces()
1348
{
1349
  model::surfaces.clear();
397,740✔
UNCOV
1350
  model::surface_map.clear();
×
1351
}
397,740✔
1352

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