Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Miscellaneous bug fixes #1948

Merged
merged 5 commits into from Jan 17, 2022
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
8 changes: 5 additions & 3 deletions openmc/data/thermal.py
Expand Up @@ -615,7 +615,8 @@ def from_ace(cls, ace_or_filename, name=None):
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]
Expand Down Expand Up @@ -783,9 +784,10 @@ def from_njoy(cls, filename, filename_thermal, temperatures=None,

# 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:
Expand Down
27 changes: 3 additions & 24 deletions openmc/deplete/abc.py
Expand Up @@ -19,7 +19,6 @@
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
Expand Down Expand Up @@ -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``.
Expand All @@ -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
Expand Down
22 changes: 21 additions & 1 deletion openmc/deplete/chain.py
Expand Up @@ -7,14 +7,16 @@
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
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
Expand Down Expand Up @@ -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.

Expand Down
75 changes: 31 additions & 44 deletions openmc/deplete/operator.py
Expand Up @@ -10,20 +10,20 @@
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 (
Expand Down Expand Up @@ -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.

Expand Down Expand Up @@ -206,6 +222,11 @@ def __init__(self, model, chain_file=None, prev_results=None,
"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,
Expand Down Expand Up @@ -270,7 +291,7 @@ def __init__(self, model, chain_file=None, prev_results=None,
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
Expand Down Expand Up @@ -637,14 +658,6 @@ def _generate_materials_xml(self):
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):
Expand Down Expand Up @@ -768,40 +781,14 @@ def _unpack_tallies_and_normalize(self, source_rate):

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)

Expand Down
9 changes: 1 addition & 8 deletions openmc/material.py
Expand Up @@ -700,14 +700,7 @@ def add_s_alpha_beta(self, name, fraction=1.0):
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]
Expand Down
10 changes: 7 additions & 3 deletions openmc/model/model.py
@@ -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

Expand Down Expand Up @@ -528,8 +529,11 @@ def run(self, particles=None, threads=None, geometry_debug=False,
"""

# 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
Expand Down
15 changes: 7 additions & 8 deletions src/source.cpp
Expand Up @@ -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)
{
Expand Down Expand Up @@ -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}};
}
}
}
Expand Down Expand Up @@ -238,7 +237,7 @@ SourceSite IndependentSource::sample(uint64_t* seed) const

// Sample particle creation time
site.time = time_->sample(seed);

return site;
}

Expand Down
14 changes: 5 additions & 9 deletions tests/unit_tests/test_deplete_operator.py
Expand Up @@ -10,7 +10,7 @@

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"
Expand All @@ -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):
Expand All @@ -54,14 +54,10 @@ def write_bos_data():
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)
Expand Down