Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 28 additions & 0 deletions openmc/_xml.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import numpy as np


def clean_indentation(element, level=0, spaces_per_level=2, trailing_indent=True):
"""Set indentation of XML element and its sub-elements.
Copied and pasted from https://effbot.org/zone/element-lib.htm#prettyprint.
Expand Down Expand Up @@ -84,3 +87,28 @@ def get_elem_list(elem, name, dtype=int):
text = get_text(elem, name)
if text is not None:
return [dtype(x) for x in text.split()]


def get_elem_array(elem, name, dtype=int):
"""Helper function to get an array of values from an elem

Parameters
----------
elem : lxml.etree._Element
XML element that should contain a tuple
name : str
Name of the subelement to obtain tuple from
dtype : data-type
The type of each element in the tuple

Returns
-------
numpy.ndarray
Data read from the list
"""
text = get_text(elem, name)
if text is not None:
text = text.strip()
if not text:
return np.array([], dtype=dtype)
return np.fromstring(text, sep=' ', dtype=dtype)
10 changes: 4 additions & 6 deletions openmc/data/decay.py
Original file line number Diff line number Diff line change
Expand Up @@ -513,11 +513,9 @@ def sources(self):
'neutrino': 'neutrino',
}[particle]

if particle_type not in sources:
sources[particle_type] = []

# Create distribution for discrete
if spectra['continuous_flag'] in ('discrete', 'both'):
if (spectra['continuous_flag'] in ('discrete', 'both')
and spectra['discrete']):
energies = []
intensities = []
for discrete_data in spectra['discrete']:
Expand All @@ -527,7 +525,7 @@ def sources(self):
intensity = spectra['discrete_normalization'].n
rates = decay_constant * intensity * np.array(intensities)
dist_discrete = Discrete(energies, rates)
sources[particle_type].append(dist_discrete)
sources.setdefault(particle_type, []).append(dist_discrete)

# Create distribution for continuous
if spectra['continuous_flag'] in ('continuous', 'both'):
Expand All @@ -543,7 +541,7 @@ def sources(self):
intensity = spectra['continuous_normalization'].n
rates = decay_constant * intensity * f.y
dist_continuous = Tabular(f.x, rates, interpolation)
sources[particle_type].append(dist_continuous)
sources.setdefault(particle_type, []).append(dist_continuous)

# Combine discrete distributions
merged_sources = {}
Expand Down
27 changes: 14 additions & 13 deletions openmc/deplete/chain.py
Original file line number Diff line number Diff line change
Expand Up @@ -563,9 +563,6 @@ def from_xml(cls, filename, fission_q=None):
nuc = Nuclide.from_xml(nuclide_elem, root, this_q)
chain.add_nuclide(nuc)

# Store path of XML file (used for handling cache invalidation)
chain._xml_path = str(Path(filename).resolve())

return chain

def export_to_xml(self, filename):
Expand Down Expand Up @@ -1408,12 +1405,18 @@ def _get_chain(
elif not isinstance(chain_file, PathLike):
raise TypeError("chain_file must be path-like, a Chain, or None")

# Determine the key for the cache, which consists of the absolute path, the
# file modification time, the file size, and the fission Q values.
chain_path = Path(chain_file).resolve()
# Determine the key for the cache, which consists of the file identity,
# modification time, file size, and fission Q values.
chain_path = Path(chain_file).absolute()
stat_result = chain_path.stat()
fq_tuple = tuple(sorted(fission_q.items())) if fission_q else ()
key = (chain_path, stat_result.st_mtime, stat_result.st_size, fq_tuple)
key = (
stat_result.st_dev,
stat_result.st_ino,
stat_result.st_mtime_ns,
stat_result.st_size,
fq_tuple,
)

# Check the global cache. If not cached, load the chain from XML and store
global _CHAIN_CACHE
Expand All @@ -1423,10 +1426,8 @@ def _get_chain(


def _invalidate_chain_cache(chain):
"""Invalidate the cache for a specific Chain (when it is modifed)."""
"""Invalidate the cache for a specific Chain (when it is modified)."""
chain._decay_matrix = None
if hasattr(chain, '_xml_path'):
# Remove all entries with the same path as self._xml_path
for key in list(_CHAIN_CACHE.keys()):
if str(key[0]) == chain._xml_path:
del _CHAIN_CACHE[key]
for key, cached_chain in list(_CHAIN_CACHE.items()):
if cached_chain is chain:
del _CHAIN_CACHE[key]
5 changes: 5 additions & 0 deletions openmc/deplete/nuclide.py
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,11 @@ def from_xml(cls, element, root=None, fission_q=None):

# Check for sources
for src_elem in element.iter('source'):
# Skip source elements with empty parameters
parameters = src_elem.find('parameters')
if parameters is not None and not (parameters.text or '').strip():
continue

particle = get_text(src_elem, "particle")
distribution = Univariate.from_xml_element(src_elem)
nuc.sources[particle] = distribution
Expand Down
9 changes: 7 additions & 2 deletions openmc/material.py
Original file line number Diff line number Diff line change
Expand Up @@ -1455,10 +1455,15 @@ def get_activity(
elif units == 'Ci/m3':
multiplier = 1e6 / _BECQUEREL_PER_CURIE

# Resolve chain to avoid repeated lookups for each nuclide
from openmc.deplete.chain import _get_chain
if chain_file is not False:
if chain_file is not None or openmc.config.get('chain_file') is not None:
chain_file = _get_chain(chain_file)

activity = {}
for nuclide, atoms_per_bcm in self.get_nuclide_atom_densities().items():
inv_seconds = openmc.data.decay_constant(
nuclide, chain_file=chain_file)
inv_seconds = openmc.data.decay_constant(nuclide, chain_file=chain_file)
activity[nuclide] = inv_seconds * 1e24 * atoms_per_bcm * multiplier

return activity if by_nuclide else sum(activity.values())
Expand Down
6 changes: 3 additions & 3 deletions openmc/stats/univariate.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
import openmc.checkvalue as cv
from openmc.data import atomic_mass, NEUTRON_MASS
import openmc.data
from .._xml import get_elem_list, get_text
from .._xml import get_elem_list, get_text, get_elem_array
from ..mixin import EqualityMixin

_INTERPOLATION_SCHEMES = {
Expand Down Expand Up @@ -403,7 +403,7 @@ def from_xml_element(cls, elem: ET.Element):
Discrete distribution generated from XML element

"""
params = get_elem_list(elem, "parameters", float)
params = get_elem_array(elem, "parameters", float)
x = params[:len(params)//2]
p = params[len(params)//2:]
bias_dist = cls._read_array_bias_from_xml(elem)
Expand Down Expand Up @@ -1768,7 +1768,7 @@ def from_xml_element(cls, elem: ET.Element):

"""
interpolation = get_text(elem, 'interpolation')
params = get_elem_list(elem, "parameters", float)
params = get_elem_array(elem, "parameters", float)
m = (len(params) + 1)//2 # +1 for when len(params) is odd
x = params[:m]
p = params[m:]
Expand Down
18 changes: 18 additions & 0 deletions tests/unit_tests/test_deplete_chain.py
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,24 @@ 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"]

Expand Down
Loading