Merge pull request #1247 from drewejohnson/dep-chain-file

Read depletion_chain information from main cross section file
This commit is contained in:
Paul Romano 2019-06-14 14:43:09 -05:00 committed by GitHub
commit ca608653e3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 159 additions and 20 deletions

View file

@ -1,5 +1,6 @@
import os
import xml.etree.ElementTree as ET
import pathlib
import h5py
@ -48,19 +49,30 @@ class DataLibrary(EqualityMixin):
Parameters
----------
filename : str
filename : str or Path
Path to the file to be registered.
If an ``xml`` file, treat as the depletion chain file without
materials.
"""
# Support pathlib
# TODO: Remove when support is Python 3.6+ only
filename = str(filename)
if not isinstance(filename, pathlib.Path):
path = pathlib.Path(filename)
else:
path = filename
with h5py.File(filename, 'r') as h5file:
filetype = h5file.attrs['filetype'].decode()[5:]
materials = list(h5file)
if path.suffix == '.xml':
filetype = 'depletion_chain'
materials = []
elif path.suffix == '.h5':
with h5py.File(path, 'r') as h5file:
filetype = h5file.attrs['filetype'].decode()[5:]
materials = list(h5file)
else:
raise ValueError(
"File type {} not supported by {}"
.format(path.name, self.__class__.__name__))
library = {'path': filename, 'type': filetype, 'materials': materials}
library = {'path': str(path), 'type': filetype, 'materials': materials}
self.libraries.append(library)
def export_to_xml(self, path='cross_sections.xml'):
@ -85,8 +97,11 @@ class DataLibrary(EqualityMixin):
dir_element.text = os.path.realpath(common_dir)
for library in self.libraries:
lib_element = ET.SubElement(root, "library")
lib_element.set('materials', ' '.join(library['materials']))
if library['type'] == "depletion_chain":
lib_element = ET.SubElement(root, "depletion_chain")
else:
lib_element = ET.SubElement(root, "library")
lib_element.set('materials', ' '.join(library['materials']))
lib_element.set('path', os.path.relpath(library['path'], common_dir))
lib_element.set('type', library['type'])
@ -106,7 +121,7 @@ class DataLibrary(EqualityMixin):
----------
path : str, optional
Path to XML file to read. If not provided, the
`OPENMC_CROSS_SECTIONS` environment variable will be used.
:envvar:`OPENMC_CROSS_SECTIONS` environment variable will be used.
Returns
-------
@ -146,4 +161,13 @@ class DataLibrary(EqualityMixin):
'materials': materials}
data.libraries.append(library)
# get depletion chain data
dep_node = root.find("depletion_chain")
if dep_node is not None:
filename = os.path.join(directory, dep_node.attrib['path'])
library = {'path': filename, 'type': 'depletion_chain',
'materials': []}
data.libraries.append(library)
return data

View file

@ -8,7 +8,10 @@ from collections import namedtuple
import os
from pathlib import Path
from abc import ABCMeta, abstractmethod
from xml.etree import ElementTree as ET
from warnings import warn
from openmc.data import DataLibrary
from .chain import Chain
OperatorResult = namedtuple('OperatorResult', ['k', 'rates'])
@ -43,8 +46,9 @@ class TransportOperator(metaclass=ABCMeta):
Parameters
----------
chain_file : str, optional
Path to the depletion chain XML file. Defaults to the
:envvar:`OPENMC_DEPLETE_CHAIN` environment variable if it exists.
Path to the depletion chain XML file. Defaults to the file
listed under ``depletion_chain`` in
:envvar:`OPENMC_CROSS_SECTIONS` environment variable.
Attributes
----------
@ -62,8 +66,21 @@ class TransportOperator(metaclass=ABCMeta):
if chain_file is None:
chain_file = os.environ.get("OPENMC_DEPLETE_CHAIN", None)
if chain_file is None:
raise IOError("No chain specified, either manually or in "
"environment variable OPENMC_DEPLETE_CHAIN.")
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)
@abstractmethod

View file

@ -107,7 +107,8 @@ class Chain(object):
yield sublibrary files. The depletion chain used during a depletion
simulation is indicated by either an argument to
:class:`openmc.deplete.Operator` or through the
:envvar:`OPENMC_DEPLETE_CHAIN` environment variable.
``depletion_chain`` item in the :envvar:`OPENMC_CROSS_SECTIONS`
environment variable.
Attributes
----------

View file

@ -64,8 +64,9 @@ class Operator(TransportOperator):
settings : openmc.Settings
OpenMC Settings object
chain_file : str, optional
Path to the depletion chain XML file. Defaults to the
:envvar:`OPENMC_DEPLETE_CHAIN` environment variable if it exists.
Path to the depletion chain XML file. Defaults to the file
listed under ``depletion_chain`` in
:envvar:`OPENMC_CROSS_SECTIONS` environment variable.
prev_results : ResultsList, optional
Results from a previous depletion calculation. If this argument is
specified, the depletion calculation will start from the latest state

View file

@ -133,8 +133,9 @@ class Model(object):
Array of timesteps in units of [s]. Note that values are not
cumulative.
chain_file : str, optional
Path to the depletion chain XML file. Defaults to the
:envvar:`OPENMC_DEPLETE_CHAIN` environment variable if it exists.
Path to the depletion chain XML file. Defaults to the chain
found under the ``depletion_chain`` in the
:envvar:`OPENMC_CROSS_SECTIONS` environment variable if it exists.
method : str
Integration method used for depletion (e.g., 'cecm', 'predictor')
**kwargs

View file

@ -2,6 +2,7 @@
from collections.abc import Mapping
import os
from pathlib import Path
import numpy as np
import pytest
@ -33,6 +34,30 @@ def test_data_library(tmpdir):
assert new_lib.libraries[-1]['type'] == 'thermal'
def test_depletion_chain_data_library(run_in_tmpdir):
dep_lib = openmc.data.DataLibrary.from_xml()
prev_len = len(dep_lib.libraries)
chain_path = Path(__file__).parents[1] / "chain_simple.xml"
dep_lib.register_file(chain_path)
assert len(dep_lib.libraries) == prev_len + 1
# Inspect
dep_dict = dep_lib.libraries[-1]
assert dep_dict['materials'] == []
assert dep_dict['type'] == 'depletion_chain'
assert dep_dict['path'] == str(chain_path)
out_path = "cross_section_chain.xml"
dep_lib.export_to_xml(out_path)
dep_import = openmc.data.DataLibrary.from_xml(out_path)
for lib in reversed(dep_import.libraries):
if lib['type'] == 'depletion_chain':
break
else:
raise IndexError("depletion_chain not found in exported DataLibrary")
assert os.path.exists(lib['path'])
def test_linearize():
"""Test linearization of a continuous function."""
x, y = openmc.data.linearize([-1., 1.], lambda x: 1 - x*x)

View file

@ -0,0 +1,70 @@
"""Basic unit tests for openmc.deplete.Operator instantiation
Modifies and resets environment variable OPENMC_CROSS_SECTIONS
to a custom file with new depletion_chain node
"""
from os import environ
from unittest import mock
from pathlib import Path
import pytest
from openmc.deplete.abc import TransportOperator
from openmc.deplete.chain import Chain
BARE_XS_FILE = "bare_cross_sections.xml"
CHAIN_PATH = Path(__file__).parents[1] / "chain_simple.xml"
@pytest.fixture()
def bare_xs(run_in_tmpdir):
"""Create a very basic cross_sections file, return simple Chain.
"""
bare_xs_contents = """<?xml version="1.0"?>
<cross_sections>
<depletion_chain path="{}" />
</cross_sections>
""".format(CHAIN_PATH)
with open(BARE_XS_FILE, "w") as out:
out.write(bare_xs_contents)
yield
class BareDepleteOperator(TransportOperator):
"""Very basic class for testing the initialization."""
# declare abstract methods so object can be created
def __call__(self, *args, **kwargs):
pass
def initial_condition(self):
pass
def get_results_info(self):
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)
act_chain = bare_op.chain
ref_chain = Chain.from_xml(CHAIN_PATH)
assert len(act_chain) == len(ref_chain)
for name in ref_chain.nuclide_dict:
# compare openmc.deplete.Nuclide objects
ref_nuc = ref_chain[name]
act_nuc = act_chain[name]
for prop in [
'name', 'half_life', 'decay_energy', 'reactions',
'decay_modes', 'yield_data', 'yield_energies',
]:
assert getattr(act_nuc, prop) == getattr(ref_nuc, prop), prop