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

openmc-dev / openmc / 7071649772

02 Dec 2023 05:35PM UTC coverage: 84.541% (+0.02%) from 84.524%
7071649772

push

github

web-flow
Mesh Source Class (#2759)

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

220 of 231 new or added lines in 12 files covered. (95.24%)

53 existing lines in 2 files now uncovered.

47205 of 55837 relevant lines covered (84.54%)

34203579.09 hits per line

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

78.44
/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)
5,929✔
16
{
17
  // Check for type of spatial distribution and read
18
  std::string type;
5,929✔
19
  if (check_for_node(node, "type"))
5,929✔
20
    type = get_node_value(node, "type", true, true);
5,929✔
21
  if (type == "cartesian") {
5,929✔
22
    return UPtrSpace {new CartesianIndependent(node)};
19✔
23
  } else if (type == "cylindrical") {
5,910✔
24
    return UPtrSpace {new CylindricalIndependent(node)};
57✔
25
  } else if (type == "spherical") {
5,853✔
26
    return UPtrSpace {new SphericalIndependent(node)};
69✔
27
  } else if (type == "mesh") {
5,784✔
28
    return UPtrSpace {new MeshSpatial(node)};
7✔
29
  } else if (type == "box") {
5,777✔
30
    return UPtrSpace {new SpatialBox(node)};
1,932✔
31
  } else if (type == "fission") {
3,845✔
32
    return UPtrSpace {new SpatialBox(node, true)};
891✔
33
  } else if (type == "point") {
2,954✔
34
    return UPtrSpace {new SpatialPoint(node)};
2,954✔
35
  } else {
NEW
36
    fatal_error(fmt::format(
×
37
      "Invalid spatial distribution for external source: {}", type));
38
  }
39
}
5,928✔
40

41
//==============================================================================
42
// CartesianIndependent implementation
43
//==============================================================================
44

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

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

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

81
Position CartesianIndependent::sample(uint64_t* seed) const
4,130✔
82
{
83
  return {x_->sample(seed), y_->sample(seed), z_->sample(seed)};
4,130✔
84
}
85

86
//==============================================================================
87
// CylindricalIndependent implementation
88
//==============================================================================
89

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

103
  // Read distribution for phi-coordinate
104
  if (check_for_node(node, "phi")) {
57✔
105
    pugi::xml_node node_dist = node.child("phi");
57✔
106
    phi_ = distribution_from_xml(node_dist);
57✔
107
  } else {
108
    // If no distribution was specified, default to a single point at phi=0
109
    double x[] {0.0};
×
110
    double p[] {1.0};
×
111
    phi_ = make_unique<Discrete>(x, p, 1);
×
112
  }
113

114
  // Read distribution for z-coordinate
115
  if (check_for_node(node, "z")) {
57✔
116
    pugi::xml_node node_dist = node.child("z");
57✔
117
    z_ = distribution_from_xml(node_dist);
57✔
118
  } else {
119
    // If no distribution was specified, default to a single point at z=0
120
    double x[] {0.0};
×
121
    double p[] {1.0};
×
122
    z_ = make_unique<Discrete>(x, p, 1);
×
123
  }
124

125
  // Read cylinder center coordinates
126
  if (check_for_node(node, "origin")) {
57✔
127
    auto origin = get_node_array<double>(node, "origin");
57✔
128
    if (origin.size() == 3) {
57✔
129
      origin_ = origin;
57✔
130
    } else {
131
      fatal_error(
×
132
        "Origin for cylindrical source distribution must be length 3");
133
    }
134
  } else {
57✔
135
    // If no coordinates were specified, default to (0, 0, 0)
136
    origin_ = {0.0, 0.0, 0.0};
×
137
  }
138
}
57✔
139

140
Position CylindricalIndependent::sample(uint64_t* seed) const
4,312✔
141
{
142
  double r = r_->sample(seed);
4,312✔
143
  double phi = phi_->sample(seed);
4,312✔
144
  double x = r * cos(phi) + origin_.x;
4,312✔
145
  double y = r * sin(phi) + origin_.y;
4,312✔
146
  double z = z_->sample(seed) + origin_.z;
4,312✔
147
  return {x, y, z};
4,312✔
148
}
149

