OpenMC/tests/unit_tests/test_mesh.py
Jon Shimwell e5c7d0ca88
Adding vtkhdf option to write vtk data (#3252)
Co-authored-by: shimwell <mail@jshimwell.com>
Co-authored-by: Jonathan Shimwell <drshimwell@gmail.com>
Co-authored-by: rherrero-pf <156206440+rherrero-pf@users.noreply.github.com>
Co-authored-by: Patrick Shriwise <pshriwise@gmail.com>
Co-authored-by: Paul Romano <paul.k.romano@gmail.com>
2025-11-05 16:03:20 +00:00

830 lines
30 KiB
Python

from math import pi
from tempfile import TemporaryDirectory
from pathlib import Path
import h5py
import numpy as np
from scipy.stats import chi2
import pytest
import openmc
import openmc.lib
from openmc.utility_funcs import change_directory
from uncertainties.unumpy import uarray, nominal_values, std_devs
@pytest.mark.parametrize("val_left,val_right", [(0, 0), (-1., -1.), (2.0, 2)])
def test_raises_error_when_flat(val_left, val_right):
"""Checks that an error is raised when a mesh is flat"""
mesh = openmc.RegularMesh()
# Same X
with pytest.raises(ValueError):
mesh.lower_left = [val_left, -25, -25]
mesh.upper_right = [val_right, 25, 25]
with pytest.raises(ValueError):
mesh.upper_right = [val_right, 25, 25]
mesh.lower_left = [val_left, -25, -25]
# Same Y
with pytest.raises(ValueError):
mesh.lower_left = [-25, val_left, -25]
mesh.upper_right = [25, val_right, 25]
with pytest.raises(ValueError):
mesh.upper_right = [25, val_right, 25]
mesh.lower_left = [-25, val_left, -25]
# Same Z
with pytest.raises(ValueError):
mesh.lower_left = [-25, -25, val_left]
mesh.upper_right = [25, 25, val_right]
with pytest.raises(ValueError):
mesh.upper_right = [25, 25, val_right]
mesh.lower_left = [-25, -25, val_left]
def test_regular_mesh_bounding_box():
mesh = openmc.RegularMesh()
mesh.lower_left = [-2, -3, -5]
mesh.upper_right = [2, 3, 5]
bb = mesh.bounding_box
assert isinstance(bb, openmc.BoundingBox)
np.testing.assert_array_equal(bb.lower_left, (-2, -3 ,-5))
np.testing.assert_array_equal(bb.upper_right, (2, 3, 5))
def test_rectilinear_mesh_bounding_box():
mesh = openmc.RectilinearMesh()
mesh.x_grid = [0., 1., 5., 10.]
mesh.y_grid = [-10., -5., 0.]
mesh.z_grid = [-100., 0., 100.]
bb = mesh.bounding_box
assert isinstance(bb, openmc.BoundingBox)
np.testing.assert_array_equal(bb.lower_left, (0., -10. ,-100.))
np.testing.assert_array_equal(bb.upper_right, (10., 0., 100.))
def test_cylindrical_mesh_bounding_box():
# test with mesh at origin (0, 0, 0)
mesh = openmc.CylindricalMesh(
r_grid=[0.1, 0.2, 0.5, 1.],
z_grid=[0.1, 0.2, 0.4, 0.6, 1.],
origin=(0, 0, 0)
)
np.testing.assert_array_equal(mesh.upper_right, (1, 1, 1))
np.testing.assert_array_equal(mesh.lower_left, (-1, -1, 0.1))
bb = mesh.bounding_box
assert isinstance(bb, openmc.BoundingBox)
np.testing.assert_array_equal(bb.lower_left, (-1, -1, 0.1))
np.testing.assert_array_equal(bb.upper_right, (1, 1, 1))
# test with mesh at origin (3, 5, 7)
mesh.origin = (3, 5, 7)
np.testing.assert_array_equal(mesh.upper_right, (4, 6, 8))
np.testing.assert_array_equal(mesh.lower_left, (2, 4, 7.1))
bb = mesh.bounding_box
assert isinstance(bb, openmc.BoundingBox)
np.testing.assert_array_equal(bb.lower_left, (2, 4, 7.1))
np.testing.assert_array_equal(bb.upper_right, (4, 6, 8))
# changing z grid to contain negative numbers
mesh.z_grid = [-10, 0, 10]
np.testing.assert_array_equal(mesh.lower_left, (2, 4, -3))
np.testing.assert_array_equal(mesh.upper_right, (4, 6, 17))
def test_spherical_mesh_bounding_box():
# test with mesh at origin (0, 0, 0)
mesh = openmc.SphericalMesh([0.1, 0.2, 0.5, 1.], origin=(0., 0., 0.))
np.testing.assert_array_equal(mesh.upper_right, (1, 1, 1))
np.testing.assert_array_equal(mesh.lower_left, (-1, -1, -1))
bb = mesh.bounding_box
assert isinstance(bb, openmc.BoundingBox)
np.testing.assert_array_equal(bb.lower_left, (-1, -1, -1))
np.testing.assert_array_equal(bb.upper_right, (1, 1, 1))
# test with mesh at origin (3, 5, 7)
mesh.origin = (3, 5, 7)
np.testing.assert_array_equal(mesh.upper_right, (4, 6, 8))
np.testing.assert_array_equal(mesh.lower_left, (2, 4, 6))
bb = mesh.bounding_box
assert isinstance(bb, openmc.BoundingBox)
np.testing.assert_array_equal(bb.lower_left, (2, 4, 6))
np.testing.assert_array_equal(bb.upper_right, (4, 6, 8))
def test_SphericalMesh_initiation():
# test defaults
mesh = openmc.SphericalMesh(r_grid=(0, 10))
assert (mesh.origin == np.array([0, 0, 0])).all()
assert (mesh.r_grid == np.array([0, 10])).all()
assert (mesh.theta_grid == np.array([0, pi])).all()
assert (mesh.phi_grid == np.array([0, 2*pi])).all()
# test setting on creation
mesh = openmc.SphericalMesh(
origin=(1, 2, 3),
r_grid=(0, 2),
theta_grid=(1, 3),
phi_grid=(2, 4)
)
assert (mesh.origin == np.array([1, 2, 3])).all()
assert (mesh.r_grid == np.array([0., 2.])).all()
assert (mesh.theta_grid == np.array([1, 3])).all()
assert (mesh.phi_grid == np.array([2, 4])).all()
# test attribute changing
mesh.r_grid = (0, 11)
assert (mesh.r_grid == np.array([0., 11.])).all()
# test invalid r_grid values
with pytest.raises(ValueError):
openmc.SphericalMesh(r_grid=[1, 1])
with pytest.raises(ValueError):
openmc.SphericalMesh(r_grid=[0])
# test invalid theta_grid values
with pytest.raises(ValueError):
openmc.SphericalMesh(r_grid=[1, 2], theta_grid=[1, 1])
with pytest.raises(ValueError):
openmc.SphericalMesh(r_grid=[1, 2], theta_grid=[0])
# test invalid phi_grid values
with pytest.raises(ValueError):
openmc.SphericalMesh(r_grid=[1, 2], phi_grid=[1, 1])
with pytest.raises(ValueError):
openmc.SphericalMesh(r_grid=[1, 2], phi_grid=[0])
# waffles and pancakes are unfortunately not valid radii
with pytest.raises(TypeError):
openmc.SphericalMesh(('🧇', '🥞'))
def test_CylindricalMesh_initiation():
# test defaults
mesh = openmc.CylindricalMesh(r_grid=(0, 10), z_grid=(0, 10))
assert (mesh.origin == np.array([0, 0, 0])).all()
assert (mesh.r_grid == np.array([0, 10])).all()
assert (mesh.phi_grid == np.array([0, 2*pi])).all()
assert (mesh.z_grid == np.array([0, 10])).all()
# test setting on creation
mesh = openmc.CylindricalMesh(
origin=(1, 2, 3),
r_grid=(0, 2),
z_grid=(1, 3),
phi_grid=(2, 4)
)
assert (mesh.origin == np.array([1, 2, 3])).all()
assert (mesh.r_grid == np.array([0., 2.])).all()
assert (mesh.z_grid == np.array([1, 3])).all()
assert (mesh.phi_grid == np.array([2, 4])).all()
# test attribute changing
mesh.r_grid = (0., 10.)
assert (mesh.r_grid == np.array([0, 10.])).all()
mesh.z_grid = (0., 4.)
assert (mesh.z_grid == np.array([0, 4.])).all()
# waffles and pancakes are unfortunately not valid radii
with pytest.raises(TypeError):
openmc.SphericalMesh(('🧇', '🥞'))
def test_invalid_cylindrical_mesh_errors():
# Test invalid r_grid values
with pytest.raises(ValueError):
openmc.CylindricalMesh(r_grid=[5, 1], phi_grid=[0, pi], z_grid=[0, 10])
with pytest.raises(ValueError):
openmc.CylindricalMesh(r_grid=[1, 2, 4, 3], phi_grid=[0, pi], z_grid=[0, 10])
with pytest.raises(ValueError):
openmc.CylindricalMesh(r_grid=[1], phi_grid=[0, pi], z_grid=[0, 10])
# Test invalid phi_grid values
with pytest.raises(ValueError):
openmc.CylindricalMesh(r_grid=[0, 1, 2], phi_grid=[-1, 3], z_grid=[0, 10])
with pytest.raises(ValueError):
openmc.CylindricalMesh(
r_grid=[0, 1, 2],
phi_grid=[0, 2*pi + 0.1],
z_grid=[0, 10]
)
with pytest.raises(ValueError):
openmc.CylindricalMesh(r_grid=[0, 1, 2], phi_grid=[pi], z_grid=[0, 10])
# Test invalid z_grid values
with pytest.raises(ValueError):
openmc.CylindricalMesh(r_grid=[0, 1, 2], phi_grid=[0, pi], z_grid=[5])
with pytest.raises(ValueError):
openmc.CylindricalMesh(r_grid=[0, 1, 2], phi_grid=[0, pi], z_grid=[5, 1])
with pytest.raises(ValueError):
openmc.CylindricalMesh(r_grid=[1, 2, 4, 3], phi_grid=[0, pi], z_grid=[0, 10, 5])
def test_centroids():
# regular mesh
mesh = openmc.RegularMesh()
mesh.lower_left = (1., 2., 3.)
mesh.upper_right = (11., 12., 13.)
mesh.dimension = (1, 1, 1)
np.testing.assert_array_almost_equal(mesh.centroids[0, 0, 0], [6., 7., 8.])
# rectilinear mesh
mesh = openmc.RectilinearMesh()
mesh.x_grid = [1., 11.]
mesh.y_grid = [2., 12.]
mesh.z_grid = [3., 13.]
np.testing.assert_array_almost_equal(mesh.centroids[0, 0, 0], [6., 7., 8.])
# cylindrical mesh
mesh = openmc.CylindricalMesh(r_grid=(0, 10), z_grid=(0, 10), phi_grid=(0, np.pi))
np.testing.assert_array_almost_equal(mesh.centroids[0, 0, 0], [0.0, 5.0, 5.0])
# ensure that setting an origin is handled correctly
mesh.origin = (5.0, 0, -10)
np.testing.assert_array_almost_equal(mesh.centroids[0, 0, 0], [5.0, 5.0, -5.0])
# spherical mesh, single element xyz-positive octant
mesh = openmc.SphericalMesh(r_grid=[0, 10], theta_grid=[0, 0.5*np.pi], phi_grid=[0, np.pi])
x = 5.*np.cos(0.5*np.pi)*np.sin(0.25*np.pi)
y = 5.*np.sin(0.5*np.pi)*np.sin(0.25*np.pi)
z = 5.*np.sin(0.25*np.pi)
np.testing.assert_array_almost_equal(mesh.centroids[0, 0, 0], [x, y, z])
mesh.origin = (-5.0, -5.0, 5.0)
np.testing.assert_array_almost_equal(mesh.centroids[0, 0, 0], [x-5.0, y-5.0, z+5.0])
@pytest.mark.parametrize('mesh_type', ('regular', 'rectilinear', 'cylindrical', 'spherical'))
def test_mesh_vertices(mesh_type):
ijk = (2, 3, 2)
# create a new mesh object
if mesh_type == 'regular':
mesh = openmc.RegularMesh()
ll = np.asarray([0.]*3)
width = np.asarray([0.5]*3)
mesh.lower_left = ll
mesh.width = width
mesh.dimension = (5, 7, 9)
# spot check that an element has the correct vertex coordinates asociated with it
# (using zero-indexing here)
exp_i_j_k = ll + np.asarray(ijk, dtype=float) * width
np.testing.assert_equal(mesh.vertices[ijk], exp_i_j_k)
# shift the mesh using the llc
shift = np.asarray((3.0, 6.0, 10.0))
mesh.lower_left += shift
np.testing.assert_equal(mesh.vertices[ijk], exp_i_j_k+shift)
elif mesh_type == 'rectilinear':
mesh = openmc.RectilinearMesh()
w = np.asarray([0.5] * 3)
ll = np.asarray([0.]*3)
dims = (5, 7, 9)
mesh.x_grid = np.linspace(ll[0], w[0]*dims[0], dims[0])
mesh.y_grid = np.linspace(ll[1], w[1]*dims[1], dims[1])
mesh.z_grid = np.linspace(ll[2], w[2]*dims[2], dims[2])
exp_vert = np.asarray((mesh.x_grid[2], mesh.y_grid[3], mesh.z_grid[2]))
np.testing.assert_equal(mesh.vertices[ijk], exp_vert)
elif mesh_type == 'cylindrical':
r_grid = np.linspace(0, 5, 10)
z_grid = np.linspace(-10, 10, 20)
phi_grid = np.linspace(0, 2*np.pi, 8)
mesh = openmc.CylindricalMesh(r_grid=r_grid, z_grid=z_grid, phi_grid=phi_grid)
exp_vert = np.asarray((mesh.r_grid[2], mesh.phi_grid[3], mesh.z_grid[2]))
np.testing.assert_equal(mesh.vertices_cylindrical[ijk], exp_vert)
elif mesh_type == 'spherical':
r_grid = np.linspace(0, 13, 14)
theta_grid = np.linspace(0, np.pi, 11)
phi_grid = np.linspace(0, 2*np.pi, 7)
mesh = openmc.SphericalMesh(r_grid=r_grid, theta_grid=theta_grid, phi_grid=phi_grid)
exp_vert = np.asarray((mesh.r_grid[2], mesh.theta_grid[3], mesh.phi_grid[2]))
np.testing.assert_equal(mesh.vertices_spherical[ijk], exp_vert)
def test_CylindricalMesh_get_indices_at_coords():
# default origin (0, 0, 0) and default phi grid (0, 2*pi)
mesh = openmc.CylindricalMesh(r_grid=(0, 5, 10), z_grid=(0, 5, 10))
assert mesh.get_indices_at_coords([1, 0, 1]) == (0, 0, 0)
assert mesh.get_indices_at_coords([6, 0, 1]) == (1, 0, 0)
assert mesh.get_indices_at_coords([9, 0, 1]) == (1, 0, 0)
assert mesh.get_indices_at_coords([0, 6, 0]) == (1, 0, 0)
assert mesh.get_indices_at_coords([0, 9, 6]) == (1, 0, 1)
assert mesh.get_indices_at_coords([-2, -2, 9]) == (0, 0, 1)
with pytest.raises(ValueError):
assert mesh.get_indices_at_coords([8, 8, 1]) # resulting r value to large
with pytest.raises(ValueError):
assert mesh.get_indices_at_coords([-8, -8, 1]) # resulting r value to large
with pytest.raises(ValueError):
assert mesh.get_indices_at_coords([1, 0, -1]) # z value below range
with pytest.raises(ValueError):
assert mesh.get_indices_at_coords([1, 0, 11]) # z value above range
assert mesh.get_indices_at_coords([1, 1, 1]) == (0, 0, 0)
# negative range on z grid
mesh = openmc.CylindricalMesh(
r_grid=(0, 5, 10),
phi_grid=(0, 0.5 * pi, pi, 1.5 * pi, 1.9 * pi),
z_grid=(-5, 0, 5, 10),
)
assert mesh.get_indices_at_coords([1, 1, 1]) == (0, 0, 1) # first angle quadrant
assert mesh.get_indices_at_coords([2, 2, 6]) == (0, 0, 2) # first angle quadrant
assert mesh.get_indices_at_coords([-2, 0.1, -1]) == (0, 1, 0) # second angle quadrant
assert mesh.get_indices_at_coords([-2, -0.1, -1]) == (0, 2, 0) # third angle quadrant
assert mesh.get_indices_at_coords([2, -0.9, -1]) == (0, 3, 0) # forth angle quadrant
with pytest.raises(ValueError):
assert mesh.get_indices_at_coords([2, -0.1, 1]) # outside of phi range
# origin of mesh not default
mesh = openmc.CylindricalMesh(
r_grid=(0, 5, 10),
phi_grid=(0, 0.5 * pi, pi, 1.5 * pi, 1.9 * pi),
z_grid=(-5, 0, 5, 10),
origin=(100, 200, 300),
)
assert mesh.get_indices_at_coords([101, 201, 301]) == (0, 0, 1) # first angle quadrant
assert mesh.get_indices_at_coords([102, 202, 306]) == (0, 0, 2) # first angle quadrant
assert mesh.get_indices_at_coords([98, 200.1, 299]) == (0, 1, 0) # second angle quadrant
assert mesh.get_indices_at_coords([98, 199.9, 299]) == (0, 2, 0) # third angle quadrant
assert mesh.get_indices_at_coords([102, 199.1, 299]) == (0, 3, 0) # forth angle quadrant
def test_mesh_name_roundtrip(run_in_tmpdir):
mesh = openmc.RegularMesh()
mesh.name = 'regular-mesh'
mesh.lower_left = (-1, -1, -1)
mesh.width = (1, 1, 1)
mesh.dimension = (1, 1, 1)
mesh_filter = openmc.MeshFilter(mesh)
tally = openmc.Tally()
tally.filters = [mesh_filter]
tally.scores = ['flux']
openmc.Tallies([tally]).export_to_xml()
xml_tallies = openmc.Tallies.from_xml()
mesh = xml_tallies[0].find_filter(openmc.MeshFilter).mesh
assert mesh.name == 'regular-mesh'
def test_umesh_roundtrip(run_in_tmpdir, request):
umesh = openmc.UnstructuredMesh(request.path.parent / 'test_mesh_tets.e', 'moab')
umesh.output = True
# create a tally using this mesh
mf = openmc.MeshFilter(umesh)
tally = openmc.Tally()
tally.filters = [mf]
tally.scores = ['flux']
tallies = openmc.Tallies([tally])
tallies.export_to_xml()
xml_tallies = openmc.Tallies.from_xml()
xml_tally = xml_tallies[0]
xml_mesh = xml_tally.filters[0].mesh
assert umesh.id == xml_mesh.id
@pytest.fixture(scope='module')
def simple_umesh(request):
"""Fixture returning UnstructuredMesh with all attributes"""
surf1 = openmc.Sphere(r=20.0, boundary_type="vacuum")
material1 = openmc.Material()
material1.add_element("H", 1.0)
material1.set_density('g/cm3', 1.0)
materials = openmc.Materials([material1])
cell1 = openmc.Cell(region=-surf1, fill=material1)
geometry = openmc.Geometry([cell1])
umesh = openmc.UnstructuredMesh(
filename=request.path.parent.parent
/ "regression_tests/external_moab/test_mesh_tets.h5m",
library="moab",
mesh_id=1
)
# setting ID to make it easier to get the mesh from the statepoint later
mesh_filter = openmc.MeshFilter(umesh)
# Create flux mesh tally to score alpha production
mesh_tally = openmc.Tally(name="test_tally")
mesh_tally.filters = [mesh_filter]
mesh_tally.scores = ["total"]
tallies = openmc.Tallies([mesh_tally])
settings = openmc.Settings()
settings.run_mode = "fixed source"
settings.batches = 2
settings.particles = 100
settings.source = openmc.IndependentSource(
space=openmc.stats.Point((0.1, 0.1, 0.1))
)
model = openmc.Model(
materials=materials, geometry=geometry, settings=settings, tallies=tallies
)
with change_directory(tmpdir=True):
statepoint_file = model.run()
with openmc.StatePoint(statepoint_file) as sp:
return sp.meshes[1]
@pytest.mark.skipif(not openmc.lib._dagmc_enabled(), reason="DAGMC not enabled.")
@pytest.mark.parametrize('export_type', ('.vtk', '.vtu'))
def test_umesh(run_in_tmpdir, simple_umesh, export_type):
"""Performs a minimal UnstructuredMesh simulation, reads in the resulting
statepoint file and writes the mesh data to vtk and vtkhdf files. It is
necessary to read in the unstructured mesh from a statepoint file to ensure
it has all the required attributes
"""
# Get VTK modules
vtkIOLegacy = pytest.importorskip("vtkmodules.vtkIOLegacy")
vtkIOXML = pytest.importorskip("vtkmodules.vtkIOXML")
# Sample some random data and write to VTK
rng = np.random.default_rng()
ref_data = rng.random(simple_umesh.dimension)
filename = f"test_mesh{export_type}"
simple_umesh.write_data_to_vtk(datasets={"mean": ref_data}, filename=filename)
assert Path(filename).exists()
if export_type == ".vtk":
reader = vtkIOLegacy.vtkGenericDataObjectReader()
elif export_type == ".vtu":
reader = vtkIOXML.vtkXMLGenericDataObjectReader()
reader.SetFileName(str(filename))
reader.Update()
# Get mean from file and make sure it matches original data
arr = reader.GetOutput().GetCellData().GetArray("mean")
mean = np.array([arr.GetTuple1(i) for i in range(ref_data.size)])
np.testing.assert_almost_equal(mean, ref_data)
# attempt to apply a dataset with an improper size to a VTK write
with pytest.raises(ValueError, match='Cannot apply dataset "mean"'):
simple_umesh.write_data_to_vtk(datasets={'mean': ref_data[:-2]}, filename=filename)
@pytest.mark.skipif(not openmc.lib._dagmc_enabled(), reason="DAGMC not enabled.")
def test_write_vtkhdf(request, run_in_tmpdir):
"""Performs a minimal UnstructuredMesh simulation, reads in the resulting
statepoint file and writes the mesh data to vtk and vtkhdf files. It is
necessary to read in the unstructured mesh from a statepoint file to ensure
it has all the required attributes
"""
model = openmc.Model()
surf1 = openmc.Sphere(r=1000.0, boundary_type="vacuum")
cell1 = openmc.Cell(region=-surf1)
model.geometry = openmc.Geometry([cell1])
umesh = openmc.UnstructuredMesh(
request.path.parent / "test_mesh_dagmc_tets.vtk",
"moab",
mesh_id = 1
)
mesh_filter = openmc.MeshFilter(umesh)
# Create flux mesh tally to score alpha production
mesh_tally = openmc.Tally(name="test_tally")
mesh_tally.filters = [mesh_filter]
mesh_tally.scores = ["flux"]
model.tallies = [mesh_tally]
model.settings.run_mode = "fixed source"
model.settings.batches = 2
model.settings.particles = 10
statepoint_file = model.run()
with openmc.StatePoint(statepoint_file) as statepoint:
my_tally = statepoint.get_tally(name="test_tally")
umesh_from_sp = statepoint.meshes[umesh.id]
datasets={
"mean": my_tally.mean.flatten(),
"std_dev": my_tally.std_dev.flatten()
}
umesh_from_sp.write_data_to_vtk(datasets=datasets, filename="test_mesh.vtkhdf")
umesh_from_sp.write_data_to_vtk(datasets=datasets, filename="test_mesh.vtk")
with pytest.raises(ValueError, match="Unsupported file extension"):
# Supported file extensions are vtk or vtkhdf, not hdf5, so this should raise an error
umesh_from_sp.write_data_to_vtk(
datasets=datasets,
filename="test_mesh.hdf5",
)
with pytest.raises(ValueError, match="Cannot apply dataset"):
# The shape of the data should match the shape of the mesh, so this should raise an error
umesh_from_sp.write_data_to_vtk(
datasets={'incorrectly_shaped_data': np.array(([1,2,3]))},
filename="test_mesh_incorrect_shape.vtkhdf",
)
assert Path("test_mesh.vtk").exists()
assert Path("test_mesh.vtkhdf").exists()
# just ensure we can open the file without error
with h5py.File("test_mesh.vtkhdf", "r"):
...
def test_mesh_get_homogenized_materials():
"""Test the get_homogenized_materials method"""
# Simple model with 1 cm of Fe56 next to 1 cm of H1
fe = openmc.Material()
fe.add_nuclide('Fe56', 1.0)
fe.set_density('g/cm3', 5.0)
h = openmc.Material()
h.add_nuclide('H1', 1.0)
h.set_density('g/cm3', 1.0)
x0 = openmc.XPlane(-1.0, boundary_type='vacuum')
x1 = openmc.XPlane(0.0)
x2 = openmc.XPlane(1.0)
x3 = openmc.XPlane(2.0, boundary_type='vacuum')
cell1 = openmc.Cell(fill=fe, region=+x0 & -x1)
cell2 = openmc.Cell(fill=h, region=+x1 & -x2)
cell_empty = openmc.Cell(region=+x2 & -x3)
model = openmc.Model(geometry=openmc.Geometry([cell1, cell2, cell_empty]))
model.settings.particles = 1000
model.settings.batches = 10
mesh = openmc.RegularMesh()
mesh.lower_left = (-1., -1., -1.)
mesh.upper_right = (1., 1., 1.)
mesh.dimension = (3, 1, 1)
m1, m2, m3 = mesh.get_homogenized_materials(model, n_samples=10_000)
# Left mesh element should be only Fe56
assert m1.get_mass_density('Fe56') == pytest.approx(5.0)
# Middle mesh element should be 50% Fe56 and 50% H1
assert m2.get_mass_density('Fe56') == pytest.approx(2.5, rel=1e-2)
assert m2.get_mass_density('H1') == pytest.approx(0.5, rel=1e-2)
# Right mesh element should be only H1
assert m3.get_mass_density('H1') == pytest.approx(1.0)
mesh_void = openmc.RegularMesh()
mesh_void.lower_left = (0.5, 0.5, -1.)
mesh_void.upper_right = (1.5, 1.5, 1.)
mesh_void.dimension = (1, 1, 1)
m4, = mesh_void.get_homogenized_materials(model, n_samples=(100, 100, 0))
# Mesh element that overlaps void should have half density
assert m4.get_mass_density('H1') == pytest.approx(0.5, rel=1e-2)
# If not including void, density of homogenized material should be same as
# original material
m5, = mesh_void.get_homogenized_materials(
model, n_samples=1000, include_void=False)
assert m5.get_mass_density('H1') == pytest.approx(1.0)
@pytest.fixture
def sphere_model():
# Model with three materials separated by planes x=0 and z=0
mats = []
for i in range(3):
mat = openmc.Material()
mat.add_nuclide('H1', 1.0)
mat.set_density('g/cm3', float(i + 1))
mats.append(mat)
sph = openmc.Sphere(r=25.0, boundary_type='vacuum')
x0 = openmc.XPlane(0.0)
z0 = openmc.ZPlane(0.0)
cell1 = openmc.Cell(fill=mats[0], region=-sph & +x0 & +z0)
cell2 = openmc.Cell(fill=mats[1], region=-sph & -x0 & +z0)
cell3 = openmc.Cell(fill=mats[2], region=-sph & -z0)
model = openmc.Model()
model.geometry = openmc.Geometry([cell1, cell2, cell3])
model.materials = openmc.Materials(mats)
return model
@pytest.mark.parametrize("n_rays", [1000, (10, 10, 0), (10, 0, 10), (0, 10, 10)])
def test_material_volumes_regular_mesh(sphere_model, n_rays):
"""Test the material_volumes method on a regular mesh"""
mesh = openmc.RegularMesh()
mesh.lower_left = (-1., -1., -1.)
mesh.upper_right = (1., 1., 1.)
mesh.dimension = (2, 2, 2)
volumes = mesh.material_volumes(sphere_model, n_rays)
mats = sphere_model.materials
np.testing.assert_almost_equal(volumes[mats[0].id], [0., 0., 0., 0., 0., 1., 0., 1.])
np.testing.assert_almost_equal(volumes[mats[1].id], [0., 0., 0., 0., 1., 0., 1., 0.])
np.testing.assert_almost_equal(volumes[mats[2].id], [1., 1., 1., 1., 0., 0., 0., 0.])
assert volumes.by_element(4) == [(mats[1].id, 1.)]
assert volumes.by_element(0) == [(mats[2].id, 1.)]
def test_material_volumes_cylindrical_mesh(sphere_model):
"""Test the material_volumes method on a cylindrical mesh"""
cyl_mesh = openmc.CylindricalMesh(
[0., 1.], [-1., 0., 1.,], [0.0, pi/4, 3*pi/4, 5*pi/4, 7*pi/4, 2*pi])
volumes = cyl_mesh.material_volumes(sphere_model, (0, 100, 100))
mats = sphere_model.materials
np.testing.assert_almost_equal(volumes[mats[0].id], [
0., 0., 0., 0., 0.,
pi/8, pi/8, 0., pi/8, pi/8
])
np.testing.assert_almost_equal(volumes[mats[1].id], [
0., 0., 0., 0., 0.,
0., pi/8, pi/4, pi/8, 0.
])
np.testing.assert_almost_equal(volumes[mats[2].id], [
pi/8, pi/4, pi/4, pi/4, pi/8,
0., 0., 0., 0., 0.
])
def test_mesh_material_volumes_serialize():
materials = np.array([
[1, -1, -2],
[-1, -2, -2],
[2, 1, -2],
[2, -2, -2]
])
volumes = np.array([
[0.5, 0.5, 0.0],
[1.0, 0.0, 0.0],
[0.5, 0.5, 0.0],
[1.0, 0.0, 0.0]
])
volumes = openmc.MeshMaterialVolumes(materials, volumes)
with TemporaryDirectory() as tmpdir:
path = f'{tmpdir}/volumes.npz'
volumes.save(path)
new_volumes = openmc.MeshMaterialVolumes.from_npz(path)
assert new_volumes.by_element(0) == [(1, 0.5), (None, 0.5)]
assert new_volumes.by_element(1) == [(None, 1.0)]
assert new_volumes.by_element(2) == [(2, 0.5), (1, 0.5)]
assert new_volumes.by_element(3) == [(2, 1.0)]
def test_mesh_material_volumes_boundary_conditions(sphere_model):
"""Test the material volumes method using a regular mesh
that overlaps with a vacuum boundary condition."""
mesh = openmc.SphericalMesh.from_domain(sphere_model.geometry, dimension=(1, 1, 1))
# extend mesh beyond the outer sphere surface to test rays crossing the boundary condition
mesh.r_grid[-1] += 5.0
# add a new cell to the modelthat occupies the outside of the sphere
sphere_surfaces = list(filter(lambda s: isinstance(s, openmc.Sphere),
sphere_model.geometry.get_all_surfaces().values()))
outer_cell = openmc.Cell(region=+sphere_surfaces[0])
sphere_model.geometry.root_universe.add_cell(outer_cell)
volumes = mesh.material_volumes(sphere_model, (0, 100, 100))
sphere_volume = 4/3*np.pi*25**3
mats = sphere_model.materials
expected_volumes = [(mats[0].id, 0.25*sphere_volume),
(mats[1].id, 0.25*sphere_volume),
(mats[2].id, 0.5*sphere_volume),
(None, 4/3*np.pi*mesh.r_grid[-1]**3 - sphere_volume)]
for evaluated, expected in zip(volumes.by_element(0), expected_volumes):
assert evaluated[0] == expected[0]
assert evaluated[1] == pytest.approx(expected[1], rel=1e-2)
def test_raytrace_mesh_infinite_loop(run_in_tmpdir):
# Create a model with one large spherical cell
sphere = openmc.Sphere(r=100, boundary_type='vacuum')
cell = openmc.Cell(region=-sphere)
model = openmc.Model()
model.geometry = openmc.Geometry([cell])
# Create a regular mesh and associated tally
mesh_surface = openmc.RegularMesh()
mesh_surface.lower_left = (-30, -30, 30)
mesh_surface.upper_right = (30, 30, 60)
mesh_surface.dimension = (1, 1, 1)
reg_filter = openmc.MeshSurfaceFilter(mesh_surface)
mesh_surface_tally = openmc.Tally()
mesh_surface_tally.filters = [reg_filter]
mesh_surface_tally.scores = ['current']
model.tallies = [mesh_surface_tally]
# Define a source such that the z position is on a mesh boundary with a very
# small directional cosine in the z direction
polar = openmc.stats.delta_function(1.75e-7)
azimuthal = openmc.stats.Uniform(0.0, 2.0*pi)
model.settings.source = openmc.IndependentSource(
angle=openmc.stats.PolarAzimuthal(polar, azimuthal)
)
model.settings.run_mode = 'fixed source'
model.settings.particles = 10
model.settings.batches = 1
# Run the model; this should not cause an infinite loop
model.run()
def test_filter_time_mesh(run_in_tmpdir):
"""Test combination of TimeFilter and MeshFilter"""
# Define material
mat = openmc.Material()
mat.add_nuclide('Fe56', 1.0)
mat.set_density('g/cm3', 7.8)
# Define geometry
surf_Z1 = openmc.XPlane(x0=-1e10, boundary_type="reflective")
surf_Z2 = openmc.XPlane(x0=1e10, boundary_type="reflective")
cell_F = openmc.Cell(fill=mat, region=+surf_Z1 & -surf_Z2)
model = openmc.Model()
model.geometry = openmc.Geometry([cell_F])
# Define settings
model.settings.run_mode = "fixed source"
model.settings.particles = 1000
model.settings.batches = 20
model.settings.output = {"tallies": False}
model.settings.cutoff = {"time_neutron": 1e-7}
# Define tallies
# Create a mesh filter that can be used in a tally
mesh = openmc.RegularMesh()
mesh.dimension = (21, 1, 1)
mesh.lower_left = (-20.5, -1e10, -1e10)
mesh.upper_right = (20.5, 1e10, 1e10)
time_grid = np.linspace(0.0, 1e-7, 21)
mesh_filter = openmc.MeshFilter(mesh)
time_filter = openmc.TimeFilter(time_grid)
# Now use the mesh filter in a tally and indicate what scores are desired
tally1 = openmc.Tally(name="collision")
tally1.estimator = "collision"
tally1.filters = [time_filter, mesh_filter]
tally1.scores = ["flux"]
tally2 = openmc.Tally(name="tracklength")
tally2.estimator = "tracklength"
tally2.filters = [time_filter, mesh_filter]
tally2.scores = ["flux"]
model.tallies = openmc.Tallies([tally1, tally2])
# Run and post-process
model.run(apply_tally_results=True)
# Get radial flux distribution
flux_collision = tally1.mean.ravel()
flux_collision_unc = tally1.std_dev.ravel()
flux_tracklength = tally2.mean.ravel()
flux_tracklength_unc = tally2.std_dev.ravel()
# Construct arrays with uncertainties
collision = uarray(flux_collision, flux_collision_unc)
tracklength = uarray(flux_tracklength, flux_tracklength_unc)
delta = collision - tracklength
# Compute differences and standard deviations
diff = nominal_values(delta)
std_dev = std_devs(delta)
# Exclude zero-uncertainty bins
mask = std_dev > 0.0
dof = int(np.sum(mask))
# Global chi-square consistency test between collision and tracklength
# estimators. Target false positive rate ~1e-4 (1 in 10,000)
z = diff[mask] / std_dev[mask]
chi2_stat = np.sum(z * z)
alpha = 1.0e-4
crit = chi2.ppf(1 - alpha, dof)
assert chi2_stat < crit, (
f"Collision vs tracklength tallies disagree: chi2={chi2_stat:.2f} "
f">= {crit=:.2f} ({dof=}, {alpha=})"
)