mirror of
https://github.com/openmc-dev/openmc.git
synced 2026-07-25 12:35:29 -04:00
Some checks are pending
Tests and Coverage / filter-changes (push) Waiting to run
Tests and Coverage / Python 3.13 (omp=n, mpi=n, dagmc=, libmesh=, event= (push) Blocked by required conditions
Tests and Coverage / Python 3.14 (omp=n, mpi=n, dagmc=, libmesh=, event= (push) Blocked by required conditions
Tests and Coverage / Python 3.14t (omp=n, mpi=n, dagmc=, libmesh=, event= (push) Blocked by required conditions
Tests and Coverage / Python 3.12 (omp=n, mpi=n, dagmc=n, libmesh=n, event=n (push) Blocked by required conditions
Tests and Coverage / Python 3.12 (omp=y, mpi=n, dagmc=n, libmesh=n, event=n (push) Blocked by required conditions
Tests and Coverage / Python 3.12 (omp=n, mpi=y, dagmc=n, libmesh=n, event=n (push) Blocked by required conditions
Tests and Coverage / Python 3.12 (omp=y, mpi=y, dagmc=n, libmesh=n, event=n (push) Blocked by required conditions
Tests and Coverage / Python 3.12 (omp=y, mpi=n, dagmc=, libmesh=y, event= (push) Blocked by required conditions
Tests and Coverage / Python 3.12 (omp=y, mpi=n, dagmc=, libmesh=, event=y (push) Blocked by required conditions
Tests and Coverage / Python 3.12 (omp=y, mpi=y, dagmc=y, libmesh=, event= (push) Blocked by required conditions
Tests and Coverage / Python 3.12 (omp=y, mpi=y, dagmc=, libmesh=y, event= (push) Blocked by required conditions
Tests and Coverage / coverage (push) Blocked by required conditions
Tests and Coverage / Check CI status (push) Blocked by required conditions
dockerhub-publish-develop / main (push) Waiting to run
dockerhub-publish-develop-dagmc-libmesh / main (push) Waiting to run
dockerhub-publish-develop-dagmc / main (push) Waiting to run
dockerhub-publish-develop-libmesh / main (push) Waiting to run
630 lines
21 KiB
Python
630 lines
21 KiB
Python
"""Tests for openmc.deplete.Chain class."""
|
|
|
|
from collections.abc import Mapping
|
|
from itertools import product
|
|
from math import log
|
|
import os
|
|
from pathlib import Path
|
|
import warnings
|
|
|
|
import numpy as np
|
|
from openmc.mpi import comm
|
|
from openmc.deplete import Chain, reaction_rates, nuclide, cram, pool
|
|
from openmc.stats import Discrete
|
|
import pytest
|
|
|
|
from tests import cdtemp
|
|
|
|
_TEST_CHAIN = """\
|
|
<depletion_chain>
|
|
<nuclide name="H1" reactions="0"/>
|
|
<nuclide name="A" half_life="23652.0" decay_modes="2" decay_energy="0.0" reactions="2">
|
|
<decay type="beta1" target="B" branching_ratio="0.6"/>
|
|
<decay type="beta2" target="C" branching_ratio="0.4"/>
|
|
<reaction type="(n,gamma)" Q="0.0" target="C"/>
|
|
<reaction type="(n,p)" Q="0.0" target="B"/>
|
|
</nuclide>
|
|
<nuclide name="B" half_life="32904.0" decay_modes="1" decay_energy="0.0" reactions="2">
|
|
<decay type="beta" target="A" branching_ratio="1.0"/>
|
|
<reaction type="(n,gamma)" Q="0.0" target="C"/>
|
|
<reaction type="(n,d)" Q="0.0" target="A"/>
|
|
</nuclide>
|
|
<nuclide name="C" reactions="3">
|
|
<reaction type="fission" Q="200000000.0"/>
|
|
<reaction type="(n,gamma)" Q="0.0" target="A" branching_ratio="0.7"/>
|
|
<reaction type="(n,gamma)" Q="0.0" target="B" branching_ratio="0.3"/>
|
|
<neutron_fission_yields>
|
|
<energies>0.0253</energies>
|
|
<fission_yields energy="0.0253">
|
|
<products>A B</products>
|
|
<data>0.0292737 0.002566345</data>
|
|
</fission_yields>
|
|
</neutron_fission_yields>
|
|
</nuclide>
|
|
</depletion_chain>
|
|
"""
|
|
|
|
|
|
@pytest.fixture(scope='module')
|
|
def simple_chain():
|
|
with cdtemp():
|
|
with open('chain_test.xml', 'w') as fh:
|
|
fh.write(_TEST_CHAIN)
|
|
yield Chain.from_xml('chain_test.xml')
|
|
|
|
|
|
@pytest.fixture(scope='module')
|
|
def endf_chain(endf_data):
|
|
endf_dir = Path(endf_data)
|
|
decay_data = (endf_dir / 'decay').glob('*.endf')
|
|
fpy_data = (endf_dir / 'nfy').glob('*.endf')
|
|
neutron_data = (endf_dir / 'neutrons').glob('*.endf')
|
|
return Chain.from_endf(decay_data, fpy_data, neutron_data)
|
|
|
|
|
|
def test_init():
|
|
"""Test depletion chain initialization."""
|
|
chain = Chain()
|
|
|
|
assert isinstance(chain.nuclides, list)
|
|
assert isinstance(chain.nuclide_dict, Mapping)
|
|
|
|
|
|
def test_len():
|
|
"""Test depletion chain length."""
|
|
chain = Chain()
|
|
chain.nuclides = ["NucA", "NucB", "NucC"]
|
|
|
|
assert len(chain) == 3
|
|
|
|
|
|
def test_from_endf(endf_chain):
|
|
"""Test depletion chain building from ENDF files"""
|
|
chain = endf_chain
|
|
assert len(chain) == len(chain.nuclides) == len(chain.nuclide_dict) == 3820
|
|
for nuc in chain.nuclides:
|
|
assert nuc == chain[nuc.name]
|
|
|
|
|
|
def test_unstable_nuclides(simple_chain: Chain):
|
|
assert [nuc.name for nuc in simple_chain.unstable_nuclides] == ["A", "B"]
|
|
|
|
|
|
def test_from_xml_empty_source(tmp_path):
|
|
"""Ignore legacy chain sources with no distribution parameters."""
|
|
chain_path = tmp_path / 'chain.xml'
|
|
chain_path.write_text("""\
|
|
<depletion_chain>
|
|
<nuclide name="Sm164" half_life="1.226" decay_modes="0"
|
|
decay_energy="0.0" reactions="0">
|
|
<source type="discrete" particle="neutron">
|
|
<parameters> </parameters>
|
|
</source>
|
|
</nuclide>
|
|
</depletion_chain>
|
|
""")
|
|
|
|
chain = Chain.from_xml(chain_path)
|
|
assert chain['Sm164'].sources == {}
|
|
|
|
|
|
def test_stable_nuclides(simple_chain: Chain):
|
|
assert [nuc.name for nuc in simple_chain.stable_nuclides] == ["H1", "C"]
|
|
|
|
|
|
def test_from_xml(simple_chain):
|
|
"""Read chain_test.xml and ensure all values are correct."""
|
|
# Unfortunately, this routine touches a lot of the code, but most of
|
|
# the components external to depletion_chain.py are simple storage
|
|
# types.
|
|
|
|
chain = simple_chain
|
|
|
|
# Basic checks
|
|
assert len(chain) == 4
|
|
|
|
# H1 tests
|
|
nuc = chain["H1"]
|
|
assert nuc.name == "H1"
|
|
assert nuc.n_decay_modes == 0
|
|
assert nuc.n_reaction_paths == 0
|
|
|
|
# A tests
|
|
nuc = chain["A"]
|
|
|
|
assert nuc.name == "A"
|
|
assert nuc.half_life == 2.36520E+04
|
|
assert nuc.n_decay_modes == 2
|
|
modes = nuc.decay_modes
|
|
assert [m.target for m in modes] == ["B", "C"]
|
|
assert [m.type for m in modes] == ["beta1", "beta2"]
|
|
assert [m.branching_ratio for m in modes] == [0.6, 0.4]
|
|
assert nuc.n_reaction_paths == 2
|
|
assert [r.target for r in nuc.reactions] == ["C", "B"]
|
|
assert [r.type for r in nuc.reactions] == ["(n,gamma)", "(n,p)"]
|
|
assert [r.branching_ratio for r in nuc.reactions] == [1.0, 1.0]
|
|
|
|
# B tests
|
|
nuc = chain["B"]
|
|
|
|
assert nuc.name == "B"
|
|
assert nuc.half_life == 3.29040E+04
|
|
assert nuc.n_decay_modes == 1
|
|
modes = nuc.decay_modes
|
|
assert [m.target for m in modes] == ["A"]
|
|
assert [m.type for m in modes] == ["beta"]
|
|
assert [m.branching_ratio for m in modes] == [1.0]
|
|
assert nuc.n_reaction_paths == 2
|
|
assert [r.target for r in nuc.reactions] == ["C", "A"]
|
|
assert [r.type for r in nuc.reactions] == ["(n,gamma)", "(n,d)"]
|
|
assert [r.branching_ratio for r in nuc.reactions] == [1.0, 1.0]
|
|
|
|
# C tests
|
|
nuc = chain["C"]
|
|
|
|
assert nuc.name == "C"
|
|
assert nuc.n_decay_modes == 0
|
|
assert nuc.n_reaction_paths == 3
|
|
assert [r.target for r in nuc.reactions] == [None, "A", "B"]
|
|
assert [r.type for r in nuc.reactions] == ["fission", "(n,gamma)", "(n,gamma)"]
|
|
assert [r.branching_ratio for r in nuc.reactions] == [1.0, 0.7, 0.3]
|
|
|
|
# Yield tests
|
|
assert nuc.yield_energies == (0.0253,)
|
|
assert list(nuc.yield_data) == [0.0253]
|
|
assert nuc.yield_data[0.0253].products == ("A", "B")
|
|
assert (nuc.yield_data[0.0253].yields == [0.0292737, 0.002566345]).all()
|
|
|
|
|
|
def test_export_to_xml(run_in_tmpdir):
|
|
"""Test writing a depletion chain to XML."""
|
|
|
|
# Prevent different MPI ranks from conflicting
|
|
filename = 'test{}.xml'.format(comm.rank)
|
|
|
|
H1 = nuclide.Nuclide("H1")
|
|
|
|
A = nuclide.Nuclide("A")
|
|
A.half_life = 2.36520e4
|
|
A.decay_modes = [
|
|
nuclide.DecayTuple("beta1", "B", 0.6),
|
|
nuclide.DecayTuple("beta2", "C", 0.4)
|
|
]
|
|
A.reactions = [
|
|
nuclide.ReactionTuple("(n,gamma)", "C", 0.0, 1.0),
|
|
nuclide.ReactionTuple("(n,p)", "B", 0.0, 1.0)
|
|
]
|
|
|
|
B = nuclide.Nuclide("B")
|
|
B.half_life = 3.29040e4
|
|
B.decay_modes = [nuclide.DecayTuple("beta", "A", 1.0)]
|
|
B.reactions = [
|
|
nuclide.ReactionTuple("(n,gamma)", "C", 0.0, 1.0),
|
|
nuclide.ReactionTuple("(n,d)", "A", 0.0, 1.0)
|
|
]
|
|
|
|
C = nuclide.Nuclide("C")
|
|
C.reactions = [
|
|
nuclide.ReactionTuple("fission", None, 2.0e8, 1.0),
|
|
nuclide.ReactionTuple("(n,gamma)", "A", 0.0, 0.7),
|
|
nuclide.ReactionTuple("(n,gamma)", "B", 0.0, 0.3)
|
|
]
|
|
C.yield_data = nuclide.FissionYieldDistribution({
|
|
0.0253: {"A": 0.0292737, "B": 0.002566345}})
|
|
|
|
chain = Chain()
|
|
chain.nuclides = [H1, A, B, C]
|
|
chain.export_to_xml(filename)
|
|
|
|
chain_xml = open(filename, 'r').read()
|
|
assert _TEST_CHAIN == chain_xml
|
|
|
|
|
|
def test_form_matrix(simple_chain):
|
|
""" Using chain_test, and a dummy reaction rate, compute the matrix. """
|
|
# Relies on test_from_xml passing.
|
|
|
|
chain = simple_chain
|
|
|
|
mats = ["10000", "10001"]
|
|
nuclides = ["A", "B", "C"]
|
|
|
|
react = reaction_rates.ReactionRates(mats, nuclides, chain.reactions)
|
|
react.set("10000", "C", "fission", 1.0)
|
|
react.set("10000", "A", "(n,gamma)", 2.0)
|
|
react.set("10000", "A", "(n,p)", 0.1)
|
|
react.set("10000", "B", "(n,gamma)", 3.0)
|
|
react.set("10000", "B", "(n,d)", 0.2)
|
|
react.set("10000", "C", "(n,gamma)", 4.0)
|
|
|
|
decay_constant = log(2) / 2.36520E+04
|
|
mat = np.zeros((4, 4))
|
|
mat[0, 1] = 0.1 # A -> B, (n,p)
|
|
mat[1, 1] = -decay_constant - 2.1 # Loss A, decay, (n,gamma), (n,p)
|
|
mat[2, 1] = decay_constant*0.6 + 0.1 # A -> B, decay, 0.6 branching ratio + (n,p)
|
|
mat[3, 1] = decay_constant*0.4 + 2 # A -> C, decay, 0.4 branching ratio + (n,gamma)
|
|
|
|
decay_constant = log(2.0) / 3.29040E+04
|
|
mat[1, 2] = decay_constant + 0.2 # B -> A, decay, 1.0 branching ratio + (n,d)
|
|
mat[2, 2] = -decay_constant - 3.2 # Loss B, decay, (n,gamma), (n,d)
|
|
mat[3, 2] = 3 # B -> C, (n,gamma)
|
|
|
|
mat[1, 3] = 0.0292737 * 1.0 + 4.0 * 0.7 # C -> A fission, (n,gamma)
|
|
mat[2, 3] = 0.002566345 * 1.0 + 4.0 * 0.3 # C -> B fission, (n,gamma)
|
|
mat[3, 3] = -1.0 - 4.0 # Loss C, fission, (n,gamma)
|
|
|
|
sp_matrix = chain.form_matrix(react[0])
|
|
assert np.allclose(mat, sp_matrix.toarray())
|
|
|
|
# Pass equivalent fission yields directly
|
|
# Ensure identical matrix is formed
|
|
f_yields = {"C": {"A": 0.0292737, "B": 0.002566345}}
|
|
new_mat = chain.form_matrix(react[0], f_yields)
|
|
for r, c in product(range(3), range(3)):
|
|
assert new_mat[r, c] == mat[r, c]
|
|
|
|
|
|
def test_decay_matrix(simple_chain):
|
|
"""Test that decay_matrix contains only radioactive decay terms."""
|
|
# Nuclide order: H1(0), A(1), B(2), C(3)
|
|
decay_A = log(2) / 2.36520E+04
|
|
decay_B = log(2) / 3.29040E+04
|
|
|
|
expected = np.zeros((4, 4))
|
|
expected[1, 1] = -decay_A # Loss: A decays
|
|
expected[2, 1] = decay_A * 0.6 # A -> B (branching ratio 0.6)
|
|
expected[3, 1] = decay_A * 0.4 # A -> C (branching ratio 0.4)
|
|
expected[1, 2] = decay_B # B -> A (branching ratio 1.0)
|
|
expected[2, 2] = -decay_B # Loss: B decays
|
|
|
|
assert np.allclose(expected, simple_chain.decay_matrix.toarray())
|
|
|
|
|
|
def test_decay_matrix_cached(simple_chain):
|
|
"""Test that decay_matrix is lazily computed and returns the same object."""
|
|
m1 = simple_chain.decay_matrix
|
|
m2 = simple_chain.decay_matrix
|
|
assert m1 is m2
|
|
|
|
|
|
def test_getitem():
|
|
"""Test nuc_by_ind converter function."""
|
|
chain = Chain()
|
|
chain.nuclides = ["NucA", "NucB", "NucC"]
|
|
chain.nuclide_dict = {nuc: chain.nuclides.index(nuc)
|
|
for nuc in chain.nuclides}
|
|
|
|
assert "NucA" == chain["NucA"]
|
|
assert "NucB" == chain["NucB"]
|
|
assert "NucC" == chain["NucC"]
|
|
|
|
|
|
def test_set_fiss_q():
|
|
"""Make sure new fission q values can be set on the chain"""
|
|
new_q = {"U235": 2.0E8, "U238": 2.0E8, "U234": 5.0E7}
|
|
chain_file = Path(__file__).parents[1] / "chain_simple.xml"
|
|
mod_chain = Chain.from_xml(chain_file, new_q)
|
|
for name, q in new_q.items():
|
|
chain_nuc = mod_chain[name]
|
|
for rx in chain_nuc.reactions:
|
|
if rx.type == 'fission':
|
|
assert rx.Q == q
|
|
|
|
|
|
def test_get_set_chain_br(simple_chain):
|
|
"""Test minor modifications to capture branch ratios"""
|
|
expected = {"C": {"A": 0.7, "B": 0.3}}
|
|
assert simple_chain.get_branch_ratios() == expected
|
|
|
|
# safely modify
|
|
new_chain = Chain.from_xml("chain_test.xml")
|
|
new_br = {"C": {"A": 0.5, "B": 0.5}, "A": {"C": 0.99, "B": 0.01}}
|
|
new_chain.set_branch_ratios(new_br)
|
|
assert new_chain.get_branch_ratios() == new_br
|
|
|
|
# write, re-read
|
|
new_chain.export_to_xml("chain_mod.xml")
|
|
assert Chain.from_xml("chain_mod.xml").get_branch_ratios() == new_br
|
|
|
|
# Test non-strict [warn, not error] setting
|
|
bad_br = {"B": {"X": 0.6, "A": 0.4}, "X": {"A": 0.5, "C": 0.5}}
|
|
bad_br.update(new_br)
|
|
new_chain.set_branch_ratios(bad_br, strict=False)
|
|
assert new_chain.get_branch_ratios() == new_br
|
|
|
|
# Ensure capture reactions are removed
|
|
rem_br = {"A": {"C": 1.0}}
|
|
new_chain.set_branch_ratios(rem_br)
|
|
# A is not in returned dict because there is no branch
|
|
assert "A" not in new_chain.get_branch_ratios()
|
|
|
|
|
|
def test_capture_branch_infer_ground():
|
|
"""Ensure the ground state is inferred if not given"""
|
|
# Make up a metastable capture transition:
|
|
infer_br = {"Xe135": {"Xe136_m1": 0.5}}
|
|
set_br = {"Xe135": {"Xe136": 0.5, "Xe136_m1": 0.5}}
|
|
|
|
chain_file = Path(__file__).parents[1] / "chain_simple.xml"
|
|
chain = Chain.from_xml(chain_file)
|
|
|
|
# Create nuclide to be added into the chain
|
|
xe136m = nuclide.Nuclide("Xe136_m1")
|
|
|
|
chain.add_nuclide(xe136m)
|
|
|
|
chain.set_branch_ratios(infer_br, "(n,gamma)")
|
|
|
|
assert chain.get_branch_ratios("(n,gamma)") == set_br
|
|
|
|
|
|
def test_capture_branch_no_rxn():
|
|
"""Ensure capture reactions that don't exist aren't created"""
|
|
u4br = {"U234": {"U235": 0.5, "U235_m1": 0.5}}
|
|
|
|
chain_file = Path(__file__).parents[1] / "chain_simple.xml"
|
|
chain = Chain.from_xml(chain_file)
|
|
|
|
u5m = nuclide.Nuclide("U235_m1")
|
|
|
|
chain.add_nuclide(u5m)
|
|
|
|
with pytest.raises(AttributeError, match="U234"):
|
|
chain.set_branch_ratios(u4br)
|
|
|
|
|
|
def test_capture_branch_failures(simple_chain):
|
|
"""Test failure modes for setting capture branch ratios"""
|
|
|
|
# Parent isotope not present
|
|
br = {"X": {"A": 0.6, "B": 0.7}}
|
|
with pytest.raises(KeyError, match="X"):
|
|
simple_chain.set_branch_ratios(br)
|
|
|
|
# Product isotope not present
|
|
br = {"C": {"X": 0.4, "A": 0.2, "B": 0.4}}
|
|
with pytest.raises(KeyError, match="X"):
|
|
simple_chain.set_branch_ratios(br)
|
|
|
|
# Sum of ratios > 1.0
|
|
br = {"C": {"A": 1.0, "B": 1.0}}
|
|
with pytest.raises(ValueError, match=r"Sum of \(n,gamma\).*for C"):
|
|
simple_chain.set_branch_ratios(br, "(n,gamma)")
|
|
|
|
|
|
def test_set_alpha_branches():
|
|
"""Test setting of alpha reaction branching ratios"""
|
|
# Build a mock chain
|
|
chain = Chain()
|
|
|
|
parent = nuclide.Nuclide()
|
|
parent.name = "A"
|
|
|
|
he4 = nuclide.Nuclide()
|
|
he4.name = "He4"
|
|
|
|
ground_tgt = nuclide.Nuclide()
|
|
ground_tgt.name = "B"
|
|
|
|
meta_tgt = nuclide.Nuclide()
|
|
meta_tgt.name = "B_m1"
|
|
|
|
for ix, nuc in enumerate((parent, ground_tgt, meta_tgt, he4)):
|
|
chain.nuclides.append(nuc)
|
|
chain.nuclide_dict[nuc.name] = ix
|
|
|
|
# add reactions to parent
|
|
parent.reactions.append(nuclide.ReactionTuple(
|
|
"(n,a)", ground_tgt.name, 1.0, 0.6))
|
|
parent.reactions.append(nuclide.ReactionTuple(
|
|
"(n,a)", meta_tgt.name, 1.0, 0.4))
|
|
parent.reactions.append(nuclide.ReactionTuple(
|
|
"(n,a)", he4.name, 1.0, 1.0))
|
|
|
|
expected_ref = {"A": {"B": 0.6, "B_m1": 0.4}}
|
|
|
|
assert chain.get_branch_ratios("(n,a)") == expected_ref
|
|
|
|
# alter and check again
|
|
|
|
altered = {"A": {"B": 0.5, "B_m1": 0.5}}
|
|
|
|
chain.set_branch_ratios(altered, "(n,a)")
|
|
assert chain.get_branch_ratios("(n,a)") == altered
|
|
|
|
# make sure that alpha particle still produced
|
|
for r in parent.reactions:
|
|
if r.target == he4.name:
|
|
break
|
|
else:
|
|
raise ValueError("Helium has been removed and should not have been")
|
|
|
|
|
|
def test_simple_fission_yields(simple_chain):
|
|
"""Check the default fission yields that can be used to form the matrix
|
|
"""
|
|
fission_yields = simple_chain.get_default_fission_yields()
|
|
assert fission_yields == {"C": {"A": 0.0292737, "B": 0.002566345}}
|
|
|
|
|
|
def test_fission_yield_attribute(simple_chain):
|
|
"""Test the fission_yields property"""
|
|
thermal_yields = simple_chain.get_default_fission_yields()
|
|
# generate default with property
|
|
assert simple_chain.fission_yields[0] == thermal_yields
|
|
empty_chain = Chain()
|
|
empty_chain.fission_yields = thermal_yields
|
|
assert empty_chain.fission_yields[0] == thermal_yields
|
|
empty_chain.fission_yields = [thermal_yields] * 2
|
|
assert empty_chain.fission_yields[0] == thermal_yields
|
|
assert empty_chain.fission_yields[1] == thermal_yields
|
|
|
|
# test failure with deplete function
|
|
# number fission yields != number of materials
|
|
dummy_conc = [[1, 2]] * (len(empty_chain.fission_yields) + 1)
|
|
with pytest.raises(
|
|
ValueError, match="fission yield.*not equal.*compositions"):
|
|
pool.deplete(cram.CRAM48, empty_chain, dummy_conc, None, 0.5)
|
|
|
|
|
|
def test_validate(simple_chain):
|
|
"""Test the validate method"""
|
|
|
|
# current chain is invalid
|
|
# fission yields do not sum to 2.0
|
|
with pytest.raises(ValueError, match="Nuclide C.*fission yields"):
|
|
simple_chain.validate(strict=True, tolerance=0.0)
|
|
|
|
with pytest.warns(UserWarning) as record:
|
|
assert not simple_chain.validate(strict=False, quiet=False, tolerance=0.0)
|
|
assert not simple_chain.validate(strict=False, quiet=True, tolerance=0.0)
|
|
assert len(record) == 1
|
|
assert "Nuclide C" in record[0].message.args[0]
|
|
|
|
# Fix fission yields but keep to restore later
|
|
old_yields = simple_chain["C"].yield_data
|
|
simple_chain["C"].yield_data = {0.0253: {"A": 1.4, "B": 0.6}}
|
|
|
|
assert simple_chain.validate(strict=True, tolerance=0.0)
|
|
with warnings.catch_warnings():
|
|
warnings.simplefilter("error")
|
|
assert simple_chain.validate(strict=False, quiet=False, tolerance=0.0)
|
|
|
|
# Mess up "earlier" nuclide's reactions
|
|
decay_mode = simple_chain["A"].decay_modes.pop()
|
|
|
|
with pytest.raises(ValueError, match="Nuclide A.*decay mode"):
|
|
simple_chain.validate(strict=True, tolerance=0.0)
|
|
|
|
# restore old fission yields
|
|
simple_chain["C"].yield_data = old_yields
|
|
|
|
with pytest.warns(UserWarning) as record:
|
|
assert not simple_chain.validate(strict=False, quiet=False, tolerance=0.0)
|
|
assert len(record) == 2
|
|
assert "Nuclide A" in record[0].message.args[0]
|
|
assert "Nuclide C" in record[1].message.args[0]
|
|
|
|
# restore decay modes
|
|
simple_chain["A"].decay_modes.append(decay_mode)
|
|
|
|
|
|
def test_validate_inputs():
|
|
c = Chain()
|
|
|
|
with pytest.raises(TypeError, match="tolerance"):
|
|
c.validate(tolerance=None)
|
|
|
|
with pytest.raises(ValueError, match="tolerance"):
|
|
c.validate(tolerance=-1)
|
|
|
|
|
|
@pytest.fixture
|
|
def gnd_simple_chain():
|
|
chainfile = Path(__file__).parents[1] / "chain_simple.xml"
|
|
return Chain.from_xml(chainfile)
|
|
|
|
|
|
def test_chain_sources(gnd_simple_chain):
|
|
i135 = gnd_simple_chain['I135']
|
|
assert isinstance(i135.sources, dict)
|
|
assert list(i135.sources.keys()) == ['photon']
|
|
photon_src = i135.sources['photon']
|
|
assert isinstance(photon_src, Discrete)
|
|
assert photon_src.integral() == pytest.approx(3.920996223799345e-05)
|
|
|
|
|
|
def test_reduce(gnd_simple_chain, endf_chain):
|
|
ref_U5 = gnd_simple_chain["U235"]
|
|
ref_iodine = gnd_simple_chain["I135"]
|
|
ref_U5_yields = ref_U5.yield_data
|
|
|
|
no_depth = gnd_simple_chain.reduce(["U235", "I135"], 0)
|
|
# We should get a chain just containing U235 and I135
|
|
assert len(no_depth) == 2
|
|
assert set(no_depth.reactions) == set(gnd_simple_chain.reactions)
|
|
|
|
u5_round0 = no_depth["U235"]
|
|
assert u5_round0.n_decay_modes == ref_U5.n_decay_modes
|
|
assert u5_round0.half_life == ref_U5.half_life
|
|
assert u5_round0.decay_energy == ref_U5.decay_energy
|
|
assert u5_round0.sources == ref_U5.sources
|
|
for newmode, refmode in zip(u5_round0.decay_modes, ref_U5.decay_modes):
|
|
assert newmode.target is None
|
|
assert newmode.type == refmode.type
|
|
assert newmode.branching_ratio == refmode.branching_ratio
|
|
|
|
assert u5_round0.n_reaction_paths == ref_U5.n_reaction_paths
|
|
for newrxn, refrxn in zip(u5_round0.reactions, ref_U5.reactions):
|
|
assert newrxn.target is None
|
|
assert newrxn.type == refrxn.type
|
|
assert newrxn.Q == refrxn.Q
|
|
assert newrxn.branching_ratio == refrxn.branching_ratio
|
|
|
|
assert u5_round0.yield_data is not None
|
|
assert u5_round0.yield_data.products == ("I135",)
|
|
assert u5_round0.yield_data.yield_matrix == (
|
|
ref_U5_yields.yield_matrix[:, ref_U5_yields.products.index("I135")]
|
|
)
|
|
|
|
bareI5 = no_depth["I135"]
|
|
assert bareI5.n_decay_modes == ref_iodine.n_decay_modes
|
|
assert bareI5.half_life == ref_iodine.half_life
|
|
assert bareI5.decay_energy == ref_iodine.decay_energy
|
|
assert bareI5.sources == ref_iodine.sources
|
|
for newmode, refmode in zip(bareI5.decay_modes, ref_iodine.decay_modes):
|
|
assert newmode.target is None
|
|
assert newmode.type == refmode.type
|
|
assert newmode.branching_ratio == refmode.branching_ratio
|
|
|
|
assert bareI5.n_reaction_paths == ref_iodine.n_reaction_paths
|
|
for newrxn, refrxn in zip(bareI5.reactions, ref_iodine.reactions):
|
|
assert newrxn.target is None
|
|
assert newrxn.type == refrxn.type
|
|
assert newrxn.Q == refrxn.Q
|
|
assert newrxn.branching_ratio == refrxn.branching_ratio
|
|
|
|
follow_u5 = gnd_simple_chain.reduce(["U235"], 1)
|
|
u5_round1 = follow_u5["U235"]
|
|
assert u5_round1.decay_modes == ref_U5.decay_modes
|
|
assert u5_round1.reactions == ref_U5.reactions
|
|
assert u5_round1.yield_data is not None
|
|
assert (
|
|
u5_round1.yield_data.yield_matrix == ref_U5_yields.yield_matrix
|
|
).all()
|
|
|
|
# Per the chain_simple.xml
|
|
# I135 -> Xe135 -> Cs135
|
|
# I135 -> Xe136
|
|
# No limit on depth
|
|
iodine_chain = gnd_simple_chain.reduce(["I135"])
|
|
truncated_iodine = gnd_simple_chain.reduce(["I135"], 1)
|
|
assert len(iodine_chain) == 4
|
|
assert len(truncated_iodine) == 3
|
|
assert set(iodine_chain.nuclide_dict) == {
|
|
"I135", "Xe135", "Xe136", "Cs135"}
|
|
assert set(truncated_iodine.nuclide_dict) == {"I135", "Xe135", "Xe136"}
|
|
assert iodine_chain.reactions == ["(n,gamma)"]
|
|
assert iodine_chain["I135"].decay_modes == ref_iodine.decay_modes
|
|
assert iodine_chain["I135"].reactions == ref_iodine.reactions
|
|
for mode in truncated_iodine["Xe135"].decay_modes:
|
|
assert mode.target is None
|
|
|
|
# Test that no FissionYieldDistribution is made if there are no
|
|
# fission products
|
|
u5_noyields = gnd_simple_chain.reduce(["U235"], 0)["U235"]
|
|
assert u5_noyields.yield_data is None
|
|
|
|
# Check early termination if the eventual full chain
|
|
# is specified by using the iodine isotopes
|
|
new_iodine = gnd_simple_chain.reduce(set(iodine_chain.nuclide_dict))
|
|
assert set(iodine_chain.nuclide_dict) == set(new_iodine.nuclide_dict)
|
|
|
|
# Failure if some requested isotopes not in chain
|
|
with pytest.raises(IndexError, match=".*not found.*Xx999"):
|
|
gnd_simple_chain.reduce(["U235", "Xx999"])
|
|
|
|
# Make sure reduce preserves light nuclides produced from reactions like (n,p)
|
|
reduced_chain = endf_chain.reduce(['U235'])
|
|
assert 'H1' in reduced_chain
|
|
assert 'H2' in reduced_chain
|