diff --git a/docs/source/usersguide/cross_sections.rst b/docs/source/usersguide/data.rst similarity index 64% rename from docs/source/usersguide/cross_sections.rst rename to docs/source/usersguide/data.rst index 0cbb581bd..48003ecb7 100644 --- a/docs/source/usersguide/cross_sections.rst +++ b/docs/source/usersguide/data.rst @@ -1,55 +1,73 @@ -.. _usersguide_cross_sections: +.. _usersguide_data: -=========================== -Cross Section Configuration -=========================== +================== +Data Configuration +================== -In order to run a simulation with OpenMC, you will need cross section data for -each nuclide or material in your problem. OpenMC can be run in continuous-energy -or multi-group mode. +OpenMC relies on a variety of physical data in order to carry out transport +simulations, depletion simulations, and other common tasks. As a user, you are +responsible for specifying one or more of the following: -In continuous-energy mode, OpenMC uses a native `HDF5 -`_ format (see :ref:`io_nuclear_data`) to -store all nuclear data. Pregenerated HDF5 libraries can be found at -https://openmc.org; unless you have specific data needs, it is highly -recommended to use one of the pregenerated libraries. Alternatively, if you have -ACE format data that was produced with NJOY_, such as that distributed with -MCNP_ or Serpent_, it can be converted to the HDF5 format using the :ref:`using -the Python API `. Several sources provide openly available -ACE data including the `ENDF/B`_, JEFF_, and TENDL_ libraries as well as the -`LANL Nuclear Data Team `_. In addition to -tabulated cross sections in the HDF5 files, OpenMC relies on :ref:`windowed -multipole ` data to perform on-the-fly Doppler broadening. +- **Cross sections (XML)** -- A :ref:`cross sections XML ` + file (commonly named ``cross_sections.xml``) contains a listing of other data + files, in particular neutron cross sections, photon cross sections, and + windowed multipole data. Each of those files, in turn, uses a `HDF5 + `_ format (see :ref:`io_nuclear_data`). In + order to run transport simulations with continuous-energy cross sections, you + need to specify this file. -In multi-group mode, OpenMC utilizes an HDF5-based library format which can be -used to describe nuclide- or material-specific quantities. +- **Depletion chain (XML)** -- A :ref:`depletion chain XML ` + file contains decay data, fission product yields, and information on what + neutron reactions can result in transmutation. This file is needed for + depletion/activation calculations as well as some basic functions in the + :mod:`openmc.data` module. + +- **Multigroup cross sections (HDF5)** -- OpenMC can also perform transport + simulations using multigroup data. In this case, multigroup cross sections are + stored in a single :ref:`HDF5 file `. Thus, in order to run a + multigroup transport simulation, this file needs to be specified. + +Each of the above files can specified in several ways. In the Python API, a +:ref:`runtime configuration variable ` +:data:`openmc.config` can be used to specify any of the above and is initialized +using a set of environment variables. + +.. _usersguide_data_runtime: --------------------- -Environment Variables +Runtime Configuration --------------------- -When :ref:`scripts_openmc` is run, it will look for several environment -variables that indicate where cross sections can be found. While the location of -cross sections can also be indicated through the -:attr:`openmc.Materials.cross_sections` attribute (or in the :ref:`materials.xml -` file), if you always use the same set of cross section data, it -is often easier to just set an environment variable that will be picked up by -default every time OpenMC is run. The following environment variables are used: +Data sources for OpenMC can be specified at runtime in Python using the +:data:`openmc.config` variable. This variable acts like a dictionary and stores +key-values pairs, where the values are file paths (strings or path-like objects) +and the key can be one of the following: -:envvar:`OPENMC_CROSS_SECTIONS` - Indicates the path to the :ref:`cross_sections.xml ` - summary file that is used to locate HDF5 format cross section libraries if the - user has not specified :attr:`openmc.Materials.cross_sections` (equivalently, - the :ref:`cross_sections` in :ref:`materials.xml `). +``"cross_sections"`` + Indicates the path to the :ref:`cross sections XML ` file + that lists HDF5 format neutron cross sections, photon cross sections, and + windowed multipole data. At startup, this is initialized with the value of the + :envvar:`OPENMC_CROSS_SECTIONS` environment variable. Note that the + :attr:`openmc.Materials.cross_sections` attribute will override this, if + specified. -:envvar:`OPENMC_MG_CROSS_SECTIONS` +``"chain_file"`` + Indicates the path to the :ref:`depletion chain XML ` file + that contains decay data, fission product yields, and what neutron reactions + may result in transmutation of a target nuclide. At startup, this is + initialized with the value of the :envvar:`OPENMC_CHAIN_FILE` environment + variable. + +``"mg_cross_sections"`` Indicates the path to an :ref:`HDF5 file ` that contains - multi-group cross sections if the user has not specified - :attr:`openmc.Materials.cross_sections` (equivalently, the - :ref:`cross_sections` in :ref:`materials.xml `). + multigroup cross sections. At startup, this is initialized with the value of + the :envvar:`OPENMC_MG_CROSS_SECTIONS` environment variable. Note that the + :attr:`openmc.Materials.cross_sections` attribute will override this if + specified. -To set these environment variables persistently, export them from your shell -profile (``.profile`` or ``.bashrc`` in bash_). +If you want to persistently set the environment variables used to initialized +the configuration, export them from your shell profile (``.profile`` or +``.bashrc`` in bash_). .. _bash: http://www.linuxfromscratch.org/blfs/view/6.3/postlfs/profile.html @@ -61,12 +79,13 @@ Using Pregenerated Libraries ---------------------------- Various evaluated nuclear data libraries have been processed into the HDF5 -format required by OpenMC and can be found at https://openmc.org. You -can find both libraries generated by the OpenMC development team as well as -libraries based on ACE files distributed elsewhere. To use these libraries, -download the archive file, unpack it, and then set your -:envvar:`OPENMC_CROSS_SECTIONS` environment variable to the absolute path of -the ``cross_sections.xml`` file contained in the unpacked directory. +format required by OpenMC and can be found at https://openmc.org. Unless you +have specific data needs, it is highly recommended to use one of the +pregenerated libraries. You can find both libraries generated by the OpenMC +development team as well as libraries based on ACE files distributed elsewhere. +To use these libraries, download the archive file, unpack it, and then specify +the path of the ``cross_sections.xml`` file contained in the unpacked directory +as described in :ref:`usersguide_data_runtime`. .. _create_xs_library: @@ -75,6 +94,12 @@ Manually Creating a Library from ACE files .. currentmodule:: openmc.data +If you have ACE format data that was produced with NJOY_, such as that +distributed with MCNP_ or Serpent_, it can be converted to the HDF5 format using +the using the Python API. Several sources provide openly available ACE data +including the `ENDF/B`_, JEFF_, and TENDL_ libraries as well as the `LANL +Nuclear Data Team `_. + The :mod:`openmc.data` module in the Python API enables users to directly convert ACE data to OpenMC's HDF5 format and create a corresponding :ref:`cross_sections.xml ` file. For those who prefer to use @@ -224,6 +249,19 @@ relaxation sublibrary files are required: Once the HDF5 files have been generated, a library can be created using the :class:`DataLibrary` class as described in :ref:`create_xs_library`. +----------- +Chain Files +----------- + +Pregenerated depletion chain XML files can be found at https://openmc.org. +Additionally, depletion chains can be generated using the +:class:`openmc.deplete.Chain` class. In particular, the +:meth:`~openmc.deplete.Chain.from_endf` method allows a chain to be generated +starting from a set of ENDF incident neutron, decay, and fission product yield +sublibrary files. Once you've downloaded or generated a depletion chain XML +file, make sure to specify its path as described in +:ref:`usersguide_data_runtime`. + ----------------------- Windowed Multipole Data ----------------------- @@ -241,18 +279,16 @@ The `official ENDF/B-VII.1 HDF5 library multipole library, so if you are using this library, the windowed multipole data will already be available to you. --------------------------- -Multi-Group Cross Sections --------------------------- +------------------------- +Multigroup Cross Sections +------------------------- -Multi-group cross section libraries are generally tailored to the specific +Multigroup cross section libraries are generally tailored to the specific calculation to be performed. Therefore, at this point in time, OpenMC is not -distributed with any pre-existing multi-group cross section libraries. -However, if obtained or generated their own library, the user -should set the :envvar:`OPENMC_MG_CROSS_SECTIONS` environment variable -to the absolute path of the file library expected to used most frequently. - -For an example of how to create a multi-group library, see the `example notebook +distributed with any pre-existing multigroup cross section libraries. However, +if a multigroup library file is downloaded or generated, the path to the file +needs to be specified as described in :ref:`usersguide_data_runtime`. For an +example of how to create a multigroup library, see the `example notebook <../examples/mg-mode-part-i.ipynb>`__. .. _NJOY: http://www.njoy21.io/ diff --git a/docs/source/usersguide/index.rst b/docs/source/usersguide/index.rst index fab353c77..511bdc6ce 100644 --- a/docs/source/usersguide/index.rst +++ b/docs/source/usersguide/index.rst @@ -13,7 +13,7 @@ essential aspects of using OpenMC to perform simulations. beginners install - cross_sections + data basics materials geometry diff --git a/openmc/__init__.py b/openmc/__init__.py index abcac899f..214695e67 100644 --- a/openmc/__init__.py +++ b/openmc/__init__.py @@ -32,6 +32,7 @@ from openmc.search import * from openmc.polynomial import * from openmc.tracks import * from . import examples +from .config import * # Import a few names from the model module from openmc.model import rectangular_prism, hexagonal_prism, Model diff --git a/openmc/config.py b/openmc/config.py new file mode 100644 index 000000000..628df7edc --- /dev/null +++ b/openmc/config.py @@ -0,0 +1,83 @@ +from collections.abc import MutableMapping +import os +from pathlib import Path +import warnings + +from openmc.data import DataLibrary + +__all__ = ["config"] + + +class _Config(MutableMapping): + def __init__(self, data=()): + self._mapping = {} + self.update(data) + + def __getitem__(self, key): + return self._mapping[key] + + def __delitem__(self, key): + del self._mapping[key] + if key == 'cross_sections': + del os.environ['OPENMC_CROSS_SECTIONS'] + elif key == 'mg_cross_sections': + del os.environ['OPENMC_MG_CROSS_SECTIONS'] + + def __setitem__(self, key, value): + if key == 'cross_sections': + # Force environment variable to match + self._set_path(key, value) + os.environ['OPENMC_CROSS_SECTIONS'] = str(value) + elif key == 'mg_cross_sections': + self._set_path(key, value) + os.environ['OPENMC_MG_CROSS_SECTIONS'] = str(value) + elif key == 'chain_file': + self._set_path(key, value) + os.environ['OPENMC_CHAIN_FILE'] = str(value) + else: + raise KeyError(f'Unrecognized config key: {key}') + + def __iter__(self): + return iter(self._mapping) + + def __len__(self): + return len(self._mapping) + + def __repr__(self): + return repr(self._mapping) + + def _set_path(self, key, value): + self._mapping[key] = p = Path(value) + if not p.exists(): + warnings.warn(f"'{value}' does not exist.") + + +def _default_config(): + """Return default configuration""" + config = _Config() + + # Set cross sections using environment variable + if "OPENMC_CROSS_SECTIONS" in os.environ: + config['cross_sections'] = os.environ["OPENMC_CROSS_SECTIONS"] + if "OPENMC_MG_CROSS_SECTIONS" in os.environ: + config['mg_cross_sections'] = os.environ["OPENMC_MG_CROSS_SECTIONS"] + + # Set depletion chain + chain_file = os.environ.get("OPENMC_CHAIN_FILE") + if (chain_file is None and + config.get('cross_sections') is not None and + config['cross_sections'].exists() + ): + # Check for depletion chain in cross_sections.xml + data = DataLibrary.from_xml(config['cross_sections']) + for lib in reversed(data.libraries): + if lib['type'] == 'depletion_chain': + chain_file = lib['path'] + break + if chain_file is not None: + config['chain_file'] = chain_file + + return config + + +config = _default_config() diff --git a/openmc/data/library.py b/openmc/data/library.py index bf937e57a..ad6c65fb6 100644 --- a/openmc/data/library.py +++ b/openmc/data/library.py @@ -4,6 +4,7 @@ import pathlib import h5py +import openmc from openmc.mixin import EqualityMixin from openmc._xml import clean_indentation, reorder_attributes @@ -124,8 +125,8 @@ class DataLibrary(EqualityMixin): Parameters ---------- path : str, optional - Path to XML file to read. If not provided, the - :envvar:`OPENMC_CROSS_SECTIONS` environment variable will be used. + Path to XML file to read. If not provided, + openmc.config['cross_sections'] will be used. Returns ------- @@ -136,15 +137,14 @@ class DataLibrary(EqualityMixin): data = cls() - # If path is None, get the cross sections from the - # OPENMC_CROSS_SECTIONS environment variable + # If path is None, get the cross sections from the global configuration if path is None: - path = os.environ.get('OPENMC_CROSS_SECTIONS') + path = openmc.config.get('cross_sections') - # Check to make sure there was an environmental variable. + # Check to make sure we picked up cross sections if path is None: - raise ValueError("Either path or OPENMC_CROSS_SECTIONS " - "environmental variable must be set") + raise ValueError("Either path or openmc.config['cross_sections'] " + "must be set") tree = ET.parse(path) root = tree.getroot() @@ -162,7 +162,6 @@ class DataLibrary(EqualityMixin): 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']) diff --git a/openmc/deplete/chain.py b/openmc/deplete/chain.py index d63bc6bfb..217c9cac0 100644 --- a/openmc/deplete/chain.py +++ b/openmc/deplete/chain.py @@ -239,24 +239,6 @@ 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. @@ -265,9 +247,8 @@ class Chain: yield sublibrary files. The depletion chain used during a depletion simulation is indicated by either an argument to :class:`openmc.deplete.CoupledOperator` or - :class:`openmc.deplete.IndependentOperator`, or through the - ``depletion_chain`` item in the :envvar:`OPENMC_CROSS_SECTIONS` - environment variable. + :class:`openmc.deplete.IndependentOperator`, or through + openmc.config['chain_file']. Attributes ---------- diff --git a/openmc/deplete/coupled_operator.py b/openmc/deplete/coupled_operator.py index dd14f282f..a8c1fa156 100644 --- a/openmc/deplete/coupled_operator.py +++ b/openmc/deplete/coupled_operator.py @@ -9,7 +9,6 @@ filesystem. """ import copy -import os from warnings import warn import numpy as np @@ -22,7 +21,6 @@ from openmc.exceptions import DataError import openmc.lib from openmc.mpi import comm from .abc import OperatorResult -from .chain import _find_chain_file from .openmc_operator import OpenMCOperator, _distribute from .results import Results from .helpers import ( @@ -48,11 +46,11 @@ def _find_cross_sections(model): return model.materials.cross_sections # otherwise fallback to environment variable - cross_sections = os.environ.get("OPENMC_CROSS_SECTIONS") + cross_sections = openmc.config.get("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." + "openmc.config['cross_sections'] is not set." ) return cross_sections @@ -103,9 +101,8 @@ class CoupledOperator(OpenMCOperator): model : openmc.model.Model OpenMC model object 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. + Path to the depletion chain XML file. Defaults to + ``openmc.config['chain_file']``. prev_results : Results, optional Results from a previous depletion calculation. If this argument is specified, the depletion calculation will start from the latest state @@ -231,10 +228,8 @@ class CoupledOperator(OpenMCOperator): " model with which to generate the transport Operator." raise TypeError(msg) - # Determine cross sections / depletion chain + # Determine cross sections 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()) diff --git a/openmc/deplete/independent_operator.py b/openmc/deplete/independent_operator.py index 7f0bf5fb2..f628a7db1 100644 --- a/openmc/deplete/independent_operator.py +++ b/openmc/deplete/independent_operator.py @@ -40,7 +40,8 @@ class IndependentOperator(OpenMCOperator): micro_xs : MicroXS One-group microscopic cross sections in [b] . chain_file : str - Path to the depletion chain XML file. + Path to the depletion chain XML file. Defaults to + ``openmc.config['chain_file']``. keff : 2-tuple of float, optional keff eigenvalue and uncertainty from transport calculation. Default is None. @@ -111,7 +112,7 @@ class IndependentOperator(OpenMCOperator): def __init__(self, materials, micro_xs, - chain_file, + chain_file=None, keff=None, normalization_mode='fission-q', fission_q=None, diff --git a/openmc/deplete/microxs.py b/openmc/deplete/microxs.py index 72d87d3b7..7f3a84bf6 100644 --- a/openmc/deplete/microxs.py +++ b/openmc/deplete/microxs.py @@ -5,18 +5,16 @@ nuclide names as row indices and reaction names as column indices. """ import tempfile -from pathlib import Path from copy import deepcopy -from pandas import DataFrame, read_csv, concat +from pandas import DataFrame, read_csv import numpy as np from openmc.checkvalue import check_type, check_value, check_iterable_type +from openmc.exceptions import DataError from openmc.mgxs import EnergyGroups, ArbitraryXS, FissionXS -from openmc.data import DataLibrary -from openmc import Tallies, StatePoint, Materials, Material - - +from openmc import Tallies, StatePoint, Materials +import openmc from .chain import Chain, REACTIONS from .coupled_operator import _find_cross_sections, _get_nuclides_with_data @@ -35,7 +33,7 @@ class MicroXS(DataFrame): def from_model(cls, model, reaction_domain, - chain_file, + chain_file=None, dilute_initial=1.0e3, energy_bounds=(0, 20e6), run_kwargs=None): @@ -48,11 +46,12 @@ class MicroXS(DataFrame): OpenMC model object. Must contain geometry, materials, and settings. reaction_domain : openmc.Material or openmc.Cell or openmc.Universe or openmc.RegularMesh Domain in which to tally reaction rates. - chain_file : str + chain_file : str, optional Path to the depletion chain XML file that will be used in depletion simulation. Used to determine cross sections for materials not - present in the inital composition. - dilute_initial : float + present in the inital composition. Defaults to + ``openmc.config['chain_file']``. + dilute_initial : float, optional Initial atom density [atoms/cm^3] to add for nuclides that are zero in initial condition to ensure they exist in the cross section data. Only done for nuclides with reaction rates. @@ -144,6 +143,13 @@ class MicroXS(DataFrame): :class:`openmc.Materials` object with nuclides added to burnable materials. """ + if chain_file is None: + chain_file = openmc.config.get('chain_file') + if chain_file is None: + raise DataError( + "No depletion chain specified and could not find depletion " + "chain in openmc.config['chain_file']" + ) chain = Chain.from_xml(chain_file) reactions = chain.reactions cross_sections = _find_cross_sections(model) diff --git a/openmc/deplete/openmc_operator.py b/openmc/deplete/openmc_operator.py index 76f8e964e..ca1516803 100644 --- a/openmc/deplete/openmc_operator.py +++ b/openmc/deplete/openmc_operator.py @@ -11,6 +11,7 @@ from collections import OrderedDict import numpy as np import openmc +from openmc.exceptions import DataError from openmc.mpi import comm from .abc import TransportOperator, OperatorResult from .atom_number import AtomNumber @@ -57,9 +58,8 @@ class OpenMCOperator(TransportOperator): Path to continuous energy cross section library, or object containing one-group cross-sections. 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. + Path to the depletion chain XML file. Defaults to + openmc.config['chain_file']. prev_results : Results, optional Results from a previous depletion calculation. If this argument is specified, the depletion calculation will start from the latest state @@ -83,7 +83,6 @@ class OpenMCOperator(TransportOperator): if ``reduce_chain`` evaluates to true. The default value of ``None`` implies no limit on the depth. - Attributes ---------- materials : openmc.Materials @@ -133,6 +132,15 @@ class OpenMCOperator(TransportOperator): reduce_chain=False, reduce_chain_level=None): + # If chain file was not specified, try to get it from global config + if chain_file is None: + chain_file = openmc.config.get('chain_file') + if chain_file is None: + raise DataError( + "No depletion chain specified and could not find depletion " + "chain in openmc.config['chain_file']" + ) + super().__init__(chain_file, fission_q, dilute_initial, prev_results) self.round_number = False self.materials = materials diff --git a/openmc/deplete/results.py b/openmc/deplete/results.py index 15a65e98a..b2c8e7733 100644 --- a/openmc/deplete/results.py +++ b/openmc/deplete/results.py @@ -392,7 +392,7 @@ class Results(list): as such cannot be used in subsequent transport calculations. If not provided, nuclides from the cross_sections element of materials.xml will be used. If that element is not present, - nuclides from OPENMC_CROSS_SECTIONS will be used. + nuclides from openmc.config['cross_sections'] will be used. Returns ------- @@ -412,7 +412,7 @@ class Results(list): # the new materials XML file. The precedence of nuclides to select # is first ones provided as a kwarg here, then ones specified # in the materials.xml file if provided, then finally from - # the environment variable OPENMC_CROSS_SECTIONS. + # openmc.config['cross_sections']. if nuc_with_data: cv.check_iterable_type('nuclide names', nuc_with_data, str) available_cross_sections = nuc_with_data diff --git a/openmc/element.py b/openmc/element.py index 1473bd63e..49aeaf464 100644 --- a/openmc/element.py +++ b/openmc/element.py @@ -1,9 +1,9 @@ from collections import OrderedDict -import os import re from xml.etree import ElementTree as ET import openmc.checkvalue as cv +import openmc from openmc.data import NATURAL_ABUNDANCE, atomic_mass, \ isotopes as natural_isotopes @@ -40,10 +40,10 @@ class Element(str): cross_sections=None): """Expand natural element into its naturally-occurring isotopes. - An optional cross_sections argument or the :envvar:`OPENMC_CROSS_SECTIONS` - environment variable is used to specify a cross_sections.xml file. - If the cross_sections.xml file is found, the element is expanded only - into the isotopes/nuclides present in cross_sections.xml. If no + An optional cross_sections argument or the ``cross_sections`` + configuration value is used to specify a cross_sections.xml file. If the + cross_sections.xml file is found, the element is expanded only into the + isotopes/nuclides present in cross_sections.xml. If no cross_sections.xml file is found, the element is expanded based on its naturally occurring isotopes. @@ -54,12 +54,13 @@ class Element(str): percent_type : {'ao', 'wo'} 'ao' for atom percent and 'wo' for weight percent enrichment : float, optional - Enrichment of an enrichment_target nuclide in percent (ao or wo). - If enrichment_target is not supplied then it is enrichment for U235 - in weight percent. For example, input 4.95 for 4.95 weight percent + Enrichment of an enrichment_target nuclide in percent (ao or wo). If + enrichment_target is not supplied then it is enrichment for U235 in + weight percent. For example, input 4.95 for 4.95 weight percent enriched U. Default is None (natural composition). enrichment_target: str, optional - Single nuclide name to enrich from a natural composition (e.g., 'O16') + Single nuclide name to enrich from a natural composition (e.g., + 'O16') .. versionadded:: 0.12 enrichment_type: {'ao', 'wo'}, optional @@ -82,8 +83,8 @@ class Element(str): ValueError No data is available for any of natural isotopes of the element ValueError - If only some natural isotopes are available in the cross-section data - library and the element is not O, W, or Ta + If only some natural isotopes are available in the cross-section + data library and the element is not O, W, or Ta ValueError If a non-naturally-occurring isotope is requested ValueError @@ -101,8 +102,8 @@ class Element(str): `ORNL/CSD/TM-244 `_ is used to calculate the weight fractions of U234, U235, U236, and U238. Namely, the weight fraction of U234 and U236 are taken to be 0.89% and 0.46%, - respectively, of the U235 weight fraction. The remainder of the - isotopic weight is assigned to U238. + respectively, of the U235 weight fraction. The remainder of the isotopic + weight is assigned to U238. When the `enrichment` argument is specified with `enrichment_target`, a general enrichment procedure is used for elements composed of exactly @@ -125,10 +126,10 @@ class Element(str): # Create dict to store the expanded nuclides and abundances abundances = OrderedDict() - # If cross_sections is None, get the cross sections from the - # OPENMC_CROSS_SECTIONS environment variable + # If cross_sections is None, get the cross sections from the global + # configuration if cross_sections is None: - cross_sections = os.environ.get('OPENMC_CROSS_SECTIONS') + cross_sections = openmc.config.get('cross_sections') # If a cross_sections library is present, check natural nuclides # against the nuclides in the library diff --git a/openmc/material.py b/openmc/material.py index ece70df88..1b2b46a73 100644 --- a/openmc/material.py +++ b/openmc/material.py @@ -1310,9 +1310,8 @@ class Materials(cv.CheckedList): """Collection of Materials used for an OpenMC simulation. This class corresponds directly to the materials.xml input file. It can be - thought of as a normal Python list where each member is a - :class:`Material`. It behaves like a list as the following example - demonstrates: + thought of as a normal Python list where each member is a :class:`Material`. + It behaves like a list as the following example demonstrates: >>> fuel = openmc.Material() >>> clad = openmc.Material() @@ -1332,9 +1331,9 @@ class Materials(cv.CheckedList): Indicates the path to an XML cross section listing file (usually named cross_sections.xml). If it is not set, the :envvar:`OPENMC_CROSS_SECTIONS` environment variable will be used for - continuous-energy calculations and - :envvar:`OPENMC_MG_CROSS_SECTIONS` will be used for multi-group - calculations to find the path to the HDF5 cross section file. + continuous-energy calculations and :envvar:`OPENMC_MG_CROSS_SECTIONS` + will be used for multi-group calculations to find the path to the HDF5 + cross section file. """ diff --git a/openmc/mgxs_library.py b/openmc/mgxs_library.py index 67ad151cc..98a99196e 100644 --- a/openmc/mgxs_library.py +++ b/openmc/mgxs_library.py @@ -1,6 +1,5 @@ import copy from numbers import Real, Integral -import os import h5py import numpy as np @@ -2536,8 +2535,7 @@ class MGXSLibrary: ---------- filename : str, optional Name of HDF5 file containing MGXS data. Default is None. - If not provided, the value of the OPENMC_MG_CROSS_SECTIONS - environmental variable will be used + If not provided, openmc.config['mg_cross_sections'] will be used. Returns ------- @@ -2545,15 +2543,14 @@ class MGXSLibrary: Multi-group cross section data object. """ - # If filename is None, get the cross sections from the - # OPENMC_CROSS_SECTIONS environment variable + # If filename is None, get the cross sections from openmc.config if filename is None: - filename = os.environ.get('OPENMC_MG_CROSS_SECTIONS') + filename = openmc.config.get('mg_cross_sections') # Check to make sure there was an environmental variable. if filename is None: - raise ValueError("Either path or OPENMC_MG_CROSS_SECTIONS " - "environmental variable must be set") + raise ValueError("Either path or openmc.config['mg_cross_sections']" + "must be set") check_type('filename', filename, str) file = h5py.File(filename, 'r') diff --git a/tests/unit_tests/test_config.py b/tests/unit_tests/test_config.py new file mode 100644 index 000000000..1d3c0f173 --- /dev/null +++ b/tests/unit_tests/test_config.py @@ -0,0 +1,43 @@ +from collections.abc import Mapping +import os + +import openmc +import pytest + + +@pytest.fixture(autouse=True, scope='module') +def reset_config(): + config = dict(openmc.config) + try: + yield + finally: + openmc.config.clear() + openmc.config.update(config) + + +def test_config_basics(): + assert isinstance(openmc.config, Mapping) + for key, value in openmc.config.items(): + assert isinstance(key, str) + assert isinstance(value, os.PathLike) + + # Set and delete + openmc.config['cross_sections'] = '/path/to/cross_sections.xml' + del openmc.config['cross_sections'] + assert 'cross_sections' not in openmc.config + assert 'OPENMC_CROSS_SECTIONS' not in os.environ + + # Can't use any key + with pytest.raises(KeyError): + openmc.config['🐖'] = '/like/to/eat/bacon' + + +def test_config_set_envvar(): + openmc.config['cross_sections'] = '/path/to/cross_sections.xml' + assert os.environ['OPENMC_CROSS_SECTIONS'] == '/path/to/cross_sections.xml' + + openmc.config['mg_cross_sections'] = '/path/to/mg_cross_sections.h5' + assert os.environ['OPENMC_MG_CROSS_SECTIONS'] == '/path/to/mg_cross_sections.h5' + + openmc.config['chain_file'] = '/path/to/chain_file.xml' + assert os.environ['OPENMC_CHAIN_FILE'] == '/path/to/chain_file.xml' diff --git a/tests/unit_tests/test_deplete_operator.py b/tests/unit_tests/test_deplete_operator.py index 5fe8715ac..6ea89fc4a 100644 --- a/tests/unit_tests/test_deplete_operator.py +++ b/tests/unit_tests/test_deplete_operator.py @@ -1,37 +1,15 @@ """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 pathlib import Path -import pytest from openmc.deplete.abc import TransportOperator -from openmc.deplete.chain import Chain, _find_chain_file +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 = """ - - - -""".format(CHAIN_PATH) - - with open(BARE_XS_FILE, "w") as out: - out.write(bare_xs_contents) - - yield BARE_XS_FILE - - class BareDepleteOperator(TransportOperator): """Very basic class for testing the initialization.""" @@ -52,10 +30,10 @@ class BareDepleteOperator(TransportOperator): pass -def test_operator_init(bare_xs): +def test_operator_init(): """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)) + bare_op = BareDepleteOperator(CHAIN_PATH) act_chain = bare_op.chain ref_chain = Chain.from_xml(CHAIN_PATH) assert len(act_chain) == len(ref_chain) @@ -73,8 +51,7 @@ def test_operator_init(bare_xs): def test_operator_fiss_q(): """Make sure fission q values can be set""" new_q = {"U235": 2.0E8, "U238": 2.0E8, "U234": 5.0E7} - chain_file = Path(__file__).parents[1] / "chain_simple.xml" - operator = BareDepleteOperator(chain_file=chain_file, fission_q=new_q) + operator = BareDepleteOperator(chain_file=CHAIN_PATH, fission_q=new_q) mod_chain = operator.chain for name, q in new_q.items(): chain_nuc = mod_chain[name]