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

openmc-dev / openmc / 21621721042

03 Feb 2026 07:51AM UTC coverage: 81.568% (-0.2%) from 81.763%
21621721042

Pull #3683

github

web-flow
Merge acf06fb0f into b41e22f68
Pull Request #3683: Using NJOY2016 to create derived photonuclear data libraries.

16759 of 23252 branches covered (72.08%)

Branch coverage included in aggregate %.

2 of 44 new or added lines in 1 file covered. (4.55%)

308 existing lines in 25 files now uncovered.

55049 of 64783 relevant lines covered (84.97%)

31755492.1 hits per line

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

61.32
/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)
3,782✔
16
{
17
  // Check for type of spatial distribution and read
18
  std::string type;
3,782✔
19
  if (check_for_node(node, "type"))
3,782!
20
    type = get_node_value(node, "type", true, true);
3,782✔
21
  if (type == "cartesian") {
3,782✔
22
    return UPtrSpace {new CartesianIndependent(node)};
8✔
23
  } else if (type == "cylindrical") {
3,774✔
24
    return UPtrSpace {new CylindricalIndependent(node)};
24✔
25
  } else if (type == "spherical") {
3,750✔
26
    return UPtrSpace {new SphericalIndependent(node)};
21✔
27
  } else if (type == "mesh") {
3,729✔
28
    return UPtrSpace {new MeshSpatial(node)};
20✔
29
  } else if (type == "cloud") {
3,709✔
30
    return UPtrSpace {new PointCloud(node)};
6✔
31
  } else if (type == "box") {
3,703✔
32
    return UPtrSpace {new SpatialBox(node)};
2,005✔
33
  } else if (type == "fission") {
1,698✔
34
    return UPtrSpace {new SpatialBox(node, true)};
12✔
35
  } else if (type == "point") {
1,686!
36
    return UPtrSpace {new SpatialPoint(node)};
1,686✔
37
  } else {
38
    fatal_error(fmt::format(
×
39
      "Invalid spatial distribution for external source: {}", type));
40
  }
41
}
3,782✔
42

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

