2024-11-13 15:39:41 -06:00
|
|
|
from collections import Counter
|
2022-08-31 12:37:06 -05:00
|
|
|
from math import pi
|
2026-05-08 20:53:12 -05:00
|
|
|
from pathlib import Path
|
2022-08-31 12:37:06 -05:00
|
|
|
|
2018-01-29 17:41:10 -06:00
|
|
|
import openmc
|
2022-09-26 14:20:34 -05:00
|
|
|
import openmc.lib
|
2018-01-29 17:41:10 -06:00
|
|
|
import openmc.stats
|
2022-08-31 12:37:06 -05:00
|
|
|
import numpy as np
|
2023-06-20 21:27:55 -05:00
|
|
|
import pytest
|
2022-08-31 12:37:06 -05:00
|
|
|
from pytest import approx
|
|
|
|
|
|
2024-05-15 22:49:04 +02:00
|
|
|
from tests.regression_tests import config
|
|
|
|
|
|
2018-01-29 17:41:10 -06:00
|
|
|
|
|
|
|
|
def test_source():
|
|
|
|
|
space = openmc.stats.Point()
|
|
|
|
|
energy = openmc.stats.Discrete([1.0e6], [1.0])
|
|
|
|
|
angle = openmc.stats.Isotropic()
|
|
|
|
|
|
2023-06-20 21:27:55 -05:00
|
|
|
src = openmc.IndependentSource(space=space, angle=angle, energy=energy)
|
2018-01-29 17:41:10 -06:00
|
|
|
assert src.space == space
|
|
|
|
|
assert src.angle == angle
|
|
|
|
|
assert src.energy == energy
|
|
|
|
|
|
|
|
|
|
elem = src.to_xml_element()
|
|
|
|
|
assert 'strength' in elem.attrib
|
|
|
|
|
assert elem.find('space') is not None
|
|
|
|
|
assert elem.find('angle') is not None
|
|
|
|
|
assert elem.find('energy') is not None
|
|
|
|
|
|
2023-06-20 21:27:55 -05:00
|
|
|
src = openmc.IndependentSource.from_xml_element(elem)
|
2019-05-22 19:26:15 -06:00
|
|
|
assert isinstance(src.angle, openmc.stats.Isotropic)
|
|
|
|
|
assert src.space.xyz == [0.0, 0.0, 0.0]
|
|
|
|
|
assert src.energy.x == [1.0e6]
|
|
|
|
|
assert src.energy.p == [1.0]
|
|
|
|
|
assert src.strength == 1.0
|
|
|
|
|
|
2018-01-29 17:41:10 -06:00
|
|
|
|
2024-11-13 15:39:41 -06:00
|
|
|
def test_point_cloud():
|
|
|
|
|
positions = [(1, 0, 2), (0, 1, 0), (0, 0, 3), (4, 9, 2)]
|
|
|
|
|
strengths = [1, 2, 3, 4]
|
|
|
|
|
|
|
|
|
|
space = openmc.stats.PointCloud(positions, strengths)
|
|
|
|
|
np.testing.assert_equal(space.positions, positions)
|
|
|
|
|
np.testing.assert_equal(space.strengths, strengths)
|
|
|
|
|
|
|
|
|
|
src = openmc.IndependentSource(space=space)
|
|
|
|
|
assert src.space == space
|
|
|
|
|
np.testing.assert_equal(src.space.positions, positions)
|
|
|
|
|
np.testing.assert_equal(src.space.strengths, strengths)
|
|
|
|
|
|
|
|
|
|
elem = src.to_xml_element()
|
|
|
|
|
src = openmc.IndependentSource.from_xml_element(elem)
|
|
|
|
|
np.testing.assert_equal(src.space.positions, positions)
|
|
|
|
|
np.testing.assert_equal(src.space.strengths, strengths)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_point_cloud_invalid():
|
|
|
|
|
with pytest.raises(ValueError, match='2D'):
|
|
|
|
|
openmc.stats.PointCloud([1, 0, 2, 0, 1, 0])
|
|
|
|
|
|
|
|
|
|
with pytest.raises(ValueError, match='3 values'):
|
|
|
|
|
openmc.stats.PointCloud([(1, 0, 2, 3), (4, 5, 2, 3)])
|
|
|
|
|
|
|
|
|
|
with pytest.raises(ValueError, match='1D'):
|
|
|
|
|
openmc.stats.PointCloud([(1, 0, 2), (4, 5, 2)], [(1, 2), (3, 4)])
|
|
|
|
|
|
|
|
|
|
with pytest.raises(ValueError, match='same length'):
|
|
|
|
|
openmc.stats.PointCloud([(1, 0, 2), (4, 5, 2)], [1, 2, 4])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_point_cloud_strengths(run_in_tmpdir, sphere_box_model):
|
|
|
|
|
positions = [(1., 0., 2.), (0., 1., 0.), (0., 0., 3.), (-1., -1., 2.)]
|
|
|
|
|
strengths = [1, 2, 3, 4]
|
|
|
|
|
space = openmc.stats.PointCloud(positions, strengths)
|
|
|
|
|
|
|
|
|
|
model = sphere_box_model[0]
|
|
|
|
|
model.settings.run_mode = 'fixed source'
|
|
|
|
|
model.settings.source = openmc.IndependentSource(space=space)
|
|
|
|
|
|
|
|
|
|
try:
|
|
|
|
|
model.init_lib()
|
|
|
|
|
n_samples = 50_000
|
|
|
|
|
sites = openmc.lib.sample_external_source(n_samples)
|
|
|
|
|
finally:
|
|
|
|
|
model.finalize_lib()
|
|
|
|
|
|
|
|
|
|
count = Counter(s.r for s in sites)
|
|
|
|
|
for i, (strength, position) in enumerate(zip(strengths, positions)):
|
|
|
|
|
sampled_strength = count[position] / n_samples
|
|
|
|
|
expected_strength = pytest.approx(strength/sum(strengths), abs=0.02)
|
|
|
|
|
assert sampled_strength == expected_strength, f'Strength incorrect for {positions[i]}'
|
|
|
|
|
|
2022-05-25 17:30:13 +02:00
|
|
|
|
2026-05-08 20:53:12 -05:00
|
|
|
def test_decay_spectrum_parent_nuclide(run_in_tmpdir):
|
|
|
|
|
chain_file = Path('chain_decay_spectrum_parent.xml')
|
|
|
|
|
chain_file.write_text("""<?xml version="1.0"?>
|
|
|
|
|
<depletion_chain>
|
|
|
|
|
<nuclide name="ParentA" decay_modes="0" reactions="0" half_life="1.0">
|
|
|
|
|
<source type="discrete" particle="photon">
|
|
|
|
|
<parameters>1000000.0 1.0</parameters>
|
|
|
|
|
</source>
|
|
|
|
|
</nuclide>
|
|
|
|
|
<nuclide name="ParentB" decay_modes="0" reactions="0" half_life="1.0">
|
|
|
|
|
<source type="discrete" particle="photon">
|
|
|
|
|
<parameters>2000000.0 1.0</parameters>
|
|
|
|
|
</source>
|
|
|
|
|
</nuclide>
|
|
|
|
|
</depletion_chain>
|
|
|
|
|
""")
|
|
|
|
|
|
|
|
|
|
inner_sphere = openmc.Sphere(r=10.0)
|
|
|
|
|
outer_sphere = openmc.Sphere(r=20.0, boundary_type='vacuum')
|
|
|
|
|
|
|
|
|
|
shell_mat = openmc.Material()
|
|
|
|
|
shell_mat.add_nuclide('H1', 1.0)
|
|
|
|
|
shell_mat.set_density('atom/b-cm', 1.0e-12)
|
|
|
|
|
|
|
|
|
|
void_cell = openmc.Cell(region=-inner_sphere)
|
|
|
|
|
shell_cell = openmc.Cell(fill=shell_mat, region=+inner_sphere & -outer_sphere)
|
|
|
|
|
|
|
|
|
|
model = openmc.Model()
|
|
|
|
|
model.geometry = openmc.Geometry([void_cell, shell_cell])
|
|
|
|
|
model.materials = [shell_mat]
|
|
|
|
|
model.settings.run_mode = 'fixed source'
|
|
|
|
|
model.settings.photon_transport = True
|
|
|
|
|
model.settings.particles = 1000
|
|
|
|
|
model.settings.batches = 5
|
|
|
|
|
model.settings.source = openmc.IndependentSource(
|
|
|
|
|
particle='photon',
|
|
|
|
|
space=openmc.stats.Point((0.0, 0.0, 0.0)),
|
|
|
|
|
energy=openmc.stats.DecaySpectrum(
|
|
|
|
|
{'ParentA': 1.0, 'ParentB': 1.0},
|
|
|
|
|
volume=1.0
|
|
|
|
|
)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
tally = openmc.Tally()
|
|
|
|
|
tally.filters = [
|
|
|
|
|
openmc.CellFilter([void_cell]),
|
|
|
|
|
openmc.ParticleFilter(['photon']),
|
|
|
|
|
openmc.EnergyFilter([0.0, 1.5e6, 2.5e6]),
|
|
|
|
|
openmc.ParentNuclideFilter(['ParentA', 'ParentB'])
|
|
|
|
|
]
|
|
|
|
|
tally.scores = ['flux']
|
|
|
|
|
model.tallies = [tally]
|
|
|
|
|
|
|
|
|
|
with openmc.config.patch('chain_file', chain_file):
|
|
|
|
|
sp_filename = model.run()
|
|
|
|
|
|
|
|
|
|
with openmc.StatePoint(sp_filename) as sp:
|
|
|
|
|
tally_out = sp.tallies[tally.id]
|
|
|
|
|
mean = tally_out.get_reshaped_data('mean').squeeze()
|
|
|
|
|
|
|
|
|
|
assert mean.shape == (2, 2)
|
|
|
|
|
assert mean[0, 0] > 0.0
|
|
|
|
|
assert mean[1, 1] > 0.0
|
|
|
|
|
assert mean[0, 1] == 0.0
|
|
|
|
|
assert mean[1, 0] == 0.0
|
|
|
|
|
assert np.count_nonzero(mean) == 2
|
|
|
|
|
|
|
|
|
|
|
2018-01-29 17:41:10 -06:00
|
|
|
def test_source_file():
|
|
|
|
|
filename = 'source.h5'
|
2023-06-20 21:27:55 -05:00
|
|
|
src = openmc.FileSource(path=filename)
|
2024-10-10 12:17:40 -05:00
|
|
|
assert src.path.name == filename
|
2018-01-29 17:41:10 -06:00
|
|
|
|
|
|
|
|
elem = src.to_xml_element()
|
|
|
|
|
assert 'strength' in elem.attrib
|
|
|
|
|
assert 'file' in elem.attrib
|
2019-10-15 22:10:33 +01:00
|
|
|
|
2022-05-25 17:30:13 +02:00
|
|
|
|
2019-10-15 22:10:33 +01:00
|
|
|
def test_source_dlopen():
|
2024-10-10 12:17:40 -05:00
|
|
|
library = 'libsource.so'
|
|
|
|
|
src = openmc.CompiledSource(library)
|
|
|
|
|
assert src.library.name == library
|
2019-10-15 22:10:33 +01:00
|
|
|
|
|
|
|
|
elem = src.to_xml_element()
|
|
|
|
|
assert 'library' in elem.attrib
|
2022-08-31 12:37:06 -05:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_source_xml_roundtrip():
|
|
|
|
|
# Create a source and write to an XML element
|
|
|
|
|
space = openmc.stats.Box([-5., -5., -5.], [5., 5., 5.])
|
|
|
|
|
energy = openmc.stats.Discrete([1.0e6, 2.0e6, 5.0e6], [0.3, 0.5, 0.2])
|
|
|
|
|
angle = openmc.stats.PolarAzimuthal(
|
|
|
|
|
mu=openmc.stats.Uniform(0., 1.),
|
|
|
|
|
phi=openmc.stats.Uniform(0., 2*pi),
|
|
|
|
|
reference_uvw=(0., 1., 0.)
|
|
|
|
|
)
|
2023-06-20 21:27:55 -05:00
|
|
|
src = openmc.IndependentSource(
|
2022-08-31 12:37:06 -05:00
|
|
|
space=space, angle=angle, energy=energy,
|
|
|
|
|
particle='photon', strength=100.0
|
|
|
|
|
)
|
|
|
|
|
elem = src.to_xml_element()
|
|
|
|
|
|
|
|
|
|
# Read from XML element and make sure data is preserved
|
2023-06-20 21:27:55 -05:00
|
|
|
new_src = openmc.IndependentSource.from_xml_element(elem)
|
2022-08-31 12:37:06 -05:00
|
|
|
assert isinstance(new_src.space, openmc.stats.Box)
|
|
|
|
|
np.testing.assert_allclose(new_src.space.lower_left, src.space.lower_left)
|
|
|
|
|
np.testing.assert_allclose(new_src.space.upper_right, src.space.upper_right)
|
|
|
|
|
assert isinstance(new_src.energy, openmc.stats.Discrete)
|
|
|
|
|
np.testing.assert_allclose(new_src.energy.x, src.energy.x)
|
|
|
|
|
np.testing.assert_allclose(new_src.energy.p, src.energy.p)
|
|
|
|
|
assert isinstance(new_src.angle, openmc.stats.PolarAzimuthal)
|
|
|
|
|
assert new_src.angle.mu.a == src.angle.mu.a
|
|
|
|
|
assert new_src.angle.mu.b == src.angle.mu.b
|
|
|
|
|
assert new_src.angle.phi.a == src.angle.phi.a
|
|
|
|
|
assert new_src.angle.phi.b == src.angle.phi.b
|
|
|
|
|
np.testing.assert_allclose(new_src.angle.reference_uvw, src.angle.reference_uvw)
|
|
|
|
|
assert new_src.particle == src.particle
|
|
|
|
|
assert new_src.strength == approx(src.strength)
|
2022-09-26 14:20:34 -05:00
|
|
|
|
|
|
|
|
|
2024-05-15 22:49:04 +02:00
|
|
|
@pytest.fixture
|
|
|
|
|
def sphere_box_model():
|
2022-09-26 14:20:34 -05:00
|
|
|
# Model with two spheres inside a box
|
|
|
|
|
mat = openmc.Material()
|
|
|
|
|
mat.add_nuclide('H1', 1.0)
|
|
|
|
|
sph1 = openmc.Sphere(x0=3, r=1.0)
|
|
|
|
|
sph2 = openmc.Sphere(x0=-3, r=1.0)
|
|
|
|
|
cube = openmc.model.RectangularParallelepiped(
|
|
|
|
|
-5., 5., -5., 5., -5., 5., boundary_type='reflective'
|
|
|
|
|
)
|
|
|
|
|
cell1 = openmc.Cell(fill=mat, region=-sph1)
|
|
|
|
|
cell2 = openmc.Cell(fill=mat, region=-sph2)
|
2022-09-28 09:33:48 -05:00
|
|
|
non_source_region = +sph1 & +sph2 & -cube
|
|
|
|
|
cell3 = openmc.Cell(region=non_source_region)
|
2022-09-26 14:20:34 -05:00
|
|
|
model = openmc.Model()
|
|
|
|
|
model.geometry = openmc.Geometry([cell1, cell2, cell3])
|
|
|
|
|
model.settings.particles = 100
|
|
|
|
|
model.settings.batches = 10
|
|
|
|
|
model.settings.run_mode = 'fixed source'
|
|
|
|
|
|
2024-05-15 22:49:04 +02:00
|
|
|
return model, cell1, cell2, cell3
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_constraints_independent(sphere_box_model, run_in_tmpdir):
|
|
|
|
|
model, cell1, cell2, cell3 = sphere_box_model
|
|
|
|
|
|
2022-09-26 14:20:34 -05:00
|
|
|
# Set up a box source with rejection on the spherical cell
|
2024-05-15 22:49:04 +02:00
|
|
|
space = openmc.stats.Box((-4., -1., -1.), (4., 1., 1.))
|
|
|
|
|
model.settings.source = openmc.IndependentSource(
|
|
|
|
|
space=space, constraints={'domains': [cell1, cell2]}
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# Load up model via openmc.lib and sample source
|
|
|
|
|
model.export_to_model_xml()
|
|
|
|
|
openmc.lib.init()
|
|
|
|
|
particles = openmc.lib.sample_external_source(1000)
|
|
|
|
|
|
|
|
|
|
# Make sure that all sampled sources are within one of the spheres
|
|
|
|
|
for p in particles:
|
|
|
|
|
assert p.r in (cell1.region | cell2.region)
|
|
|
|
|
assert p.r not in cell3.region
|
|
|
|
|
|
|
|
|
|
openmc.lib.finalize()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def test_constraints_mesh(sphere_box_model, run_in_tmpdir):
|
|
|
|
|
model, cell1, cell2, cell3 = sphere_box_model
|
|
|
|
|
|
|
|
|
|
bbox = cell3.bounding_box
|
|
|
|
|
mesh = openmc.RegularMesh()
|
|
|
|
|
mesh.lower_left = bbox.lower_left
|
|
|
|
|
mesh.upper_right = bbox.upper_right
|
|
|
|
|
mesh.dimension = (2, 1, 1)
|
|
|
|
|
|
|
|
|
|
left_source = openmc.IndependentSource()
|
|
|
|
|
right_source = openmc.IndependentSource()
|
|
|
|
|
model.settings.source = openmc.MeshSource(
|
|
|
|
|
mesh, [left_source, right_source], constraints={'domains': [cell1, cell2]}
|
|
|
|
|
)
|
2022-09-26 14:20:34 -05:00
|
|
|
|
|
|
|
|
# Load up model via openmc.lib and sample source
|
2024-05-15 22:49:04 +02:00
|
|
|
model.export_to_model_xml()
|
2022-09-26 14:20:34 -05:00
|
|
|
openmc.lib.init()
|
|
|
|
|
particles = openmc.lib.sample_external_source(1000)
|
|
|
|
|
|
|
|
|
|
# Make sure that all sampled sources are within one of the spheres
|
|
|
|
|
for p in particles:
|
2024-05-15 22:49:04 +02:00
|
|
|
assert p.r in (cell1.region | cell2.region)
|
|
|
|
|
assert p.r not in cell3.region
|
2022-09-26 14:20:34 -05:00
|
|
|
|
|
|
|
|
openmc.lib.finalize()
|
2023-06-20 21:27:55 -05:00
|
|
|
|
|
|
|
|
|
2024-05-15 22:49:04 +02:00
|
|
|
def test_constraints_file(sphere_box_model, run_in_tmpdir):
|
|
|
|
|
model = sphere_box_model[0]
|
|
|
|
|
|
|
|
|
|
# Create source file with randomly sampled source sites
|
|
|
|
|
rng = np.random.default_rng()
|
|
|
|
|
energy = rng.uniform(0., 1e6, 10_000)
|
|
|
|
|
time = rng.uniform(0., 1., 10_000)
|
|
|
|
|
particles = [openmc.SourceParticle(E=e, time=t) for e, t in zip(energy, time)]
|
|
|
|
|
openmc.write_source_file(particles, 'uniform_source.h5')
|
|
|
|
|
|
|
|
|
|
# Use source file
|
|
|
|
|
model.settings.source = openmc.FileSource(
|
|
|
|
|
'uniform_source.h5',
|
|
|
|
|
constraints={
|
|
|
|
|
'time_bounds': [0.25, 0.75],
|
|
|
|
|
'energy_bounds': [500.e3, 1.0e6],
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
# Load up model via openmc.lib and sample source
|
|
|
|
|
model.export_to_model_xml()
|
|
|
|
|
openmc.lib.init()
|
|
|
|
|
particles = openmc.lib.sample_external_source(1000)
|
|
|
|
|
|
|
|
|
|
# Make sure that all sampled sources are within energy/time bounds
|
|
|
|
|
for p in particles:
|
|
|
|
|
assert 0.25 <= p.time <= 0.75
|
|
|
|
|
assert 500.e3 <= p.E <= 1.0e6
|
|
|
|
|
|
|
|
|
|
openmc.lib.finalize()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@pytest.mark.skipif(config['mpi'], reason='Not compatible with MPI')
|
2025-06-10 22:51:51 -05:00
|
|
|
def test_rejection_fraction(run_in_tmpdir):
|
|
|
|
|
mat = openmc.Material()
|
|
|
|
|
mat.add_nuclide('H1', 1.0)
|
|
|
|
|
w = 0.25
|
|
|
|
|
rpp1 = openmc.model.RectangularParallelepiped(
|
|
|
|
|
-w/2, w/2, -w/2, w/2, -w/2, w/2)
|
|
|
|
|
rpp2 = openmc.model.RectangularParallelepiped(
|
|
|
|
|
-0.5, 0.5, -0.5, 0.5, -0.5, 0.5, boundary_type='vacuum')
|
|
|
|
|
cell1 = openmc.Cell(fill=mat, region=-rpp1)
|
|
|
|
|
cell2 = openmc.Cell(region=+rpp1 & -rpp2)
|
|
|
|
|
model = openmc.Model()
|
|
|
|
|
model.geometry = openmc.Geometry([cell1, cell2])
|
2024-05-15 22:49:04 +02:00
|
|
|
|
2025-06-10 22:51:51 -05:00
|
|
|
# Create a box source over a 1 cm³ volume that is constrained to the source
|
|
|
|
|
# cell of volume (0.25 cm)³ = 0.0125 cm³, which means the default rejection
|
|
|
|
|
# fraction of 0.05 won't work
|
|
|
|
|
model.settings.particles = 1000
|
|
|
|
|
model.settings.batches = 1
|
|
|
|
|
model.settings.run_mode = 'fixed source'
|
2024-05-15 22:49:04 +02:00
|
|
|
model.settings.source = openmc.IndependentSource(
|
2025-06-10 22:51:51 -05:00
|
|
|
space=openmc.stats.Box(*(-rpp2).bounding_box),
|
2024-05-15 22:49:04 +02:00
|
|
|
constraints={'domains': [cell1]}
|
|
|
|
|
)
|
2025-06-10 22:51:51 -05:00
|
|
|
with pytest.raises(RuntimeError, match='Too few source sites'):
|
2024-05-15 22:49:04 +02:00
|
|
|
model.run(openmc_exec=config['exe'])
|
|
|
|
|
|
2025-06-10 22:51:51 -05:00
|
|
|
# With a source rejection fraction below 0.0125, the simulation should run
|
|
|
|
|
model.settings.source_rejection_fraction = 0.005
|
|
|
|
|
model.run(openmc_exec=config['exe'])
|
|
|
|
|
|
2024-05-15 22:49:04 +02:00
|
|
|
|
2023-06-20 21:27:55 -05:00
|
|
|
def test_exceptions():
|
|
|
|
|
|
|
|
|
|
with pytest.raises(AttributeError, match=r'Please use the FileSource class'):
|
|
|
|
|
s = openmc.IndependentSource()
|
|
|
|
|
s.file = 'my_file'
|
|
|
|
|
|
|
|
|
|
with pytest.raises(AttributeError, match=r'Please use the CompiledSource class'):
|
|
|
|
|
s = openmc.IndependentSource()
|
|
|
|
|
s.library = 'my_library'
|
|
|
|
|
|
|
|
|
|
with pytest.raises(AttributeError, match=r'Please use the CompiledSource class'):
|
|
|
|
|
s = openmc.IndependentSource()
|
|
|
|
|
s.parameters = 'my_params'
|
|
|
|
|
|
|
|
|
|
with pytest.warns(FutureWarning, match=r'in favor of \'IndependentSource\''):
|
|
|
|
|
s = openmc.Source()
|
|
|
|
|
|
|
|
|
|
with pytest.raises(AttributeError, match=r'has no attribute \'frisbee\''):
|
|
|
|
|
s = openmc.IndependentSource()
|
2024-05-15 22:49:04 +02:00
|
|
|
s.frisbee
|