150
//==============================================================================
151
// SphericalIndependent implementation
152
//==============================================================================
153

154
SphericalIndependent::SphericalIndependent(pugi::xml_node node)
69✔
155
{
156
  // Read distribution for r-coordinate
157
  if (check_for_node(node, "r")) {
69✔
158
    pugi::xml_node node_dist = node.child("r");
69✔
159
    r_ = distribution_from_xml(node_dist);
69✔
160
  } else {
161
    // If no distribution was specified, default to a single point at r=0
162
    double x[] {0.0};
×
163
    double p[] {1.0};
×
164
    r_ = make_unique<Discrete>(x, p, 1);
×
165
  }
166

167
  // Read distribution for cos_theta-coordinate
168
  if (check_for_node(node, "cos_theta")) {
69✔
169
    pugi::xml_node node_dist = node.child("cos_theta");
69✔
170
    cos_theta_ = distribution_from_xml(node_dist);
69✔
171
  } else {
172
    // If no distribution was specified, default to a single point at
173
    // cos_theta=0
174
    double x[] {0.0};
×
175
    double p[] {1.0};
×
176
    cos_theta_ = make_unique<Discrete>(x, p, 1);
×
177
  }
178

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

190
  // Read sphere center coordinates
191
  if (check_for_node(node, "origin")) {
69✔
192
    auto origin = get_node_array<double>(node, "origin");
69✔
193
    if (origin.size() == 3) {
69✔
194
      origin_ = origin;
69✔
195
    } else {
196
      fatal_error("Origin for spherical source distribution must be length 3");
×
197
    }
198
  } else {
69✔
199
    // If no coordinates were specified, default to (0, 0, 0)
200
    origin_ = {0.0, 0.0, 0.0};
×
201
  }
202
}
69✔
203

204
Position SphericalIndependent::sample(uint64_t* seed) const
182,744✔
205
{
206
  double r = r_->sample(seed);
182,744✔
207
  double cos_theta = cos_theta_->sample(seed);
182,744✔
208
  double phi = phi_->sample(seed);
182,744✔
209
  // sin(theta) by sin**2 + cos**2 = 1
210
  double x = r * std::sqrt(1 - cos_theta * cos_theta) * cos(phi) + origin_.x;
182,744✔
211
  double y = r * std::sqrt(1 - cos_theta * cos_theta) * sin(phi) + origin_.y;
182,744✔
212
  double z = r * cos_theta + origin_.z;
182,744✔
213
  return {x, y, z};
182,744✔
214
}
215

216
//==============================================================================
217
// MeshSpatial implementation
218
//==============================================================================
219

220
MeshSpatial::MeshSpatial(pugi::xml_node node)
7✔
221
{
222

223
  if (get_node_value(node, "type", true, true) != "mesh") {
7✔
NEW
224
    fatal_error(fmt::format(
×
225
      "Incorrect spatial type '{}' for a MeshSpatial distribution"));
226
  }
227

228
  // No in-tet distributions implemented, could include distributions for the
229
  // barycentric coords Read in unstructured mesh from mesh_id value
230
  int32_t mesh_id = std::stoi(get_node_value(node, "mesh_id"));
7✔
231
  // Get pointer to spatial distribution
232
  mesh_idx_ = model::mesh_map.at(mesh_id);
7✔
233

234
  const auto mesh_ptr = model::meshes.at(mesh_idx_).get();
7✔
235

236
  check_element_types();
7✔
237

238
  size_t n_bins = this->n_sources();
7✔
239
  std::vector<double> strengths(n_bins, 1.0);
7✔
240

241
  // Create cdfs for sampling for an element over a mesh
242
  // Volume scheme is weighted by the volume of each tet
243
  // File scheme is weighted by an array given in the xml file
244
  if (check_for_node(node, "strengths")) {
7✔
245
    strengths = get_node_array<double>(node, "strengths");
4✔
246
    if (strengths.size() != n_bins) {
4✔
247
      fatal_error(
1✔
248
        fmt::format("Number of entries in the source strengths array {} does "
1✔
249
                    "not match the number of entities in mesh {} ({}).",
250
          strengths.size(), mesh_id, n_bins));
1✔
251
    }
252
  }
253

254
  if (get_node_value_bool(node, "volume_normalized")) {
6✔
255
    for (int i = 0; i < n_bins; i++) {
36,003✔
256
      strengths[i] *= this->mesh()->volume(i);
36,000✔
257
    }
258
  }
259

260
  elem_idx_dist_.assign(strengths);
6✔
261
}
6✔
262

