forked from openmc-dev/openmc
-
Notifications
You must be signed in to change notification settings - Fork 3
Expand file tree
/
Copy pathtest_source_mesh.py
More file actions
443 lines (341 loc) · 15.7 KB
/
test_source_mesh.py
File metadata and controls
443 lines (341 loc) · 15.7 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
from itertools import product
from pathlib import Path
import pytest
import numpy as np
import openmc
import openmc.lib
from tests import cdtemp
###################
# MeshSpatial Tests
###################
TETS_PER_VOXEL = 12
# This test uses a geometry file with cells that match a regular mesh. Each cell
# in the geometry corresponds to 12 tetrahedra in the unstructured mesh file.
@pytest.fixture
def model():
openmc.reset_auto_ids()
### Materials ###
materials = openmc.Materials()
water_mat = openmc.Material(name="water")
water_mat.add_nuclide("H1", 2.0)
water_mat.add_nuclide("O16", 1.0)
water_mat.set_density("atom/b-cm", 0.07416)
materials.append(water_mat)
### Geometry ###
# This test uses a geometry file that resembles a regular mesh.
# 12 tets are used to match each voxel in the geometry.
# create a regular mesh that matches the superimposed mesh
regular_mesh = openmc.RegularMesh(mesh_id=10)
regular_mesh.lower_left = (-10, -10, -10)
regular_mesh.dimension = (10, 10, 10)
regular_mesh.width = (2, 2, 2)
root_cell, _ = regular_mesh.build_cells(bc=['vacuum']*6)
geometry = openmc.Geometry(root=[root_cell])
### Settings ###
settings = openmc.Settings()
settings.run_mode = 'fixed source'
settings.particles = 100
settings.batches = 2
return openmc.Model(geometry=geometry,
materials=materials,
settings=settings)
### Setup test cases ###
param_values = (['libmesh', 'moab'], # mesh libraries
['uniform', 'manual']) # Element weighting schemes
test_cases = []
for i, (lib, schemes) in enumerate(product(*param_values)):
test_cases.append({'library' : lib,
'source_strengths' : schemes})
def ids(params):
"""Test naming function for clarity"""
return f"{params['library']}-{params['source_strengths']}"
@pytest.mark.parametrize("test_cases", test_cases, ids=ids)
def test_unstructured_mesh_sampling(model, request, test_cases):
# skip the test if the library is not enabled
if test_cases['library'] == 'moab' and not openmc.lib._dagmc_enabled():
pytest.skip("DAGMC (and MOAB) mesh not enabled in this build.")
if test_cases['library'] == 'libmesh' and not openmc.lib._libmesh_enabled():
pytest.skip("LibMesh is not enabled in this build.")
# setup mesh source ###
mesh_filename = Path(request.fspath).parent / "test_mesh_tets.e"
uscd_mesh = openmc.UnstructuredMesh(mesh_filename, test_cases['library'])
# subtract one to account for root cell produced by RegularMesh.build_cells
n_cells = len(model.geometry.get_all_cells()) - 1
# set source weights according to test case
if test_cases['source_strengths'] == 'uniform':
vol_norm = True
strengths = None
elif test_cases['source_strengths'] == 'manual':
vol_norm = False
# assign random weights
strengths = np.random.rand(n_cells*TETS_PER_VOXEL)
# create the spatial distribution based on the mesh
space = openmc.stats.MeshSpatial(uscd_mesh, strengths, vol_norm)
energy = openmc.stats.Discrete(x=[15.e+06], p=[1.0])
source = openmc.IndependentSource(space=space, energy=energy)
model.settings.source = source
with cdtemp([mesh_filename]):
model.export_to_xml()
n_measurements = 100
n_samples = 1000
cell_counts = np.zeros((n_cells, n_measurements))
# This model contains 1000 geometry cells. Each cell is a hex
# corresponding to 12 of the tets. This test runs 1000 samples. This
# results in the following average for each cell
openmc.lib.init([])
# perform many sets of samples and track counts for each cell
for m in range(n_measurements):
sites = openmc.lib.sample_external_source(n_samples)
cells = [openmc.lib.find_cell(s.r) for s in sites]
for c in cells:
# subtract one from index to account for root cell
cell_counts[c[0]._index - 1, m] += 1
# make sure particle transport is successful
openmc.lib.run()
openmc.lib.finalize()
# normalize cell counts to get sampling frequency per particle
cell_counts /= n_samples
# get the mean and std. dev. of the cell counts
mean = cell_counts.mean(axis=1)
std_dev = cell_counts.std(axis=1)
if test_cases['source_strengths'] == 'uniform':
exp_vals = np.ones(n_cells) / n_cells
else:
# sum up the source strengths for each tet, these are the expected true mean
# of the sampling frequency for that cell
exp_vals = strengths.reshape(-1, 12).sum(axis=1) / sum(strengths)
diff = np.abs(mean - exp_vals)
assert((diff < 2*std_dev).sum() / diff.size >= 0.95)
assert((diff < 6*std_dev).sum() / diff.size >= 0.997)
def test_strengths_size_failure(request, model):
# setup mesh source ###
mesh_filename = Path(request.fspath).parent / "test_mesh_tets.e"
uscd_mesh = openmc.UnstructuredMesh(mesh_filename, 'libmesh')
# intentionally incorrectly sized to trigger an error
n_cells = len(model.geometry.get_all_cells())
strengths = np.random.rand(n_cells*TETS_PER_VOXEL)
# create the spatial distribution based on the mesh
space = openmc.stats.MeshSpatial(uscd_mesh, strengths)
energy = openmc.stats.Discrete(x=[15.e+06], p=[1.0])
source = openmc.IndependentSource(space=space, energy=energy)
model.settings.source = source
# skip the test if unstructured mesh is not available
if not openmc.lib._libmesh_enabled():
if openmc.lib._dagmc_enabled():
source.space.mesh.library = 'moab'
else:
pytest.skip("Unstructured mesh support unavailable.")
# make sure that an incorrrectly sized strengths array causes a failure
source.space.strengths = source.space.strengths[:-1]
mesh_filename = Path(request.fspath).parent / source.space.mesh.filename
with pytest.raises(RuntimeError, match=r'strengths array'), cdtemp([mesh_filename]):
model.export_to_xml()
openmc.run()
def test_roundtrip(run_in_tmpdir, model, request):
if not openmc.lib._libmesh_enabled() and not openmc.lib._dagmc_enabled():
pytest.skip("Unstructured mesh is not enabled in this build.")
mesh_filename = Path(request.fspath).parent / 'test_mesh_tets.e'
ucd_mesh = openmc.UnstructuredMesh(mesh_filename, library='libmesh')
if not openmc.lib._libmesh_enabled():
ucd_mesh.library = 'moab'
n_cells = len(model.geometry.get_all_cells())
space_out = openmc.MeshSpatial(ucd_mesh)
space_out.strengths = np.random.rand(n_cells*TETS_PER_VOXEL)
model.settings.source = openmc.IndependentSource(space=space_out)
# write out the model
model.export_to_xml()
model_in = openmc.Model.from_xml()
space_in = model_in.settings.source[0].space
np.testing.assert_equal(space_out.strengths, space_in.strengths)
assert space_in.mesh.id == space_out.mesh.id
assert space_in.volume_normalized == space_out.volume_normalized
###################
# MeshSource tests
###################
@pytest.fixture
def void_model():
"""
A void model containing a single box
"""
model = openmc.Model()
box = openmc.model.RectangularParallelepiped(*[-10, 10]*3, boundary_type='vacuum')
model.geometry = openmc.Geometry([openmc.Cell(region=-box)])
model.settings.particles = 100
model.settings.batches = 10
model.settings.run_mode = 'fixed source'
return model
@pytest.mark.parametrize('mesh_type', ('rectangular', 'cylindrical'))
def test_mesh_source_independent(run_in_tmpdir, void_model, mesh_type):
"""
A void model containing a single box
"""
model = void_model
# define a 2 x 2 x 2 mesh
if mesh_type == 'rectangular':
mesh = openmc.RegularMesh.from_domain(model.geometry, (2, 2, 2))
elif mesh_type == 'cylindrical':
mesh = openmc.CylindricalMesh.from_domain(model.geometry, (1, 4, 2))
energy = openmc.stats.Discrete([1.e6], [1.0])
# create sources with only one non-zero strength for the source in the mesh
# voxel occupying the lowest octant. Direct source particles straight out of
# the problem from there. This demonstrates that
# 1) particles are only being sourced within the intented mesh voxel based
# on source strength
# 2) particles are respecting the angle distributions assigned to each voxel
sources = np.empty(mesh.dimension, dtype=openmc.SourceBase)
centroids = mesh.centroids
x, y, z = np.swapaxes(mesh.centroids, -1, 0)
for i, j, k in mesh.indices:
# mesh.indices is currently one-indexed, adjust for Python arrays
ijk = (i-1, j-1, k-1)
# get the centroid of the ijk mesh element and use it to set the
# direction of the source directly out of the problem
centroid = centroids[ijk]
vec = np.sign(centroid, dtype=float)
vec /= np.linalg.norm(vec)
angle = openmc.stats.Monodirectional(vec)
sources[ijk] = openmc.IndependentSource(energy=energy, angle=angle, strength=0.0)
# create and apply the mesh source
mesh_source = openmc.MeshSource(mesh, sources)
model.settings.source = mesh_source
# tally the flux on the mesh
mesh_filter = openmc.MeshFilter(mesh)
tally = openmc.Tally()
tally.filters = [mesh_filter]
tally.scores = ['flux']
model.tallies = openmc.Tallies([tally])
# for each element, set a single-non zero source with particles
# traveling out of the mesh (and geometry) w/o crossing any other
# mesh elements
for flat_index, (i, j, k) in enumerate(mesh.indices):
ijk = (i-1, j-1, k-1)
# zero-out all source strengths and set the strength
# on the element of interest
mesh_source.strength = 0.0
mesh_source.sources[flat_index].strength = 1.0
sp_file = model.run()
with openmc.StatePoint(sp_file) as sp:
tally_out = sp.get_tally(id=tally.id)
mean = tally_out.get_reshaped_data(expand_dims=True)
# remove nuclides and scores axes
mean = mean[..., 0, 0]
# the mesh elment with a non-zero source strength should have a value
assert mean[ijk] != 0
# all other values should be zero
mean[ijk] = 0
assert np.all(mean == 0), f'Failed on index {ijk} with centroid {mesh.centroids[ijk]}'
# test roundtrip
xml_model = openmc.Model.from_model_xml()
xml_source = xml_model.settings.source[0]
assert isinstance(xml_source, openmc.MeshSource)
assert xml_source.strength == 1.0
assert isinstance(xml_source.mesh, type(mesh_source.mesh))
assert xml_source.mesh.dimension == mesh_source.mesh.dimension
assert xml_source.mesh.id == mesh_source.mesh.id
assert len(xml_source.sources) == len(mesh_source.sources)
# check strength adjustment methods
assert mesh_source.strength == 1.0
mesh_source.strength = 100.0
assert mesh_source.strength == 100.0
mesh_source.normalize_source_strengths()
assert mesh_source.strength == 1.0
@pytest.mark.parametrize("library", ('moab', 'libmesh'))
def test_umesh_source_independent(run_in_tmpdir, request, void_model, library):
import openmc.lib
# skip the test if the library is not enabled
if library == 'moab' and not openmc.lib._dagmc_enabled():
pytest.skip("DAGMC (and MOAB) mesh not enabled in this build.")
if library == 'libmesh' and not openmc.lib._libmesh_enabled():
pytest.skip("LibMesh is not enabled in this build.")
model = void_model
mesh_filename = Path(request.fspath).parent / "test_mesh_tets.e"
uscd_mesh = openmc.UnstructuredMesh(mesh_filename, library)
ind_source = openmc.IndependentSource()
n_elements = 12_000
model.settings.source = openmc.MeshSource(uscd_mesh, n_elements*[ind_source])
model.export_to_model_xml()
try:
openmc.lib.init()
openmc.lib.simulation_init()
sites = openmc.lib.sample_external_source(10)
openmc.lib.statepoint_write('statepoint.h5')
finally:
openmc.lib.finalize()
with openmc.StatePoint('statepoint.h5') as sp:
uscd_mesh = sp.meshes[uscd_mesh.id]
# ensure at least that all sites are inside the mesh
bounding_box = uscd_mesh.bounding_box
for site in sites:
assert site.r in bounding_box
def test_mesh_source_file(run_in_tmpdir):
# Creating a source file with a single particle
source_particle = openmc.SourceParticle(time=10.0)
openmc.write_source_file([source_particle], 'source.h5')
file_source = openmc.FileSource('source.h5')
model = openmc.Model()
rect_prism = openmc.model.RectangularParallelepiped(
-5.0, 5.0, -5.0, 5.0, -5.0, 5.0, boundary_type='vacuum')
mat = openmc.Material()
mat.add_nuclide('H1', 1.0)
model.geometry = openmc.Geometry([openmc.Cell(fill=mat, region=-rect_prism)])
model.settings.particles = 1000
model.settings.batches = 10
model.settings.run_mode = 'fixed source'
mesh = openmc.RegularMesh()
mesh.lower_left = (-1, -2, -3)
mesh.upper_right = (2, 3, 4)
mesh.dimension = (1, 1, 1)
model.settings.source = openmc.MeshSource(mesh, [file_source])
model.export_to_model_xml()
openmc.lib.init()
openmc.lib.simulation_init()
sites = openmc.lib.sample_external_source(10)
openmc.lib.simulation_finalize()
openmc.lib.finalize()
# The mesh bounds do not contain the point of the lone source site in the
# file source, so it should not appear in the set of source sites produced
# from the mesh source. Additionally, the source should be located within
# the mesh
bbox = mesh.bounding_box
for site in sites:
assert site.r != (0, 0, 0)
assert site.E == source_particle.E
assert site.u == source_particle.u
assert site.time == source_particle.time
assert site.r in bbox
@pytest.mark.parametrize("mesh_type", ('rectangular', 'cylindrical', 'spherical'))
def test_mesh_spatial(run_in_tmpdir, mesh_type):
"""Test that a spherical mesh source works as expected."""
model = openmc.Model()
# Set up geometry, a box that is shifted in x, y, and z
box = openmc.model.RectangularParallelepiped(5.0, 25.0, -20.0, 20.0, -30.0, 30.0, boundary_type='vacuum')
mat = openmc.Material()
mat.add_nuclide('H1', 1.0)
model.geometry = openmc.Geometry([openmc.Cell(fill=mat, region=-box)])
# Create a mesh of each type in turn
if mesh_type == 'rectangular':
mesh = openmc.RegularMesh.from_domain(model.geometry, (10, 2, 2))
elif mesh_type == 'cylindrical':
mesh = openmc.CylindricalMesh.from_domain(model.geometry, (10, 2, 2))
assert max(mesh.r_grid) == 10.0, "Cylindrical mesh radius exceeds geometry bounds"
assert mesh.origin[0] == 15.0, "Cylindrical mesh origin x-coordinate is incorrect"
elif mesh_type == 'spherical':
mesh = openmc.SphericalMesh.from_domain(model.geometry, (10, 2, 2))
assert max(mesh.r_grid) == 10.0, "Spherical mesh radius exceeds geometry bounds"
assert mesh.origin[0] == 15.0, "Spherical mesh origin x-coordinate is incorrect"
# Create a mesh source with a single particle
ind_source = openmc.IndependentSource(space=openmc.stats.MeshSpatial(mesh, np.prod(mesh.dimension)*[1.0]))
model.settings.source = ind_source
model.settings.particles = 100
model.settings.batches = 10
model.settings.run_mode = 'fixed source'
model.export_to_model_xml()
openmc.lib.init()
openmc.lib.simulation_init()
sites = openmc.lib.sample_external_source(10)
openmc.lib.simulation_finalize()
openmc.lib.finalize()
# Check that the sites are within the spherical mesh bounds
bbox = mesh.bounding_box
for site in sites:
assert site.r in bbox