Consistent use of cross sections / chain file in Operator class

This commit is contained in:
Paul Romano 2022-01-11 17:50:50 -06:00
parent 34ec14c5ee
commit badaaacca4
4 changed files with 60 additions and 78 deletions

View file

@ -19,7 +19,6 @@ from warnings import warn
from numpy import nonzero, empty, asarray
from uncertainties import ufloat
from openmc.data import DataLibrary
from openmc.lib import MaterialFilter, Tally
from openmc.checkvalue import check_type, check_greater_than
from openmc.mpi import comm
@ -70,10 +69,8 @@ class TransportOperator(ABC):
Parameters
----------
chain_file : str, optional
Path to the depletion chain XML file. Defaults to the file
listed under ``depletion_chain`` in
:envvar:`OPENMC_CROSS_SECTIONS` environment variable.
chain_file : str
Path to the depletion chain XML file
fission_q : dict, optional
Dictionary of nuclides and their fission Q values [eV]. If not given,
values will be pulled from the ``chain_file``.
@ -95,30 +92,12 @@ class TransportOperator(ABC):
Results from a previous depletion calculation. ``None`` if no
results are to be used.
"""
def __init__(self, chain_file=None, fission_q=None, dilute_initial=1.0e3,
def __init__(self, chain_file, fission_q=None, dilute_initial=1.0e3,
prev_results=None):
self.dilute_initial = dilute_initial
self.output_dir = '.'
# Read depletion chain
if chain_file is None:
chain_file = os.environ.get("OPENMC_DEPLETE_CHAIN", None)
if chain_file is None:
data = DataLibrary.from_xml()
# search for depletion_chain path from end of list
for lib in reversed(data.libraries):
if lib['type'] == 'depletion_chain':
break
else:
raise IOError(
"No chain specified, either manually or "
"under depletion_chain in environment variable "
"OPENMC_CROSS_SECTIONS.")
chain_file = lib['path']
else:
warn("Use of OPENMC_DEPLETE_CHAIN is deprecated in favor "
"of adding depletion_chain to OPENMC_CROSS_SECTIONS",
FutureWarning)
self.chain = Chain.from_xml(chain_file, fission_q)
if prev_results is None:
self.prev_res = None

View file

@ -7,6 +7,7 @@ loaded from an .xml file and all the nuclides are linked together.
from io import StringIO
from itertools import chain
import math
import os
import re
from collections import OrderedDict, defaultdict, namedtuple
from collections.abc import Mapping, Iterable
@ -14,7 +15,8 @@ from numbers import Real, Integral
from warnings import warn
from openmc.checkvalue import check_type, check_greater_than
from openmc.data import gnd_name, zam
from openmc.data import gnd_name, zam, DataLibrary
from openmc.exceptions import DataError
from .nuclide import FissionYieldDistribution
# Try to use lxml if it is available. It preserves the order of attributes and
@ -237,6 +239,24 @@ def replace_missing_fpy(actinide, fpy_data, decay_data):
return 'U235'
def _find_chain_file(cross_sections=None):
# First check deprecated OPENMC_DEPLETE_CHAIN environment variable
chain_file = os.environ.get("OPENMC_DEPLETE_CHAIN")
if chain_file is not None:
warn("Use of OPENMC_DEPLETE_CHAIN is deprecated in favor of adding "
"depletion_chain to OPENMC_CROSS_SECTIONS", FutureWarning)
return chain_file
# Check for depletion chain in cross_sections.xml
data = DataLibrary.from_xml(cross_sections)
for lib in reversed(data.libraries):
if lib['type'] == 'depletion_chain':
return lib['path']
raise DataError("No depletion chain specified and could not find depletion "
f"chain in {cross_sections}")
class Chain:
"""Full representation of a depletion chain.

View file

