OpenMC/tests/unit_tests/test_deplete_microxs.py

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

314 lines
11 KiB
Python
Raw Permalink Normal View History

"""Basic unit tests for openmc.deplete.IndependentOperator instantiation
Modifies and resets environment variable OPENMC_CROSS_SECTIONS
to a custom file with new depletion_chain node
"""
from os import remove
from pathlib import Path
from unittest.mock import patch
import pytest
import openmc
from openmc.deplete import MicroXS, get_microxs_and_flux
import numpy as np
ONE_GROUP_XS = Path(__file__).parents[1] / "micro_xs_simple.csv"
CHAIN_FILE = Path(__file__).parents[1] / "chain_simple.xml"
def test_from_array():
nuclides = [
'U234',
'U235',
'U238',
'U236',
'O16',
'O17',
'I135',
'Xe135',
'Xe136',
'Cs135',
'Gd157',
'Gd156']
reactions = ['fission', '(n,gamma)']
# These values are placeholders and are not at all
# physically meaningful.
data = np.array([[0.1, 0.],
[0.1, 0.],
[0.9, 0.],
[0.4, 0.],
[0., 0.],
[0., 0.],
[0., 0.1],
[0., 0.9],
[0., 0.],
[0., 0.],
[0., 0.1],
[0., 0.1]])
data.shape = (12, 2, 1)
MicroXS(data, nuclides, reactions)
with pytest.raises(ValueError, match='Data array must be 3D'):
MicroXS(data[:, 0], nuclides, reactions)
def test_csv():
ref_xs = MicroXS.from_csv(ONE_GROUP_XS)
ref_xs.to_csv('temp_xs.csv')
temp_xs = MicroXS.from_csv('temp_xs.csv')
assert np.all(ref_xs.data == temp_xs.data)
remove('temp_xs.csv')
def test_from_multigroup_flux():
energies = [0., 6.25e-1, 5.53e3, 8.21e5, 2.e7]
flux = [1.1e-7, 1.2e-6, 1.3e-5, 1.4e-4]
chain_file = Path(__file__).parents[1] / 'chain_simple.xml'
kwargs = {'multigroup_flux': flux, 'chain_file': chain_file}
# test with energy group structure from string
microxs = MicroXS.from_multigroup_flux(energies='CASMO-4', **kwargs)
assert isinstance(microxs, MicroXS)
# test with energy group structure as floats
microxs = MicroXS.from_multigroup_flux(energies=energies, **kwargs)
assert isinstance(microxs, MicroXS)
# test with nuclides provided
microxs = MicroXS.from_multigroup_flux(
energies=energies, nuclides=['Gd157', 'H1'], **kwargs
)
assert isinstance(microxs, MicroXS)
assert microxs.nuclides == ['Gd157', 'H1']
# test with reactions provided
microxs = MicroXS.from_multigroup_flux(
energies=energies, reactions=['fission', '(n,2n)'], **kwargs
)
assert isinstance(microxs, MicroXS)
assert microxs.reactions == ['fission', '(n,2n)']
def test_multigroup_flux_same():
chain_file = Path(__file__).parents[1] / 'chain_simple.xml'
# Generate micro XS based on 4-group flux
energies = [0., 6.25e-1, 5.53e3, 8.21e5, 2.e7]
flux_per_ev = [0.3, 0.3, 1.0, 1.0]
flux = flux_per_ev * np.diff(energies)
flux_sum = flux.sum()
microxs_4g = MicroXS.from_multigroup_flux(
energies=energies, multigroup_flux=flux, chain_file=chain_file)
# from_multigroup_flux should not modify the flux
assert flux.sum() == flux_sum
# Generate micro XS based on 2-group flux, where the boundaries line up with
# the 4 group flux and have the same flux per eV across the full energy
# range
energies = [0., 5.53e3, 2.0e7]
flux_per_ev = [0.3, 1.0]
flux = flux_per_ev * np.diff(energies)
microxs_2g = MicroXS.from_multigroup_flux(
energies=energies, multigroup_flux=flux, chain_file=chain_file)
assert microxs_4g.data == pytest.approx(microxs_2g.data)
def test_microxs_zero_flux():
chain_file = Path(__file__).parents[1] / 'chain_simple.xml'
# Generate micro XS based on zero flux
energies = [0., 6.25e-1, 5.53e3, 8.21e5, 2.e7]
flux = [0.0, 0.0, 0.0, 0.0]
microxs = MicroXS.from_multigroup_flux(
energies=energies, multigroup_flux=flux, chain_file=chain_file)
# All microscopic cross sections should be zero
assert np.all(microxs.data == 0.0)
def test_hybrid_tally_setup():
"""In hybrid mode a 1-group RR tally is added alongside the flux tally."""
# Create a simple model with one material and a few nuclides for testing
model = openmc.Model()
mat = openmc.Material(components={'U235': 1.0, 'O16': 2.0})
sphere = openmc.Sphere(r=10.0, boundary_type='vacuum')
cell = openmc.Cell(region=-sphere, fill=mat)
model.geometry = openmc.Geometry([cell])
model.settings.batches = 2
model.settings.particles = 10
# Define 2-group energy structure for the test
energies = [0., 0.625, 2.0e7]
# Function to replace Model.run and capture the tallies that were created
captured = {}
def capture_run(**kwargs):
captured['tallies'] = list(model.tallies)
raise StopIteration
# Call get_microxs_and_flux but replace Model.run with a function that
# captures the tallies and raises StopIteration to exit early
with patch.object(model, 'run', side_effect=capture_run):
with pytest.raises(StopIteration):
get_microxs_and_flux(
model, [mat],
nuclides=['U235', 'O16'],
reactions=['fission', '(n,gamma)'],
energies=energies,
reaction_rate_mode='flux',
reaction_rate_opts={'nuclides': ['U235'], 'reactions': ['fission']},
chain_file=CHAIN_FILE,
)
# Check that both tallies were created with the expected properties
tally_names = [t.name for t in captured['tallies']]
assert 'MicroXS flux 0' in tally_names
assert 'MicroXS RR 0' in tally_names
# Check that the RR tally has the expected nuclides and reactions
rr = next(t for t in captured['tallies'] if t.name == 'MicroXS RR 0')
assert rr.nuclides == ['U235']
assert rr.scores == ['fission']
# RR tally must use a 1-group energy filter spanning the full energy range
ef = next(f for f in rr.filters if isinstance(f, openmc.EnergyFilter))
assert len(ef.values) == 2
assert ef.values[0] == pytest.approx(energies[0])
assert ef.values[-1] == pytest.approx(energies[-1])
def _simple_model():
model = openmc.Model()
mat = openmc.Material(components={'H1': 1.0, 'H2': 1.0},
density=5.0, density_units='g/cm3')
sphere = openmc.Sphere(r=10.0, boundary_type='vacuum')
cell = openmc.Cell(region=-sphere, fill=mat)
model.geometry = openmc.Geometry([cell])
model.settings.particles = 100
model.settings.batches = 5
model.settings.run_mode = 'fixed source'
return model, mat
def test_hybrid_tally_defaults_to_all_nuclides(run_in_tmpdir):
energies = [0., 0.625, 2.0e7]
kwargs = {
'nuclides': ['H1', 'H2'],
'reactions': ['(n,2n)', '(n,gamma)'],
'energies': energies,
'reaction_rate_mode': 'flux',
'chain_file': CHAIN_FILE,
}
model, mat = _simple_model()
default_fluxes, default_micros = get_microxs_and_flux(
model, [mat], reaction_rate_opts={'reactions': ['(n,2n)']}, **kwargs
)
model, mat = _simple_model()
explicit_fluxes, explicit_micros = get_microxs_and_flux(
model, [mat],
reaction_rate_opts={
'nuclides': ['H1', 'H2'],
'reactions': ['(n,2n)']
},
**kwargs
)
np.testing.assert_allclose(default_fluxes[0], explicit_fluxes[0])
np.testing.assert_allclose(default_micros[0].data, explicit_micros[0].data)
assert default_micros[0].nuclides == explicit_micros[0].nuclides
assert default_micros[0].reactions == explicit_micros[0].reactions
def test_flux_mode_returns_one_group_flux(run_in_tmpdir):
model, mat = _simple_model()
fluxes, micros = get_microxs_and_flux(
model, [mat],
nuclides=['H1'],
reactions=['(n,2n)'],
energies=[0., 0.625, 2.0e7],
reaction_rate_mode='flux',
chain_file=CHAIN_FILE,
)
assert fluxes[0].shape == (1,)
assert micros[0].data.shape == (1, 1, 1)
assert fluxes[0][0] > 0.0
# ---------------------------------------------------------------------------
# Tests for MicroXS.merge()
# ---------------------------------------------------------------------------
def _make_microxs(nuclides, reactions, values, groups=1):
"""Helper: build a MicroXS from a flat list of values (nuclide-major order)."""
data = np.array(values, dtype=float).reshape(
len(nuclides), len(reactions), groups)
return MicroXS(data, nuclides, reactions)
def test_merge_disjoint():
"""Merging two MicroXS with no overlapping nuclides or reactions."""
m1 = _make_microxs(['U235', 'U238'], ['fission', '(n,gamma)'],
[1., 2., 3., 4.])
m2 = _make_microxs(['Pu239'], ['(n,2n)'], [5.])
merged = m1.merge(m2)
assert merged.nuclides == ['U235', 'U238', 'Pu239']
assert merged.reactions == ['fission', '(n,gamma)', '(n,2n)']
assert merged.data.shape == (3, 3, 1)
# Self data preserved
assert merged['U235', 'fission'] == pytest.approx([1.])
assert merged['U238', '(n,gamma)'] == pytest.approx([4.])
# New data from other
assert merged['Pu239', '(n,2n)'] == pytest.approx([5.])
# Cross-terms that had no data should be zero
assert merged['U235', '(n,2n)'] == pytest.approx([0.])
assert merged['Pu239', 'fission'] == pytest.approx([0.])
def test_merge_prefer_other():
"""prefer='other': other overwrites shared entries, adds new ones."""
# m1: U235 and U238, reactions fission and (n,gamma)
m1 = _make_microxs(['U235', 'U238'], ['fission', '(n,gamma)'],
[1., 2., 3., 4.])
# m2: only U235, reactions fission (overlap) and (n,2n) (new)
m2 = _make_microxs(['U235'], ['fission', '(n,2n)'], [9., 5.])
merged = m1.merge(m2)
# Nuclide/reaction sets
assert set(merged.nuclides) == {'U235', 'U238'}
assert set(merged.reactions) == {'fission', '(n,gamma)', '(n,2n)'}
# U235/fission overwritten by m2
assert merged['U235', 'fission'] == pytest.approx([9.])
# U235/(n,gamma) untouched
assert merged['U235', '(n,gamma)'] == pytest.approx([2.])
# New reaction added from m2
assert merged['U235', '(n,2n)'] == pytest.approx([5.])
# U238 data preserved
assert merged['U238', 'fission'] == pytest.approx([3.])
assert merged['U238', '(n,gamma)'] == pytest.approx([4.])
# U238/(n,2n) not in either → zero
assert merged['U238', '(n,2n)'] == pytest.approx([0.])
def test_merge_prefer_self():
"""prefer='self': shared pairs keep self's value; new entries use other's value."""
m1 = _make_microxs(['U235', 'U238'], ['fission', '(n,gamma)'],
[1., 2., 3., 4.])
m2 = _make_microxs(['U235'], ['fission', '(n,2n)'], [9., 5.])
merged = m1.merge(m2, prefer='self')
# U235/fission: self wins
assert merged['U235', 'fission'] == pytest.approx([1.])
# U235/(n,2n): other used
assert merged['U235', '(n,2n)'] == pytest.approx([5.])