Merge pull request #1948 from paulromano/more-bugfixes

Miscellaneous bug fixes
This commit is contained in:
Patrick Shriwise 2022-01-17 16:57:14 -06:00 committed by GitHub
commit 0a82da0a1a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 80 additions and 100 deletions

View file

@ -615,7 +615,8 @@ class ThermalScattering(EqualityMixin):
ace_name, xs = ace.name.split('.')
if not xs.endswith('t'):
raise TypeError("{} is not a thermal scattering ACE table.".format(ace))
name = get_thermal_name(ace_name)
if name is None:
name = get_thermal_name(ace_name)
# Assign temperature to the running list
kTs = [ace.temperature*EV_PER_MEV]
@ -783,9 +784,10 @@ class ThermalScattering(EqualityMixin):
# Create instance from ACE tables within library
lib = Library(kwargs['ace'])
data = cls.from_ace(lib.tables[0])
name = kwargs.get('table_name')
data = cls.from_ace(lib.tables[0], name=name)
for table in lib.tables[1:]:
data.add_temperature_from_ace(table)
data.add_temperature_from_ace(table, name=name)
# Load ENDF data to replace incoherent elastic
if use_endf_data:

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

@ -700,14 +700,7 @@ class Material(IDManagerMixin):
cv.check_type('S(a,b) fraction', fraction, Real)
cv.check_greater_than('S(a,b) fraction', fraction, 0.0, True)
cv.check_less_than('S(a,b) fraction', fraction, 1.0, True)
new_name = openmc.data.get_thermal_name(name)
if new_name != name:
msg = 'OpenMC S(a,b) tables follow the GND naming convention. ' \
'Table "{}" is being renamed as "{}".'.format(name, new_name)
warnings.warn(msg)
self._sab.append((new_name, fraction))
self._sab.append((name, fraction))
def make_isotropic_in_lab(self):
self.isotropic = [x.name for x in self._nuclides]

View file

@ -1,10 +1,11 @@
from collections.abc import Iterable
from contextlib import contextmanager
from functools import lru_cache
import os
from pathlib import Path
from numbers import Integral
from tempfile import NamedTemporaryFile
import time
from contextlib import contextmanager
import h5py
@ -528,8 +529,11 @@ class Model:
"""
# Setting tstart here ensures we don't pick up any pre-existing
# statepoint files in the output directory
tstart = time.time()
# statepoint files in the output directory -- just in case there are
# differences between the system clock and the filesystem, we get the
# time of a just-created temporary file
with NamedTemporaryFile() as fp:
tstart = Path(fp.name).stat().st_mtime
last_statepoint = None
# Operate in the provided working directory

View file

@ -46,12 +46,11 @@ vector<unique_ptr<Source>> external_sources;
// IndependentSource implementation
//==============================================================================
IndependentSource::IndependentSource(UPtrSpace space, UPtrAngle angle, UPtrDist energy, UPtrDist time)
: space_{std::move(space)},
angle_{std::move(angle)},
energy_{std::move(energy)},
time_{std::move(time)}
{ }
IndependentSource::IndependentSource(
UPtrSpace space, UPtrAngle angle, UPtrDist energy, UPtrDist time)
: space_ {std::move(space)}, angle_ {std::move(angle)},
energy_ {std::move(energy)}, time_ {std::move(time)}
{}
IndependentSource::IndependentSource(pugi::xml_node node)
{
@ -150,7 +149,7 @@ IndependentSource::IndependentSource(pugi::xml_node node)
// Default to a Constant time T=0
double T[] {0.0};
double p[] {1.0};
time_ = UPtrDist {new Discrete{T, p, 1}};
time_ = UPtrDist {new Discrete {T, p, 1}};
}
}
}
@ -238,7 +237,7 @@ SourceSite IndependentSource::sample(uint64_t* seed) const
// Sample particle creation time
site.time = time_->sample(seed);
return site;
}

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)