@ -10,20 +10,20 @@ densities is all done in-memory instead of through the filesystem.
import copy
from collections import OrderedDict
import os
from typing import Type
import xml.etree.ElementTree as ET
from warnings import warn
from pathlib import Path
import numpy as np
from uncertainties import ufloat
import openmc
from openmc.checkvalue import check_value
from openmc.data import DataLibrary
from openmc.exceptions import DataError
import openmc.lib
from openmc.mpi import comm
from .abc import TransportOperator, OperatorResult
from .atom_number import AtomNumber
from .chain import _find_chain_file
from .reaction_rates import ReactionRates
from .results_list import ResultsList
from .helpers import (
@ -58,6 +58,22 @@ def _distribute(items):
j += chunk_size
def _find_cross_sections(model):
"""Determine cross sections to use for depletion"""
if model.materials and model.materials.cross_sections is not None:
# Prefer info from Model class if available
return model.materials.cross_sections
# otherwise fallback to environment variable
cross_sections = os.environ.get("OPENMC_CROSS_SECTIONS")
if cross_sections is None:
raise DataError(
"Cross sections were not specified in Model.materials and "
"the OPENMC_CROSS_SECTIONS environment variable is not set."
)
return cross_sections
class Operator(TransportOperator):
"""OpenMC transport operator for depletion.
@ -206,6 +222,11 @@ class Operator(TransportOperator):
"to generate the depletion Operator."
raise TypeError(msg)
# Determine cross sections / depletion chain
cross_sections = _find_cross_sections(model)
if chain_file is None:
chain_file = _find_chain_file(cross_sections)
check_value('fission yield mode', fission_yield_mode,
self._fission_helpers.keys())
check_value('normalization mode', normalization_mode,
@ -270,7 +291,7 @@ class Operator(TransportOperator):
self.prev_res.append(new_res)
# Determine which nuclides have incident neutron data
self.nuclides_with_data = self._get_nuclides_with_data()
self.nuclides_with_data = self._get_nuclides_with_data(cross_sections)
# Select nuclides with data that are also in the chain
self._burnable_nucs = [nuc.name for nuc in self.chain.nuclides
@ -637,14 +658,6 @@ class Operator(TransportOperator):
for mat in self.materials:
mat._nuclides.sort(key=lambda x: nuclides.index(x[0]))
# Grab the cross sections tag from the existing file
mfile = Path("materials.xml")
if mfile.exists():
tree = ET.parse(str(mfile))
xs = tree.find('cross_sections')
if xs is not None and self.materials.cross_sections is None:
self.materials.cross_sections = xs.text
self.materials.export_to_xml()
def _get_tally_nuclides(self):
@ -768,40 +781,14 @@ class Operator(TransportOperator):
return OperatorResult(k_combined, rates)
def _get_nuclides_with_data(self):
"""Loads a cross_sections.xml file to find participating nuclides.
This allows for nuclides that are important in the decay chain but not
important neutronically, or have no cross section data.
"""
# Reads cross_sections.xml to create a dictionary containing
# participating (burning and not just decaying) nuclides.
try:
filename = os.environ["OPENMC_CROSS_SECTIONS"]
except KeyError:
filename = None
def _get_nuclides_with_data(self, cross_sections):
"""Loads cross_sections.xml file to find nuclides with neutron data"""
nuclides = set()
try:
tree = ET.parse(filename)
except Exception:
if filename is None:
msg = "No cross_sections.xml specified in materials."
else:
msg = 'Cross section file "{}" is invalid.'.format(filename)
raise IOError(msg)
root = tree.getroot()
for nuclide_node in root.findall('library'):
mats = nuclide_node.get('materials')
if not mats:
data_lib = DataLibrary.from_xml(cross_sections)
for library in data_lib.libraries:
if library['type'] != 'neutron':
continue
for name in mats.split():
# Make a burn list of the union of nuclides in cross_sections.xml
# and nuclides in depletion chain.
for name in library['materials']:
if name not in nuclides:
nuclides.add(name)

View file

@ -10,7 +10,7 @@ from pathlib import Path
import pytest
from openmc.deplete.abc import TransportOperator
from openmc.deplete.chain import Chain
from openmc.deplete.chain import Chain, _find_chain_file
BARE_XS_FILE = "bare_cross_sections.xml"
CHAIN_PATH = Path(__file__).parents[1] / "chain_simple.xml"
@ -31,7 +31,7 @@ def bare_xs(run_in_tmpdir):
with open(BARE_XS_FILE, "w") as out:
out.write(bare_xs_contents)
yield
yield BARE_XS_FILE
class BareDepleteOperator(TransportOperator):
@ -54,14 +54,10 @@ class BareDepleteOperator(TransportOperator):
pass
@mock.patch.dict(environ, {"OPENMC_CROSS_SECTIONS": BARE_XS_FILE})
def test_operator_init(bare_xs):
"""The test will set and unset environment variable OPENMC_CROSS_SECTIONS
to point towards a temporary dummy file. This file will be removed
at the end of the test, and only contains a
depletion_chain node."""
# force operator to read from OPENMC_CROSS_SECTIONS
bare_op = BareDepleteOperator(chain_file=None)
"""The test uses a temporary dummy chain. This file will be removed
at the end of the test, and only contains a depletion_chain node."""
bare_op = BareDepleteOperator(_find_chain_file(bare_xs))
act_chain = bare_op.chain
ref_chain = Chain.from_xml(CHAIN_PATH)
assert len(act_chain) == len(ref_chain)