mirror of
https://github.com/openmc-dev/openmc.git
synced 2026-07-26 05:05:30 -04:00
Consistent use of cross sections / chain file in Operator class
This commit is contained in:
parent
34ec14c5ee
commit
badaaacca4
4 changed files with 60 additions and 78 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue