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

openmc-dev / openmc / 28871986781

07 Jul 2026 01:59PM UTC coverage: 81.29% (+0.007%) from 81.283%
28871986781

Pull #4000

github

web-flow
Merge b118958a7 into 0c6b3fb83
Pull Request #4000: This fixes compile isuees found with GCC 16.1.1 and FMT version

18190 of 26400 branches covered (68.9%)

Branch coverage included in aggregate %.

4 of 7 new or added lines in 3 files covered. (57.14%)

59396 of 69043 relevant lines covered (86.03%)

48624698.7 hits per line

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

56.91
/src/distribution_spatial.cpp
1
#include "openmc/distribution_spatial.h"
2

3
#include "openmc/error.h"
4
#include "openmc/mesh.h"
5
#include "openmc/random_lcg.h"
6
#include "openmc/search.h"
7
#include "openmc/xml_interface.h"
8

9
namespace openmc {
10

11
//==============================================================================
12
// SpatialDistribution implementation
13
//==============================================================================
14

15
unique_ptr<SpatialDistribution> SpatialDistribution::create(pugi::xml_node node)
7,901✔
16
{
17
  // Check for type of spatial distribution and read
18
  std::string type;
7,901✔
19
  if (check_for_node(node, "type"))
7,901!
20
    type = get_node_value(node, "type", true, true);
7,901✔
21
  if (type == "cartesian") {
7,901✔
22
    return UPtrSpace {new CartesianIndependent(node)};
45✔
23
  } else if (type == "cylindrical") {
7,856✔
24
    return UPtrSpace {new CylindricalIndependent(node)};
45✔
25
  } else if (type == "spherical") {
7,811✔
26
    return UPtrSpace {new SphericalIndependent(node)};
61✔
27
  } else if (type == "mesh") {
7,750✔
28
    return UPtrSpace {new MeshSpatial(node)};
40✔
29
  } else if (type == "cloud") {
7,710✔
30
    return UPtrSpace {new PointCloud(node)};
11✔
31
  } else if (type == "box") {
7,699✔
32
    return UPtrSpace {new SpatialBox(node)};
4,271✔
33
  } else if (type == "fission") {
3,428✔
34
    return UPtrSpace {new SpatialBox(node, true)};
22✔
35
  } else if (type == "point") {
3,406!
36
    return UPtrSpace {new SpatialPoint(node)};
3,406✔
37
  } else {
38
    fatal_error(fmt::format(
×
39
      "Invalid spatial distribution for external source: {}", type));
40
  }
41
}
7,900✔
42

43
//==============================================================================
44
// CartesianIndependent implementation
45
//==============================================================================
46

47
CartesianIndependent::CartesianIndependent(pugi::xml_node node)
45✔
48
{
49
  // Read distribution for x coordinate
50
  if (check_for_node(node, "x")) {
45!
51
    pugi::xml_node node_dist = node.child("x");
45✔
52
    x_ = distribution_from_xml(node_dist);
45✔
53
  } else {
54
    // If no distribution was specified, default to a single point at x=0
55
    double x[] {0.0};
×
56
    double p[] {1.0};
×
57
    x_ = UPtrDist {new Discrete {x, p, 1}};
×
58
  }
59

60
  // Read distribution for y coordinate
61
  if (check_for_node(node, "y")) {
45!
62
    pugi::xml_node node_dist = node.child("y");
45✔
63
    y_ = distribution_from_xml(node_dist);
45✔
64
  } else {
65
    // If no distribution was specified, default to a single point at y=0
66
    double x[] {0.0};
×
67
    double p[] {1.0};
×
68
    y_ = UPtrDist {new Discrete {x, p, 1}};
×
69
  }
70

71
  // Read distribution for z coordinate
72
  if (check_for_node(node, "z")) {
45!
73
    pugi::xml_node node_dist = node.child("z");
45✔
74
    z_ = distribution_from_xml(node_dist);
45✔
75
  } else {
76
    // If no distribution was specified, default to a single point at z=0
77
    double x[] {0.0};
×
78
    double p[] {1.0};
×
79
    z_ = UPtrDist {new Discrete {x, p, 1}};
×
80
  }
81
}
45✔
82

83
std::pair<Position, double> CartesianIndependent::sample(uint64_t* seed) const
8,690✔
84
{
85
  auto [x_val, x_wgt] = x_->sample(seed);
8,690✔
86
  auto [y_val, y_wgt] = y_->sample(seed);
8,690✔
87
  auto [z_val, z_wgt] = z_->sample(seed);
8,690✔
88
  Position xi {x_val, y_val, z_val};
8,690✔
89
  return {xi, x_wgt * y_wgt * z_wgt};
8,690✔
90
}
91

92
//==============================================================================
93
// CylindricalIndependent implementation
94
//==============================================================================
95

96
CylindricalIndependent::CylindricalIndependent(pugi::xml_node node)
45✔
97
{
98
  // Read distribution for r-coordinate
99
  if (check_for_node(node, "r")) {
45!
100
    pugi::xml_node node_dist = node.child("r");
45✔
101
    r_ = distribution_from_xml(node_dist);
45✔
102
  } else {
103
    // If no distribution was specified, default to a single point at r=0
104
    double x[] {0.0};
×
105
    double p[] {1.0};
×
106
    r_ = make_unique<Discrete>(x, p, 1);
×
107
  }
108

109
  // Read distribution for phi-coordinate
110
  if (check_for_node(node, "phi")) {
45!
111
    pugi::xml_node node_dist = node.child("phi");
45✔
112
    phi_ = distribution_from_xml(node_dist);
45✔
113
  } else {
114
    // If no distribution was specified, default to a single point at phi=0
115
    double x[] {0.0};
×
116
    double p[] {1.0};
×
117
    phi_ = make_unique<Discrete>(x, p, 1);
×
118
  }
119

120
  // Read distribution for z-coordinate
121
  if (check_for_node(node, "z")) {
45!
122
    pugi::xml_node node_dist = node.child("z");
45✔
123
    z_ = distribution_from_xml(node_dist);
45✔
124
  } else {
125
    // If no distribution was specified, default to a single point at z=0
126
    double x[] {0.0};
×
127
    double p[] {1.0};
×
128
    z_ = make_unique<Discrete>(x, p, 1);
×
129
  }
130

131
  // Read cylinder center coordinates
132
  if (check_for_node(node, "origin")) {
45!
133
    auto origin = get_node_array<double>(node, "origin");
45✔
134
    if (origin.size() == 3) {
45!
135
      origin_ = origin;
45✔
136
    } else {
137
      fatal_error(
×
138
        "Origin for cylindrical source distribution must be length 3");
139
    }
140
  } else {
45✔
141
    // If no coordinates were specified, default to (0, 0, 0)
142
    origin_ = {0.0, 0.0, 0.0};
×
143
  }
144

145
  // Read cylinder z_dir
146
  if (check_for_node(node, "z_dir")) {
45!
147
    auto z_dir = get_node_array<double>(node, "z_dir");
×
148
    if (z_dir.size() == 3) {
×
149
      z_dir_ = z_dir;
×
150
      z_dir_ /= z_dir_.norm();
×
151
    } else {
152
      fatal_error("z_dir for cylindrical source distribution must be length 3");
×
153
    }
154
  } else {
×
155
    // If no z_dir was specified, default to (0, 0, 1)
156
    z_dir_ = {0.0, 0.0, 1.0};
45✔
157
  }
158

159
  // Read cylinder r_dir
160
  if (check_for_node(node, "r_dir")) {
45!
161
    auto r_dir = get_node_array<double>(node, "r_dir");
×
162
    if (r_dir.size() == 3) {
×
163
      r_dir_ = r_dir;
×
164
      r_dir_ /= r_dir_.norm();
×
165
    } else {
166
      fatal_error("r_dir for cylindrical source distribution must be length 3");
×
167
    }
168
  } else {
×
169
    // If no r_dir was specified, default to (1, 0, 0)
170
    r_dir_ = {1.0, 0.0, 0.0};
45✔
171
  }
172

173
  if (r_dir_.dot(z_dir_) > 1e-12)
45!
174
    fatal_error("r_dir must be perpendicular to z_dir");
×
175

176
  auto phi_dir = z_dir_.cross(r_dir_);
45✔
177
  phi_dir /= phi_dir.norm();
45✔
178
  phi_dir_ = phi_dir;
45✔
179
}
45✔
180

181
std::pair<Position, double> CylindricalIndependent::sample(uint64_t* seed) const
3,542✔
182
{
183
  auto [r, r_wgt] = r_->sample(seed);
3,542✔
184
  auto [phi, phi_wgt] = phi_->sample(seed);
3,542✔
185
  auto [z, z_wgt] = z_->sample(seed);
3,542✔
186
  Position xi =
3,542✔
187
    r * (cos(phi) * r_dir_ + sin(phi) * phi_dir_) + z * z_dir_ + origin_;
3,542✔
188
  return {xi, r_wgt * phi_wgt * z_wgt};
3,542✔
189
}
190

191
//==============================================================================
192
// SphericalIndependent implementation
193
//==============================================================================
194

195
SphericalIndependent::SphericalIndependent(pugi::xml_node node)
61✔
196
{
197
  // Read distribution for r-coordinate
198
  if (check_for_node(node, "r")) {
61!
199
    pugi::xml_node node_dist = node.child("r");
61✔
200
    r_ = distribution_from_xml(node_dist);
61✔
201
  } else {
202
    // If no distribution was specified, default to a single point at r=0
203
    double x[] {0.0};
×
204
    double p[] {1.0};
×
205
    r_ = make_unique<Discrete>(x, p, 1);
×
206
  }
207

208
  // Read distribution for cos_theta-coordinate
209
  if (check_for_node(node, "cos_theta")) {
61!
210
    pugi::xml_node node_dist = node.child("cos_theta");
61✔
211
    cos_theta_ = distribution_from_xml(node_dist);
61✔
212
  } else {
213
    // If no distribution was specified, default to a single point at
214
    // cos_theta=0
215
    double x[] {0.0};
×
216
    double p[] {1.0};
×
217
    cos_theta_ = make_unique<Discrete>(x, p, 1);
×
218
  }
219

220
  // Read distribution for phi-coordinate
221
  if (check_for_node(node, "phi")) {
61!
222
    pugi::xml_node node_dist = node.child("phi");
61✔
223
    phi_ = distribution_from_xml(node_dist);
61✔
224
  } else {
225
    // If no distribution was specified, default to a single point at phi=0
226
    double x[] {0.0};
×
227
    double p[] {1.0};
×
228
    phi_ = make_unique<Discrete>(x, p, 1);
×
229
  }
230

231
  // Read sphere center coordinates
232
  if (check_for_node(node, "origin")) {
61!
233
    auto origin = get_node_array<double>(node, "origin");
61✔
234
    if (origin.size() == 3) {
61!
235
      origin_ = origin;
61✔
236
    } else {
237
      fatal_error("Origin for spherical source distribution must be length 3");
×
238
    }
239
  } else {
61✔
240
    // If no coordinates were specified, default to (0, 0, 0)
241
    origin_ = {0.0, 0.0, 0.0};
×
242
  }
243
}
61✔
244

245
std::pair<Position, double> SphericalIndependent::sample(uint64_t* seed) const
182,288✔
246
{
247
  auto [r, r_wgt] = r_->sample(seed);
182,288✔
248
  auto [cos_theta, cos_theta_wgt] = cos_theta_->sample(seed);
182,288✔
249
  auto [phi, phi_wgt] = phi_->sample(seed);
182,288✔
250
  // sin(theta) by sin**2 + cos**2 = 1
251
  double x = r * std::sqrt(1 - cos_theta * cos_theta) * cos(phi) + origin_.x;
182,288✔
252
  double y = r * std::sqrt(1 - cos_theta * cos_theta) * sin(phi) + origin_.y;
182,288✔
253
  double z = r * cos_theta + origin_.z;
182,288✔
254
  Position xi {x, y, z};
182,288✔
255
  return {xi, r_wgt * cos_theta_wgt * phi_wgt};
182,288✔
256
}
257

258
//==============================================================================
259
// MeshSpatial implementation
260
//==============================================================================
261

262
MeshSpatial::MeshSpatial(pugi::xml_node node)
40✔
263
{
264

265
  auto spatial_type = get_node_value(node, "type", true, true);
40✔
266
  if (spatial_type != "mesh") {
40!
NEW
267
    fatal_error(
×
NEW
268
      fmt::format("Incorrect spatial type '{}' for a MeshSpatial distribution",
×
269
        spatial_type));
270
  }
271

272
  // No in-tet distributions implemented, could include distributions for the
273
  // barycentric coords Read in unstructured mesh from mesh_id value
274
  int32_t mesh_id = std::stoi(get_node_value(node, "mesh_id"));
80✔
275
  // Get pointer to spatial distribution
276
  mesh_idx_ = model::mesh_map.at(mesh_id);
40✔
277

278
  const auto mesh_ptr = model::meshes.at(mesh_idx_).get();
40✔
279

280
  check_element_types();
40✔
281

282
  size_t n_bins = this->n_sources();
40✔
283
  std::vector<double> strengths(n_bins, 1.0);
40✔
284

285
  // Create cdfs for sampling for an element over a mesh
286
  // Volume scheme is weighted by the volume of each tet
287
  // File scheme is weighted by an array given in the xml file
288
  if (check_for_node(node, "strengths")) {
40✔
289
    strengths = get_node_array<double>(node, "strengths");
74✔
290
    if (strengths.size() != n_bins) {
37✔
291
      fatal_error(
1!
292
        fmt::format("Number of entries in the source strengths array {} does "
1!
293
                    "not match the number of entities in mesh {} ({}).",
294
          strengths.size(), mesh_id, n_bins));
1!
295
    }
296
  }
297

298
  if (get_node_value_bool(node, "volume_normalized")) {
39✔
299
    for (int i = 0; i < n_bins; i++) {
37,356✔
300
      strengths[i] *= this->mesh()->volume(i);
37,320✔
301
    }
302
  }
303

304
  elem_idx_dist_.assign(strengths);
39✔
305

306
  if (check_for_node(node, "bias")) {
39!
307
    pugi::xml_node bias_node = node.child("bias");
×
308

309
    if (check_for_node(bias_node, "strengths")) {
×
310
      std::vector<double> bias_strengths(n_bins, 1.0);
×
311
      bias_strengths = get_node_array<double>(node, "strengths");
×
312

313
      if (bias_strengths.size() != n_bins) {
×
314
        fatal_error(
×
315
          fmt::format("Number of entries in the bias strengths array {} does "
×
316
                      "not match the number of entities in mesh {} ({}).",
317
            bias_strengths.size(), mesh_id, n_bins));
×
318
      }
319

320
      if (get_node_value_bool(node, "volume_normalized")) {
×
321
        for (int i = 0; i < n_bins; i++) {
×
322
          bias_strengths[i] *= this->mesh()->volume(i);
×
323
        }
324
      }
325

326
      // Compute importance weights
327
      weight_ = compute_importance_weights(strengths, bias_strengths);
×
328

329
      // Re-initialize DiscreteIndex with bias strengths for sampling
330
      elem_idx_dist_.assign(bias_strengths);
×
331
    } else {
×
332
      fatal_error(fmt::format(
×
333
        "Bias node for mesh {} found without strengths array.", mesh_id));
334
    }
335
  }
336
}
39✔
337

338
MeshSpatial::MeshSpatial(int32_t mesh_idx, span<const double> strengths)
201✔
339
  : mesh_idx_(mesh_idx)
201✔
340
{
341
  check_element_types();
201✔
342
  elem_idx_dist_.assign(strengths);
201✔
343
}
201✔
344

345
void MeshSpatial::check_element_types() const
241✔
346
{
347
  const auto umesh_ptr = dynamic_cast<const UnstructuredMesh*>(this->mesh());
241!
348
  if (umesh_ptr) {
241✔
349
    // ensure that the unstructured mesh contains only linear tets
350
    for (int bin = 0; bin < umesh_ptr->n_bins(); bin++) {
120,010✔
351
      if (umesh_ptr->element_type(bin) != ElementType::LINEAR_TET) {
120,000!
352
        fatal_error(
×
353
          "Mesh specified for source must contain only linear tetrahedra.");
354
      }
355
    }
356
  }
357
}
241✔
358

359
int32_t MeshSpatial::sample_element_index(uint64_t* seed) const
2,107,842✔
360
{
361
  return elem_idx_dist_.sample(seed);
2,107,842✔
362
}
363

364
std::pair<int32_t, Position> MeshSpatial::sample_mesh(uint64_t* seed) const
601,530✔
365
{
366
  // Sample the CDF defined in initialization above
367
  int32_t elem_idx = this->sample_element_index(seed);
601,530✔
368
  return {elem_idx, mesh()->sample_element(elem_idx, seed)};
601,530✔
369
}
370

371
std::pair<Position, double> MeshSpatial::sample(uint64_t* seed) const
601,530✔
372
{
373
  auto [elem_idx, u] = this->sample_mesh(seed);
601,530✔
374
  double wgt = weight_.empty() ? 1.0 : weight_[elem_idx];
601,530!
375
  return {u, wgt};
601,530✔
376
}
377

378
//==============================================================================
379
// PointCloud implementation
380
//==============================================================================
381

382
PointCloud::PointCloud(pugi::xml_node node)
11✔
383
{
384
  if (check_for_node(node, "coords")) {
11!
385
    point_cloud_ = get_node_position_array(node, "coords");
11✔
386
  } else {
387
    fatal_error("No coordinates were provided for the PointCloud "
×
388
                "spatial distribution");
389
  }
390

391
  std::vector<double> strengths;
11✔
392

393
  if (check_for_node(node, "strengths"))
11!
394
    strengths = get_node_array<double>(node, "strengths");
22✔
395
  else
396
    strengths.resize(point_cloud_.size(), 1.0);
×
397

398
  if (strengths.size() != point_cloud_.size()) {
11!
399
    fatal_error(
×
400
      fmt::format("Number of entries for the strengths array {} does "
×
401
                  "not match the number of spatial points provided {}.",
402
        strengths.size(), point_cloud_.size()));
×
403
  }
404

405
  point_idx_dist_.assign(strengths);
11✔
406

407
  if (check_for_node(node, "bias")) {
11!
408
    pugi::xml_node bias_node = node.child("bias");
×
409

410
    if (check_for_node(bias_node, "strengths")) {
×
411
      std::vector<double> bias_strengths(point_cloud_.size(), 1.0);
×
412
      bias_strengths = get_node_array<double>(node, "strengths");
×
413

414
      if (bias_strengths.size() != point_cloud_.size()) {
×
415
        fatal_error(
×
416
          fmt::format("Number of entries in the bias strengths array {} does "
×
417
                      "not match the number of spatial points provided {}.",
418
            bias_strengths.size(), point_cloud_.size()));
×
419
      }
420

421
      // Compute importance weights
422
      weight_ = compute_importance_weights(strengths, bias_strengths);
×
423

424
      // Re-initialize DiscreteIndex with bias strengths for sampling
425
      point_idx_dist_.assign(bias_strengths);
×
426
    } else {
×
427
      fatal_error(
×
428
        fmt::format("Bias node for PointCloud found without strengths array."));
×
429
    }
430
  }
431
}
11✔
432

433
PointCloud::PointCloud(
×
434
  std::vector<Position> point_cloud, span<const double> strengths)
×
435
{
436
  point_cloud_.assign(point_cloud.begin(), point_cloud.end());
×
437
  point_idx_dist_.assign(strengths);
×
438
}
×
439

440
std::pair<Position, double> PointCloud::sample(uint64_t* seed) const
550,000✔
441
{
442
  int32_t index = point_idx_dist_.sample(seed);
550,000✔
443
  double wgt = weight_.empty() ? 1.0 : weight_[index];
550,000!
444
  return {point_cloud_[index], wgt};
550,000✔
445
}
446

447
//==============================================================================
448
// SpatialBox implementation
449
//==============================================================================
450

451
SpatialBox::SpatialBox(pugi::xml_node node, bool fission)
4,293✔
452
  : only_fissionable_ {fission}
4,293✔
453
{
454
  // Read lower-right/upper-left coordinates
455
  auto params = get_node_array<double>(node, "parameters");
4,293✔
456
  if (params.size() != 6)
4,293!
457
    openmc::fatal_error("Box/fission spatial source must have six "
×
458
                        "parameters specified.");
459

460
  lower_left_ = Position {params[0], params[1], params[2]};
4,293✔
461
  upper_right_ = Position {params[3], params[4], params[5]};
4,293✔
462
}
4,293✔
463

464
SpatialBox::SpatialBox(Position lower_left, Position upper_right, bool fission)
11✔
465
  : lower_left_(lower_left), upper_right_(upper_right),
11✔
466
    only_fissionable_(fission)
11✔
467
{}
11✔
468

469
std::pair<Position, double> SpatialBox::sample(uint64_t* seed) const
7,674,178✔
470
{
471
  Position xi {prn(seed), prn(seed), prn(seed)};
7,674,178✔
472
  return {lower_left_ + xi * (upper_right_ - lower_left_), 1.0};
7,674,178✔
473
}
474

475
//==============================================================================
476
// SpatialPoint implementation
477
//==============================================================================
478

479
SpatialPoint::SpatialPoint(pugi::xml_node node)
3,406✔
480
{
481
  // Read location of point source
482
  auto params = get_node_array<double>(node, "parameters");
3,406✔
483
  if (params.size() != 3)
3,406!
484
    openmc::fatal_error("Point spatial source must have three "
×
485
                        "parameters specified.");
486

487
  // Set position
488
  r_ = Position {params.data()};
3,406✔
489
}
3,406✔
490

491
std::pair<Position, double> SpatialPoint::sample(uint64_t* seed) const
27,842,764✔
492
{
493
  return {r_, 1.0};
27,842,764✔
494
}
495

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