263
MeshSpatial::MeshSpatial(int32_t mesh_idx, gsl::span<const double> strengths)
238✔
264
  : mesh_idx_(mesh_idx)
238✔
265
{
266
  check_element_types();
238✔
267
  elem_idx_dist_.assign(strengths);
238✔
268
}
238✔
269

270
void MeshSpatial::check_element_types() const
245✔
271
{
272
  const auto umesh_ptr = dynamic_cast<const UnstructuredMesh*>(this->mesh());
245✔
273
  if (umesh_ptr) {
245✔
274
    // ensure that the unstructured mesh contains only linear tets
275
    for (int bin = 0; bin < umesh_ptr->n_bins(); bin++) {
84,007✔
276
      if (umesh_ptr->element_type(bin) != ElementType::LINEAR_TET) {
84,000✔
NEW
277
        fatal_error(
×
278
          "Mesh specified for source must contain only linear tetrahedra.");
279
      }
280
    }
281
  }
282
}
245✔
283

284
std::pair<int32_t, Position> MeshSpatial::sample_mesh(uint64_t* seed) const
825,340✔
285
{
286
  // Sample the CDF defined in initialization above
287
  int32_t elem_idx = elem_idx_dist_.sample(seed);
825,340✔
288
  return {elem_idx, mesh()->sample_element(elem_idx, seed)};
825,340✔
289
}
290

291
Position MeshSpatial::sample(uint64_t* seed) const
601,200✔
292
{
293
  return this->sample_mesh(seed).second;
601,200✔
294
}
295

296
//==============================================================================
297
// SpatialBox implementation
298
//==============================================================================
299

300
SpatialBox::SpatialBox(pugi::xml_node node, bool fission)
2,823✔
301
  : only_fissionable_ {fission}
2,823✔
302
{
303
  // Read lower-right/upper-left coordinates
304
  auto params = get_node_array<double>(node, "parameters");
2,823✔
305
  if (params.size() != 6)
2,823✔
306
    openmc::fatal_error("Box/fission spatial source must have six "
×
307
                        "parameters specified.");
308

309
  lower_left_ = Position {params[0], params[1], params[2]};
2,823✔
310
  upper_right_ = Position {params[3], params[4], params[5]};
2,823✔
311
}
2,823✔
312

313
Position SpatialBox::sample(uint64_t* seed) const
4,401,714✔
314
{
315
  Position xi {prn(seed), prn(seed), prn(seed)};
4,401,714✔
316
  return lower_left_ + xi * (upper_right_ - lower_left_);
8,803,428✔
317
}
318

319
//==============================================================================
320
// SpatialPoint implementation
321
//==============================================================================
322

323
SpatialPoint::SpatialPoint(pugi::xml_node node)
2,954✔
324
{
325
  // Read location of point source
326
  auto params = get_node_array<double>(node, "parameters");
2,954✔
327
  if (params.size() != 3)
2,954✔
328
    openmc::fatal_error("Point spatial source must have three "
×
329
                        "parameters specified.");
330

331
  // Set position
332
  r_ = Position {params.data()};
2,954✔
333
}
2,954✔
334

335
Position SpatialPoint::sample(uint64_t* seed) const
13,786,765✔
336
{
337
  return r_;
13,786,765✔
338
}
339

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