47
CartesianIndependent::CartesianIndependent(pugi::xml_node node)
8✔
48
{
49
  // Read distribution for x coordinate
50
  if (check_for_node(node, "x")) {
8!
51
    pugi::xml_node node_dist = node.child("x");
8✔
52
    x_ = distribution_from_xml(node_dist);
8✔
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")) {
8!
62
    pugi::xml_node node_dist = node.child("y");
8✔
63
    y_ = distribution_from_xml(node_dist);
8✔
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")) {
8!
73
    pugi::xml_node node_dist = node.child("z");
8✔
74
    z_ = distribution_from_xml(node_dist);
8✔
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
}
8✔
82

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

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

96
CylindricalIndependent::CylindricalIndependent(pugi::xml_node node)
24✔
97
{
98
  // Read distribution for r-coordinate
99
  if (check_for_node(node, "r")) {
24!
100
    pugi::xml_node node_dist = node.child("r");
24✔
101
    r_ = distribution_from_xml(node_dist);
24✔
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")) {
24!
111
    pugi::xml_node node_dist = node.child("phi");
24✔
112
    phi_ = distribution_from_xml(node_dist);
24✔
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")) {
24!
122
    pugi::xml_node node_dist = node.child("z");
24✔
123
    z_ = distribution_from_xml(node_dist);
24✔
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")) {
24!
133
    auto origin = get_node_array<double>(node, "origin");
24✔
134
    if (origin.size() == 3) {
24!
135
      origin_ = origin;
24✔
136
    } else {
137
      fatal_error(
×
138
        "Origin for cylindrical source distribution must be length 3");
139
    }
140
  } else {
24✔
141
    // If no coordinates were specified, default to (0, 0, 0)
142
    origin_ = {0.0, 0.0, 0.0};
×
143
  }
144
}
24✔
145

146
std::pair<Position, double> CylindricalIndependent::sample(uint64_t* seed) const
1,932✔
147
{
148
  auto [r, r_wgt] = r_->sample(seed);
1,932✔
149
  auto [phi, phi_wgt] = phi_->sample(seed);
1,932✔
150
  auto [z, z_wgt] = z_->sample(seed);
1,932✔
151
  double x = r * cos(phi) + origin_.x;
1,932✔
152
  double y = r * sin(phi) + origin_.y;
1,932✔
153
  z += origin_.z;
1,932✔
154
  Position xi {x, y, z};
1,932✔
155
  return {xi, r_wgt * phi_wgt * z_wgt};
1,932✔
156
}
157

158
//==============================================================================
159
// SphericalIndependent implementation
160
//==============================================================================
161

162
SphericalIndependent::SphericalIndependent(pugi::xml_node node)
21✔
163
{
164
  // Read distribution for r-coordinate
165
  if (check_for_node(node, "r")) {
21!
166
    pugi::xml_node node_dist = node.child("r");
21✔
167
    r_ = distribution_from_xml(node_dist);
21✔
168
  } else {
169
    // If no distribution was specified, default to a single point at r=0
170
    double x[] {0.0};
×
171
    double p[] {1.0};
×
172
    r_ = make_unique<Discrete>(x, p, 1);
×
173
  }
174

175
  // Read distribution for cos_theta-coordinate
176
  if (check_for_node(node, "cos_theta")) {
21!
177
    pugi::xml_node node_dist = node.child("cos_theta");
21✔
178
    cos_theta_ = distribution_from_xml(node_dist);
21✔
179
  } else {
180
    // If no distribution was specified, default to a single point at
181
    // cos_theta=0
182
    double x[] {0.0};
×
183
    double p[] {1.0};
×
184
    cos_theta_ = make_unique<Discrete>(x, p, 1);
×
185
  }
186

187
  // Read distribution for phi-coordinate
188
  if (check_for_node(node, "phi")) {
21!
189
    pugi::xml_node node_dist = node.child("phi");
21✔
190
    phi_ = distribution_from_xml(node_dist);
21✔
191
  } else {
192
    // If no distribution was specified, default to a single point at phi=0
193
    double x[] {0.0};
×
194
    double p[] {1.0};
×
195
    phi_ = make_unique<Discrete>(x, p, 1);
×
196
  }
197

198
  // Read sphere center coordinates
199
  if (check_for_node(node, "origin")) {
21!
200
    auto origin = get_node_array<double>(node, "origin");
21✔
201
    if (origin.size() == 3) {
21!
202
      origin_ = origin;
21✔
203
    } else {
204
      fatal_error("Origin for spherical source distribution must be length 3");
×
205
    }
206
  } else {
21✔
207
    // If no coordinates were specified, default to (0, 0, 0)
208
    origin_ = {0.0, 0.0, 0.0};
×
209
  }
210
}
21✔
211

212
std::pair<Position, double> SphericalIndependent::sample(uint64_t* seed) const
51,248✔
213
{
214
  auto [r, r_wgt] = r_->sample(seed);
51,248✔
215
  auto [cos_theta, cos_theta_wgt] = cos_theta_->sample(seed);
51,248✔
216
  auto [phi, phi_wgt] = phi_->sample(seed);
51,248✔
217
  // sin(theta) by sin**2 + cos**2 = 1
218
  double x = r * std::sqrt(1 - cos_theta * cos_theta) * cos(phi) + origin_.x;
51,248✔
219
  double y = r * std::sqrt(1 - cos_theta * cos_theta) * sin(phi) + origin_.y;
51,248✔
220
  double z = r * cos_theta + origin_.z;
51,248✔
221
  Position xi {x, y, z};
51,248✔
222
  return {xi, r_wgt * cos_theta_wgt * phi_wgt};
51,248✔
223
}
224

225
//==============================================================================
226
// MeshSpatial implementation
227
//==============================================================================
228

229
MeshSpatial::MeshSpatial(pugi::xml_node node)
20✔
230
{
231

232
  if (get_node_value(node, "type", true, true) != "mesh") {
20!
233
    fatal_error(fmt::format(
×
234
      "Incorrect spatial type '{}' for a MeshSpatial distribution"));
235
  }
236

237
  // No in-tet distributions implemented, could include distributions for the
238
  // barycentric coords Read in unstructured mesh from mesh_id value
239
  int32_t mesh_id = std::stoi(get_node_value(node, "mesh_id"));
20✔
240
  // Get pointer to spatial distribution
241
  mesh_idx_ = model::mesh_map.at(mesh_id);
20✔
242

243
  const auto mesh_ptr = model::meshes.at(mesh_idx_).get();
20✔
244

245
  check_element_types();
20✔
246

247
  size_t n_bins = this->n_sources();
20✔
248
  std::vector<double> strengths(n_bins, 1.0);
20✔
249

250
  // Create cdfs for sampling for an element over a mesh
251
  // Volume scheme is weighted by the volume of each tet
252
  // File scheme is weighted by an array given in the xml file
253
  if (check_for_node(node, "strengths")) {
20✔
254
    strengths = get_node_array<double>(node, "strengths");
19✔
255
    if (strengths.size() != n_bins) {
19!
UNCOV
256
      fatal_error(
×
UNCOV
257
        fmt::format("Number of entries in the source strengths array {} does "
×
258
                    "not match the number of entities in mesh {} ({}).",
UNCOV
259
          strengths.size(), mesh_id, n_bins));
×
260
    }
261
  }
262

263
  if (get_node_value_bool(node, "volume_normalized")) {
20✔
264
    for (int i = 0; i < n_bins; i++) {
12,739✔
265
      strengths[i] *= this->mesh()->volume(i);
12,720✔
266
    }
267
  }
268

269
  elem_idx_dist_.assign(strengths);
20✔
270

271
  if (check_for_node(node, "bias")) {
20!
272
    pugi::xml_node bias_node = node.child("bias");
×
273

274
    if (check_for_node(bias_node, "strengths")) {
×
275
      std::vector<double> bias_strengths(n_bins, 1.0);
×
276
      bias_strengths = get_node_array<double>(node, "strengths");
×
277

278
      if (bias_strengths.size() != n_bins) {
×
279
        fatal_error(
×
280
          fmt::format("Number of entries in the bias strengths array {} does "
×
281
                      "not match the number of entities in mesh {} ({}).",
282
            bias_strengths.size(), mesh_id, n_bins));
×
283
      }
284

285
      if (get_node_value_bool(node, "volume_normalized")) {
×
286
        for (int i = 0; i < n_bins; i++) {
×
287
          bias_strengths[i] *= this->mesh()->volume(i);
×
288
        }
289
      }
290

291
      // Compute importance weights
292
      weight_ = compute_importance_weights(strengths, bias_strengths);
×
293

294
      // Re-initialize DiscreteIndex with bias strengths for sampling
295
      elem_idx_dist_.assign(bias_strengths);
×
296
    } else {
×
297
      fatal_error(fmt::format(
×
298
        "Bias node for mesh {} found without strengths array.", mesh_id));
299
    }
300
  }
301
}
20✔
302

303
MeshSpatial::MeshSpatial(int32_t mesh_idx, span<const double> strengths)
109✔
304
  : mesh_idx_(mesh_idx)
109✔
305
{
306
  check_element_types();
109✔
307
  elem_idx_dist_.assign(strengths);
109✔
308
}
109✔
309

310
void MeshSpatial::check_element_types() const
129✔
311
{
312
  const auto umesh_ptr = dynamic_cast<const UnstructuredMesh*>(this->mesh());
129!
313
  if (umesh_ptr) {
129✔
314
    // ensure that the unstructured mesh contains only linear tets
315
    for (int bin = 0; bin < umesh_ptr->n_bins(); bin++) {
36,003✔
316
      if (umesh_ptr->element_type(bin) != ElementType::LINEAR_TET) {
36,000!
317
        fatal_error(
×
318
          "Mesh specified for source must contain only linear tetrahedra.");
319
      }
320
    }
321
  }
322
}
129✔
323

324
int32_t MeshSpatial::sample_element_index(uint64_t* seed) const
1,014,181✔
325
{
326
  return elem_idx_dist_.sample(seed);
1,014,181✔
327
}
328

329
std::pair<int32_t, Position> MeshSpatial::sample_mesh(uint64_t* seed) const
200,580✔
330
{
331
  // Sample the CDF defined in initialization above
332
  int32_t elem_idx = this->sample_element_index(seed);
200,580✔
333
  return {elem_idx, mesh()->sample_element(elem_idx, seed)};
200,580✔
334
}
335

336
std::pair<Position, double> MeshSpatial::sample(uint64_t* seed) const
200,580✔
337
{
338
  auto [elem_idx, u] = this->sample_mesh(seed);
200,580✔
339
  double wgt = weight_.empty() ? 1.0 : weight_[elem_idx];
200,580!
340
  return {u, wgt};
401,160✔
341
}
342

343
//==============================================================================
344
// PointCloud implementation
345
//==============================================================================
346

347
PointCloud::PointCloud(pugi::xml_node node)
6✔
348
{
349
  if (check_for_node(node, "coords")) {
6!
350
    point_cloud_ = get_node_position_array(node, "coords");
6✔
351
  } else {
352
    fatal_error("No coordinates were provided for the PointCloud "
×
353
                "spatial distribution");
354
  }
355

356
  std::vector<double> strengths;
6✔
357

358
  if (check_for_node(node, "strengths"))
6!
359
    strengths = get_node_array<double>(node, "strengths");
6✔
360
  else
361
    strengths.resize(point_cloud_.size(), 1.0);
×
362

363
  if (strengths.size() != point_cloud_.size()) {
6!
364
    fatal_error(
×
365
      fmt::format("Number of entries for the strengths array {} does "
×
366
                  "not match the number of spatial points provided {}.",
367
        strengths.size(), point_cloud_.size()));
×
368
  }
369

370
  point_idx_dist_.assign(strengths);
6✔
371

372
  if (check_for_node(node, "bias")) {
6!
373
    pugi::xml_node bias_node = node.child("bias");
×
374

375
    if (check_for_node(bias_node, "strengths")) {
×
376
      std::vector<double> bias_strengths(point_cloud_.size(), 1.0);
×
377
      bias_strengths = get_node_array<double>(node, "strengths");
×
378

379
      if (bias_strengths.size() != point_cloud_.size()) {
×
380
        fatal_error(
×
381
          fmt::format("Number of entries in the bias strengths array {} does "
×
382
                      "not match the number of spatial points provided {}.",
383
            bias_strengths.size(), point_cloud_.size()));
×
384
      }
385

386
      // Compute importance weights
387
      weight_ = compute_importance_weights(strengths, bias_strengths);
×
388

389
      // Re-initialize DiscreteIndex with bias strengths for sampling
390
      point_idx_dist_.assign(bias_strengths);
×
391
    } else {
×
392
      fatal_error(
×
393
        fmt::format("Bias node for PointCloud found without strengths array."));
×
394
    }
395
  }
396
}
6✔
397

398
PointCloud::PointCloud(
×
399
  std::vector<Position> point_cloud, span<const double> strengths)
×
400
{
401
  point_cloud_.assign(point_cloud.begin(), point_cloud.end());
×
402
  point_idx_dist_.assign(strengths);
×
403
}
×
404

405
std::pair<Position, double> PointCloud::sample(uint64_t* seed) const
300,000✔
406
{
407
  int32_t index = point_idx_dist_.sample(seed);
300,000✔
408
  double wgt = weight_.empty() ? 1.0 : weight_[index];
300,000!
409
  return {point_cloud_[index], wgt};
600,000✔
410
}
411

412
//==============================================================================
413
// SpatialBox implementation
414
//==============================================================================
415

416
SpatialBox::SpatialBox(pugi::xml_node node, bool fission)
2,017✔
417
  : only_fissionable_ {fission}
2,017✔
418
{
419
  // Read lower-right/upper-left coordinates
420
  auto params = get_node_array<double>(node, "parameters");
2,017✔
421
  if (params.size() != 6)
2,017!
422
    openmc::fatal_error("Box/fission spatial source must have six "
×
423
                        "parameters specified.");
424

425
  lower_left_ = Position {params[0], params[1], params[2]};
2,017✔
426
  upper_right_ = Position {params[3], params[4], params[5]};
2,017✔
427
}
2,017✔
428

429
SpatialBox::SpatialBox(Position lower_left, Position upper_right, bool fission)
6✔
430
  : lower_left_(lower_left), upper_right_(upper_right),
6✔
431
    only_fissionable_(fission)
6✔
432
{}
6✔
433

434
std::pair<Position, double> SpatialBox::sample(uint64_t* seed) const
3,553,718✔
435
{
436
  Position xi {prn(seed), prn(seed), prn(seed)};
3,553,718✔
437
  return {lower_left_ + xi * (upper_right_ - lower_left_), 1.0};
3,553,718✔
438
}
439

440
//==============================================================================
441
// SpatialPoint implementation
442
//==============================================================================
443

444
SpatialPoint::SpatialPoint(pugi::xml_node node)
1,686✔
445
{
446
  // Read location of point source
447
  auto params = get_node_array<double>(node, "parameters");
1,686✔
448
  if (params.size() != 3)
1,686!
449
    openmc::fatal_error("Point spatial source must have three "
×
450
                        "parameters specified.");
451

452
  // Set position
453
  r_ = Position {params.data()};
1,686✔
454
}
1,686✔
455

456
std::pair<Position, double> SpatialPoint::sample(uint64_t* seed) const
14,452,417✔
457
{
458
  return {r_, 1.0};
14,452,417✔
459
}
460

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