diff --git a/CMakeLists.txt b/CMakeLists.txt index 8d6e622829..381f0cd7b7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -120,6 +120,11 @@ target_include_directories(pugixml PUBLIC vendor/pugixml/) # xtensor header-only library #=============================================================================== +# CMake 3.13+ will complain about policy CMP0079 unless it is set explicitly +if (NOT (CMAKE_VERSION VERSION_LESS 3.13)) + cmake_policy(SET CMP0079 NEW) +endif() + add_subdirectory(vendor/xtl) add_subdirectory(vendor/xtensor) target_link_libraries(xtensor INTERFACE xtl) diff --git a/docs/source/_templates/myintegrator.rst b/docs/source/_templates/myintegrator.rst new file mode 100644 index 0000000000..803dfde067 --- /dev/null +++ b/docs/source/_templates/myintegrator.rst @@ -0,0 +1,9 @@ +{{ fullname }} +{{ underline }} + +.. currentmodule:: {{ module }} + +.. autoclass:: {{ objname }} + :members: + :inherited-members: + :special-members: __call__, __len__, __iter__ diff --git a/docs/source/developers.rst b/docs/source/developers.rst deleted file mode 100644 index 7d32b5831c..0000000000 --- a/docs/source/developers.rst +++ /dev/null @@ -1,28 +0,0 @@ -.. _developers: - -================ -Development Team -================ - -The following people have contributed to development of the OpenMC Monte Carlo -code: - -* `Paul Romano `_ -* `Bryan Herman `_ -* `Nick Horelik `_ -* `Adam Nelson `_ -* `Jon Walsh `_ -* `Sterling Harper `_ -* `Will Boyd `_ -* `Samuel Shaner `_ -* `Jingang Liang `_ -* `Colin Josey `_ -* `Amanda Lund `_ -* `Guillaume Giudicelli `_ -* `Isaac Meyer `_ -* `Patrick Shriwise `_ -* `Shikhar Kumar `_ -* `Andrew Davis `_ -* `Benoit Forget `_ -* `Kord Smith `_ -* `Andrew Siegel `_ diff --git a/docs/source/index.rst b/docs/source/index.rst index 5500890e2b..2ce5072d01 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -44,4 +44,3 @@ list `_. io_formats/index publications license - developers diff --git a/docs/source/license.rst b/docs/source/license.rst index c0728e1a8c..98f6655bc2 100644 --- a/docs/source/license.rst +++ b/docs/source/license.rst @@ -4,7 +4,7 @@ License Agreement ================= -Copyright © 2011-2018 Massachusetts Institute of Technology and OpenMC contributors +Copyright © 2011-2019 Massachusetts Institute of Technology and OpenMC contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/docs/source/publications.rst b/docs/source/publications.rst index d554fe5a14..f33cf79587 100644 --- a/docs/source/publications.rst +++ b/docs/source/publications.rst @@ -57,6 +57,9 @@ Benchmarking Coupling and Multi-physics -------------------------- +- Miriam A. Kreher, Benoit Forget, and Kord Smith, "Single-Batch Monte Carlo + Multiphysics Coupling," *Proc. M&C*, Portland, Oregon, Aug. 25-29 (2019). + - Ze-Long Zhao, Yongwei Yang, and Shuang Hong, "`Application of FLUKA and OpenMC in coupled physics calculation of target and subcritical reactor for ADS `_," *Nucl. Sci. Tech.*, **30**: 10 @@ -113,6 +116,10 @@ Coupling and Multi-physics Geometry and Visualization -------------------------- +- Sterling Harper, Paul Romano, Benoit Forget, and Kord Smith, "Efficient + dynamic threadsafe neighbor lists for Monte Carlo ray tracing," *Proc. M&C*, + Portland, Oregon, Aug. 25-29 (2019). + - Jin-Yang Li, Long Gu, Hu-Shan Xu, Nadezha Korepanova, Rui Yu, Yan-Lei Zhu, and Chang-Ping Qin, "`CAD modeling study on FLUKA and OpenMC for accelerator driven system simulation `_", @@ -303,6 +310,11 @@ Multigroup Cross Section Generation Doppler Broadening ------------------ +- Jonathan A. Walsh, Benoit Forget, Kord S. Smith, and Forrest B. Brown, + "`On-the-fly Doppler broadening of unresolved resonance region cross sections + `_," *Prog. Nucl. Energy*, + **101**, 444-460 (2017). + - Colin Josey, Pablo Ducru, Benoit Forget, and Kord Smith, "`Windowed multipole for cross section Doppler broadening `_," *J. Comput. Phys.*, **307**, @@ -313,6 +325,12 @@ Doppler Broadening via Probability Band Interpolation," *Proc. PHYSOR*, Sun Valley, Idaho, May 1-5, 2016. +- Jonathan A. Walsh, Benoit Forget, Kord S. Smith, Brian C. Kiedrowski, and + Forrest B. Brown, "`Direct, on-the-fly calculation of unresolved resonance + region cross sections in Monte Carlo simulations + `_," *Proc. Joint Int. Conf. M&C+SNA+MC*, + Nashville, Tennessee, Apr. 19--23 (2015). + - Colin Josey, Benoit Forget, and Kord Smith, "`Windowed multipole sensitivity to target accuracy of the optimization procedure `_," @@ -366,11 +384,6 @@ Nuclear Data `_", *Comput. Phys. Commun.*, **196**, 134-142 (2015). -- Jonathan A. Walsh, Benoit Forget, Kord S. Smith, Brian C. Kiedrowski, and - Forrest B. Brown, "Direct, on-the-fly calculation of unresolved resonance - region cross sections in Monte Carlo simulations," *Proc. Joint - Int. Conf. M&C+SNA+MC*, Nashville, Tennessee, Apr. 19--23 (2015). - - Amanda L. Lund, Andrew R. Siegel, Benoit Forget, Colin Josey, and Paul K. Romano, "Using fractional cascading to accelerate cross section lookups in Monte Carlo particle transport calculations," *Proc. Joint @@ -481,6 +494,10 @@ Parallelism Depletion --------- +- Jose L. Salcedo-Perez, Benoit Forget, Kord Smith, and Paul Romano, "Hybrid + tallies to improve performance in depletion Monte Carlo simulations," *Proc. + M&C*, Aug. 25-29 (2019). + - Zhao-Qing Liu, Ze-Long Zhao, Yong-Wei Yang, Yu-Cui Gao, Hai-Yan Meng, and Qing-Yu Gao, "`Development and validation of depletion code system IMPC-Burnup for ADS `_," *Nucl. Sci. Tech.*, diff --git a/docs/source/pythonapi/deplete.rst b/docs/source/pythonapi/deplete.rst index 0dcd47ef54..5b26eb38aa 100644 --- a/docs/source/pythonapi/deplete.rst +++ b/docs/source/pythonapi/deplete.rst @@ -6,26 +6,26 @@ .. module:: openmc.deplete -Several functions are provided that implement different time-integration +Several classes are provided that implement different time-integration algorithms for depletion calculations, which are described in detail in Colin Josey's thesis, `Development and analysis of high order neutron transport-depletion coupling algorithms `_. .. autosummary:: - :toctree: generated - :nosignatures: - :template: myfunction.rst + :toctree: generated + :nosignatures: + :template: myintegrator.rst - integrator.predictor - integrator.cecm - integrator.celi - integrator.leqi - integrator.cf4 - integrator.epc_rk4 - integrator.si_celi - integrator.si_leqi + PredictorIntegrator + CECMIntegrator + CELIIntegrator + CF4Integrator + EPCRK4Integrator + LEQIIntegrator + SICELIIntegrator + SILEQIIntegrator -Each of these functions expects a "transport operator" to be passed. An operator +Each of these classes expects a "transport operator" to be passed. An operator specific to OpenMC is available using the following class: .. autosummary:: @@ -65,6 +65,8 @@ for a depletion chain: DecayTuple Nuclide ReactionTuple + FissionYieldDistribution + FissionYield The following classes are used during a depletion simulation and store auxiliary data, such as number densities and reaction rates for each material. @@ -75,13 +77,25 @@ data, such as number densities and reaction rates for each material. :template: myclass.rst AtomNumber - ChainFissionHelper - DirectReactionRateHelper OperatorResult ReactionRates Results ResultsList +The following classes are used to help the :class:`openmc.deplete.Operator` +compute quantities like effective fission yields, reaction rates, and +total system energy. + +.. autosummary:: + :toctree: generated + :nosignatures: + :template: myclass.rst + + helpers.AveragedFissionYieldHelper + helpers.ChainFissionHelper + helpers.ConstantFissionYieldHelper + helpers.DirectReactionRateHelper + helpers.FissionYieldCutoffHelper The following classes are abstract classes that can be used to extend the :mod:`openmc.deplete` capabilities: @@ -91,11 +105,24 @@ The following classes are abstract classes that can be used to extend the :nosignatures: :template: myclass.rst - ReactionRateHelper EnergyHelper + FissionYieldHelper + ReactionRateHelper + TalliedFissionYieldHelper TransportOperator -Each of the integrator functions also relies on a number of "helper" functions +Custom integrators can be developed by subclassing from the following abstract +base classes: + +.. autosummary:: + :toctree: generated + :nosignatures: + :template: myintegrator.rst + + Integrator + SIIntegrator + +Each of the integrator classes also relies on a number of "helper" functions as follows: .. autosummary:: @@ -103,5 +130,5 @@ as follows: :nosignatures: :template: myfunction.rst - integrator.CRAM16 - integrator.CRAM48 + cram.CRAM16 + cram.CRAM48 diff --git a/docs/source/usersguide/geometry.rst b/docs/source/usersguide/geometry.rst index 80e60bd34c..51daf7b5d1 100644 --- a/docs/source/usersguide/geometry.rst +++ b/docs/source/usersguide/geometry.rst @@ -25,7 +25,7 @@ surface is a locus of zeros of a function of Cartesian coordinates Defining a surface alone is not sufficient to specify a volume -- in order to define an actual volume, one must reference the *half-space* of a surface. A -surface half-space is the region whose points satisfy a positive of negative +surface half-space is the region whose points satisfy a positive or negative inequality of the surface equation. For example, for a sphere of radius one centered at the origin, the surface equation is :math:`f(x,y,z) = x^2 + y^2 + z^2 - 1 = 0`. Thus, we say that the negative half-space of the sphere, is diff --git a/docs/source/usersguide/scripts.rst b/docs/source/usersguide/scripts.rst index 76c7c2f222..388130947c 100644 --- a/docs/source/usersguide/scripts.rst +++ b/docs/source/usersguide/scripts.rst @@ -106,6 +106,37 @@ Compton profile data using an existing data library from `Geant4 `_. Note that OpenMC includes this data file by default so it should not be necessary in practice to generate it yourself. + +.. _scripts_depletion_chain: + +------------------------------- +``openmc-make-depletion-chain`` +------------------------------- + +This script generates a depletion chain file called ``chain_endfb71.xml`` +using ENDF/B-VII.1 nuclear data. If the :envvar:`OPENMC_ENDF_DATA` variable +is not set, and ``"neutron"``, ``"decay"``, ``"nfy"`` directories +do not exist, then ENDF/B-VII.1 data will be downloaded. + +.. _scripts_depletion_chain_casl: + +------------------------------------ +``openmc-make-depletion-chain-casl`` +------------------------------------ + +This script generates a depletion chain called ``chain_casl.xml`` +using ENDF/B-VII.1 nuclear data for a simplified chain. +The nuclides were chosen by CASL-ORIGEN, which can be found in +Appendix A of Kang Seog Kim, `"Specification for the VERA Depletion +Benchmark Suite" `_, +CASL-U-2015-1014-000, Rev. 0, ORNL/TM-2016/53, 2016. +``Te129`` has been added into this chain due to its link to +``I129`` production. + +If the :envvar:`OPENMC_ENDF_DATA` variable is not set, +and ``"neutron"``, ``"decay"``, ``"nfy"`` directories +to not exist, then ENDF/B-VII.1 data will be downloaded. + .. _scripts_stopping: ------------------------------- diff --git a/include/openmc/capi.h b/include/openmc/capi.h index ded41f738d..3d2e8f57bf 100644 --- a/include/openmc/capi.h +++ b/include/openmc/capi.h @@ -21,6 +21,10 @@ extern "C" { int openmc_cell_set_temperature(int32_t index, double T, const int32_t* instance); int openmc_energy_filter_get_bins(int32_t index, const double** energies, size_t* n); int openmc_energy_filter_set_bins(int32_t index, size_t n, const double* energies); + int openmc_energyfunc_filter_get_energy(int32_t index, size_t* n, const double** energy); + int openmc_energyfunc_filter_get_y(int32_t index, size_t* n, const double** y); + int openmc_energyfunc_filter_set_data(int32_t index, size_t n, + const double* energies, const double* y); int openmc_extend_cells(int32_t n, int32_t* index_start, int32_t* index_end); int openmc_extend_filters(int32_t n, int32_t* index_start, int32_t* index_end); int openmc_extend_materials(int32_t n, int32_t* index_start, int32_t* index_end); diff --git a/include/openmc/nuclide.h b/include/openmc/nuclide.h index 4be898a64e..a936ac0102 100644 --- a/include/openmc/nuclide.h +++ b/include/openmc/nuclide.h @@ -126,6 +126,12 @@ namespace data { extern std::array energy_min; extern std::array energy_max; +//! Minimum temperature in [K] that nuclide data is available at +extern double temperature_min; + +//! Maximum temperature in [K] that nuclide data is available at +extern double temperature_max; + extern std::vector> nuclides; extern std::unordered_map nuclide_map; diff --git a/include/openmc/tallies/filter_energyfunc.h b/include/openmc/tallies/filter_energyfunc.h index 9b8d839f8b..df82e659e7 100644 --- a/include/openmc/tallies/filter_energyfunc.h +++ b/include/openmc/tallies/filter_energyfunc.h @@ -40,6 +40,13 @@ public: std::string text_label(int bin) const override; + //---------------------------------------------------------------------------- + // Accessors + + const std::vector& energy() const { return energy_; } + const std::vector& y() const { return y_; } + void set_data(gsl::span energy, gsl::span y); + private: //---------------------------------------------------------------------------- // Data members diff --git a/man/man1/openmc.1 b/man/man1/openmc.1 index f574ea10ae..229b64fd1a 100644 --- a/man/man1/openmc.1 +++ b/man/man1/openmc.1 @@ -54,7 +54,7 @@ Indicates the default path to an HDF5 file that contains multi-group cross section libraries if the user has not specified the tag in .I materials.xml\fP. .SH LICENSE -Copyright \(co 2011-2018 Massachusetts Institute of Technology and OpenMC +Copyright \(co 2011-2019 Massachusetts Institute of Technology and OpenMC contributors. .PP Permission is hereby granted, free of charge, to any person obtaining a copy of diff --git a/openmc/capi/filter.py b/openmc/capi/filter.py index bda3c3178f..92818a2aa1 100644 --- a/openmc/capi/filter.py +++ b/openmc/capi/filter.py @@ -34,6 +34,18 @@ _dll.openmc_energy_filter_get_bins.errcheck = _error_handler _dll.openmc_energy_filter_set_bins.argtypes = [c_int32, c_size_t, POINTER(c_double)] _dll.openmc_energy_filter_set_bins.restype = c_int _dll.openmc_energy_filter_set_bins.errcheck = _error_handler +_dll.openmc_energyfunc_filter_set_data.restype = c_int +_dll.openmc_energyfunc_filter_set_data.errcheck = _error_handler +_dll.openmc_energyfunc_filter_set_data.argtypes = [ + c_int32, c_size_t, POINTER(c_double), POINTER(c_double)] +_dll.openmc_energyfunc_filter_get_energy.resttpe = c_int +_dll.openmc_energyfunc_filter_get_energy.errcheck = _error_handler +_dll.openmc_energyfunc_filter_get_energy.argtypes = [ + c_int32, POINTER(c_size_t), POINTER(POINTER(c_double))] +_dll.openmc_energyfunc_filter_get_y.resttpe = c_int +_dll.openmc_energyfunc_filter_get_y.errcheck = _error_handler +_dll.openmc_energyfunc_filter_get_y.argtypes = [ + c_int32, POINTER(c_size_t), POINTER(POINTER(c_double))] _dll.openmc_filter_get_id.argtypes = [c_int32, POINTER(c_int32)] _dll.openmc_filter_get_id.restype = c_int _dll.openmc_filter_get_id.errcheck = _error_handler @@ -201,6 +213,48 @@ class DistribcellFilter(Filter): class EnergyFunctionFilter(Filter): filter_type = 'energyfunction' + def __new__(cls, energy=None, y=None, uid=None, new=True, index=None): + return super().__new__(cls, uid=uid, new=new, index=index) + + def __init__(self, energy=None, y=None, uid=None, new=True, index=None): + if (energy is None) != (y is None): + raise AttributeError("Need both energy and y or neither") + super().__init__(uid, new, index) + if energy is not None: + self.set_data(energy, y) + + def set_data(self, energy, y): + """Set the interpolation information for the filter + + Parameters + ---------- + energy : numpy.ndarray + Independent variable for the interpolation + y : numpy.ndarray + Dependent variable for the interpolation + """ + energy_array = np.asarray(energy) + y_array = np.asarray(y) + energy_p = energy_array.ctypes.data_as(POINTER(c_double)) + y_p = y_array.ctypes.data_as(POINTER(c_double)) + + _dll.openmc_energyfunc_filter_set_data( + self._index, len(energy_array), energy_p, y_p) + + @property + def energy(self): + return self._get_attr(_dll.openmc_energyfunc_filter_get_energy) + + @property + def y(self): + return self._get_attr(_dll.openmc_energyfunc_filter_get_y) + + def _get_attr(self, cfunc): + array_p = POINTER(c_double)() + n = c_size_t() + cfunc(self._index, n, array_p) + return as_array(array_p, (n.value, )) + class LegendreFilter(Filter): filter_type = 'legendre' diff --git a/openmc/data/data.py b/openmc/data/data.py index b332344241..e813209f0a 100644 --- a/openmc/data/data.py +++ b/openmc/data/data.py @@ -358,7 +358,12 @@ def zam(name): symbol, A, state = _GND_NAME_RE.match(name).groups() except AttributeError: raise ValueError("'{}' does not appear to be a nuclide name in GND " - "format.".format(name)) + "format".format(name)) + + if symbol not in ATOMIC_NUMBER: + raise ValueError("'{}' is not a recognized element symbol" + .format(symbol)) + metastable = int(state[2:]) if state else 0 return (ATOMIC_NUMBER[symbol], int(A), metastable) diff --git a/openmc/data/neutron.py b/openmc/data/neutron.py index 9c73152d45..9024b932e4 100644 --- a/openmc/data/neutron.py +++ b/openmc/data/neutron.py @@ -1,11 +1,9 @@ -import sys from collections import OrderedDict -from collections.abc import Iterable, Mapping, MutableMapping +from collections.abc import Mapping, MutableMapping from io import StringIO from math import log10 from numbers import Integral, Real import os -import shutil import tempfile from warnings import warn @@ -15,7 +13,8 @@ import h5py from . import HDF5_VERSION, HDF5_VERSION_MAJOR from .ace import Library, Table, get_table, get_metadata from .data import ATOMIC_SYMBOL, K_BOLTZMANN, EV_PER_MEV -from .endf import Evaluation, SUM_RULES, get_head_record, get_tab1_record +from .endf import ( + Evaluation, SUM_RULES, get_head_record, get_tab1_record, get_evaluations) from .fission_energy import FissionEnergyRelease from .function import Tabulated1D, Sum, ResonancesWithBackground from .grid import linearize, thin @@ -453,7 +452,7 @@ class IncidentNeutron(EqualityMixin): if rx.redundant: photon_rx = any(p.particle == 'photon' for p in rx.products) keep_mts = (4, 16, 103, 104, 105, 106, 107, - 203, 204, 205, 206, 207, 301, 444) + 203, 204, 205, 206, 207, 301, 318, 444) if not (photon_rx or rx.mt in keep_mts): continue @@ -554,6 +553,20 @@ class IncidentNeutron(EqualityMixin): fer_group = group['fission_energy_release'] data.fission_energy = FissionEnergyRelease.from_hdf5(fer_group) + # Rebuild non-fission heating + total_heating = data.reactions.get(301) + fission_heating = data.reactions.get(318) + if total_heating is not None and fission_heating is not None: + non_fission_heating = Reaction(999) + non_fission_heating.redundant = True + for strT, total in total_heating.xs.items(): + fission = fission_heating.xs.get(strT) + if fission is None: + continue + non_fission_heating.xs[strT] = Tabulated1D( + total.x, total.y - fission(total.x)) + data.reactions[999] = non_fission_heating + return data @classmethod @@ -801,14 +814,14 @@ class IncidentNeutron(EqualityMixin): """ with tempfile.TemporaryDirectory() as tmpdir: # Run NJOY to create an ACE library - kwargs.setdefault('ace', os.path.join(tmpdir, 'ace')) - kwargs.setdefault('xsdir', os.path.join(tmpdir, 'xsdir')) - kwargs.setdefault('pendf', os.path.join(tmpdir, 'pendf')) + kwargs.setdefault("output_dir", tmpdir) + for key in ("acer", "pendf", "heatr", "broadr", "gaspr", "purr"): + kwargs.setdefault(key, os.path.join(kwargs["output_dir"], key)) kwargs['evaluation'] = evaluation make_ace(filename, temperatures, **kwargs) # Create instance from ACE tables within library - lib = Library(kwargs['ace']) + lib = Library(kwargs['acer']) data = cls.from_ace(lib.tables[0]) for table in lib.tables[1:]: data.add_temperature_from_ace(table) @@ -817,6 +830,29 @@ class IncidentNeutron(EqualityMixin): ev = evaluation if evaluation is not None else Evaluation(filename) if (1, 458) in ev.section: data.fission_energy = FissionEnergyRelease.from_endf(ev, data) + # Add 318 fission heating data from heatr + non_fission_heating = Reaction(999) + non_fission_heating.redundant = True + fission_heating = Reaction(318) + + heatr_evals = get_evaluations(kwargs["heatr"]) + for heatr in heatr_evals: + temp = "{}K".format(round(heatr.target["temperature"])) + f318 = StringIO(heatr.section[3, 318]) + get_head_record(f318) + _params, fission_kerma = get_tab1_record(f318) + fission_heating.xs[temp] = fission_kerma + total_heating_xs = data.reactions[301].xs.get(temp) + if total_heating_xs is None: + continue + non_fission_heating.xs[temp] = Tabulated1D( + fission_kerma.x, + total_heating_xs(fission_kerma.x) - fission_kerma.y, + breakpoints=fission_kerma.breakpoints, + interpolation=fission_kerma.interpolation) + + data.reactions[318] = fission_heating + data.reactions[999] = non_fission_heating # Add 0K elastic scattering cross section if '0K' not in data.energy: diff --git a/openmc/data/njoy.py b/openmc/data/njoy.py index c8d630851b..7993ec4004 100644 --- a/openmc/data/njoy.py +++ b/openmc/data/njoy.py @@ -1,11 +1,10 @@ -import argparse from collections import namedtuple from io import StringIO import os import shutil from subprocess import Popen, PIPE, STDOUT, CalledProcessError -import sys import tempfile +from pathlib import Path from . import endf @@ -215,11 +214,21 @@ def make_pendf(filename, pendf='pendf', error=0.001, stdout=False): heatr=False, purr=False, acer=False, stdout=stdout) -def make_ace(filename, temperatures=None, ace='ace', xsdir='xsdir', pendf=None, - error=0.001, broadr=True, heatr=True, gaspr=True, purr=True, - acer=True, evaluation=None, **kwargs): +def make_ace(filename, temperatures=None, acer=True, xsdir=None, + output_dir=None, pendf=False, error=0.001, broadr=True, + heatr=True, gaspr=True, purr=True, evaluation=None, + **kwargs): """Generate incident neutron ACE file from an ENDF file + File names can be passed to + ``[acer, xsdir, pendf, broadr, heatr, gaspr, purr]`` + to specify the exact output for the given module. + Otherwise, the files will be writen to the current directory + or directory specified by ``output_dir``. Default file + names mirror the variable names, e.g. ``heatr`` output + will be written to a file named ``heatr`` unless otherwise + specified. + Parameters ---------- filename : str @@ -227,24 +236,34 @@ def make_ace(filename, temperatures=None, ace='ace', xsdir='xsdir', pendf=None, temperatures : iterable of float, optional Temperatures in Kelvin to produce ACE files at. If omitted, data is produced at room temperature (293.6 K). - ace : str, optional - Path of ACE file to write + acer : bool or str, optional + Flag indicating if acer should be run. If a string is give, write the + resulting ``ace`` file to this location. Path of ACE file to write. + Defaults to ``"ace"`` xsdir : str, optional - Path of xsdir file to write + Path of xsdir file to write. Defaults to ``"xsdir"`` in the same + directory as ``acer`` + output_dir : str, optional + Directory to write output for requested modules. If not provided + and at least one of ``[pendf, broadr, heatr, gaspr, purr, acer]`` + is ``True``, then write output files to current directory. If given, + must be a path to a directory. pendf : str, optional Path of pendf file to write. If omitted, the pendf file is not saved. error : float, optional Fractional error tolerance for NJOY processing - broadr : bool, optional - Indicating whether to Doppler broaden XS when running NJOY - heatr : bool, optional - Indicating whether to add heating kerma when running NJOY - gaspr : bool, optional - Indicating whether to add gas production data when running NJOY - purr : bool, optional - Indicating whether to add probability table when running NJOY - acer : bool, optional - Indicating whether to generate ACE file when running NJOY + broadr : bool or str, optional + Indicating whether to Doppler broaden XS when running NJOY. If string, + write the output tape to this file. + heatr : bool or str, optional + Indicating whether to add heating kerma when running NJOY. If string, + write the output tape to this file. + gaspr : bool or str, optional + Indicating whether to add gas production data when running NJOY. + If string, write the output tape to this file. + purr : bool or str, optional + Indicating whether to add probability table when running NJOY. + If string, write the output tape to this file. evaluation : openmc.data.endf.Evaluation, optional If the ENDF file contains multiple material evaluations, this argument indicates which evaluation should be used. @@ -255,8 +274,17 @@ def make_ace(filename, temperatures=None, ace='ace', xsdir='xsdir', pendf=None, ------ subprocess.CalledProcessError If the NJOY process returns with a non-zero status + IOError + If ``output_dir`` does not point to a directory """ + if output_dir is None: + output_dir = Path() + else: + output_dir = Path(output_dir) + if not output_dir.is_dir(): + raise IOError("{} is not a directory".format(output_dir)) + ev = evaluation if evaluation is not None else endf.Evaluation(filename) mat = ev.material zsymam = ev.target['zsymam'] @@ -275,8 +303,8 @@ def make_ace(filename, temperatures=None, ace='ace', xsdir='xsdir', pendf=None, nendf, npendf = 20, 21 tapein = {nendf: filename} tapeout = {} - if pendf is not None: - tapeout[npendf] = pendf + if pendf: + tapeout[npendf] = (output_dir / "pendf") if pendf is True else pendf # reconr commands += _TEMPLATE_RECONR @@ -285,6 +313,7 @@ def make_ace(filename, temperatures=None, ace='ace', xsdir='xsdir', pendf=None, # broadr if broadr: nbroadr = nlast + 1 + tapeout[nbroadr] = (output_dir / "broadr") if broadr is True else broadr commands += _TEMPLATE_BROADR nlast = nbroadr @@ -292,6 +321,7 @@ def make_ace(filename, temperatures=None, ace='ace', xsdir='xsdir', pendf=None, if heatr: nheatr_in = nlast nheatr = nheatr_in + 1 + tapeout[nheatr] = (output_dir / "heatr") if heatr is True else heatr commands += _TEMPLATE_HEATR nlast = nheatr @@ -299,6 +329,7 @@ def make_ace(filename, temperatures=None, ace='ace', xsdir='xsdir', pendf=None, if gaspr: ngaspr_in = nlast ngaspr = ngaspr_in + 1 + tapeout[ngaspr] = (output_dir / "gaspr") if gaspr is True else gaspr commands += _TEMPLATE_GASPR nlast = ngaspr @@ -306,6 +337,7 @@ def make_ace(filename, temperatures=None, ace='ace', xsdir='xsdir', pendf=None, if purr: npurr_in = nlast npurr = npurr_in + 1 + tapeout[npurr] = (output_dir / "purr") if purr is True else purr commands += _TEMPLATE_PURR nlast = npurr @@ -323,19 +355,21 @@ def make_ace(filename, temperatures=None, ace='ace', xsdir='xsdir', pendf=None, commands += _TEMPLATE_ACER.format(**locals()) # Indicate tapes to save for each ACER run - tapeout[nace] = fname.format(ace, temperature) - tapeout[ndir] = fname.format(xsdir, temperature) + tapeout[nace] = fname.format("ace", temperature) + tapeout[ndir] = fname.format("xsdir", temperature) commands += 'stop\n' run(commands, tapein, tapeout, **kwargs) if acer: - with open(ace, 'w') as ace_file, open(xsdir, 'w') as xsdir_file: + ace = (output_dir / "ace") if acer is True else Path(acer) + xsdir = (ace.parent / "xsdir") if xsdir is None else xsdir + with ace.open('w') as ace_file, xsdir.open('w') as xsdir_file: for temperature in temperatures: # Get contents of ACE file - text = open(fname.format(ace, temperature), 'r').read() + text = open(fname.format("ace", temperature), 'r').read() - # If the target is metastable, make sure that ZAID in the ACE file reflects - # this by adding 400 + # If the target is metastable, make sure that ZAID in the ACE + # file reflects this by adding 400 if ev.target['isomeric_state'] > 0: mass_first_digit = int(text[3]) if mass_first_digit <= 2: @@ -345,13 +379,13 @@ def make_ace(filename, temperatures=None, ace='ace', xsdir='xsdir', pendf=None, ace_file.write(text) # Concatenate into destination xsdir file - text = open(fname.format(xsdir, temperature), 'r').read() + text = open(fname.format("xsdir", temperature), 'r').read() xsdir_file.write(text) # Remove ACE/xsdir files for each temperature for temperature in temperatures: - os.remove(fname.format(ace, temperature)) - os.remove(fname.format(xsdir, temperature)) + os.remove(fname.format("ace", temperature)) + os.remove(fname.format("xsdir", temperature)) def make_ace_thermal(filename, filename_thermal, temperatures=None, diff --git a/openmc/data/reaction.py b/openmc/data/reaction.py index 05b6b8d2d6..696310ccaf 100644 --- a/openmc/data/reaction.py +++ b/openmc/data/reaction.py @@ -54,6 +54,7 @@ REACTION_NAME = {1: '(n,total)', 2: '(n,elastic)', 4: '(n,level)', 198: '(n,n3p)', 199: '(n,3n2pa)', 200: '(n,5n2p)', 203: '(n,Xp)', 204: '(n,Xd)', 205: '(n,Xt)', 206: '(n,X3He)', 207: '(n,Xa)', 301: 'heating', 444: 'damage-energy', + 318: "fission-heating", 999: "non-fission-heating", 649: '(n,pc)', 699: '(n,dc)', 749: '(n,tc)', 799: '(n,3Hec)', 849: '(n,ac)', 891: '(n,2nc)'} REACTION_NAME.update({i: '(n,n{})'.format(i - 50) for i in range(50, 91)}) diff --git a/openmc/deplete/__init__.py b/openmc/deplete/__init__.py index b5eb2dec2e..8c19615ece 100644 --- a/openmc/deplete/__init__.py +++ b/openmc/deplete/__init__.py @@ -38,4 +38,4 @@ from .reaction_rates import * from .abc import * from .results import * from .results_list import * -from .integrator import * +from .integrators import * diff --git a/openmc/deplete/_matrix_funcs.py b/openmc/deplete/_matrix_funcs.py new file mode 100644 index 0000000000..a606d739f7 --- /dev/null +++ b/openmc/deplete/_matrix_funcs.py @@ -0,0 +1,80 @@ +"""Functions to form the special matrix for depletion""" + + +def celi_f1(chain, rates, fission_yields=None): + return (5 / 12 * chain.form_matrix(rates[0], fission_yields) + + 1 / 12 * chain.form_matrix(rates[1], fission_yields)) + + +def celi_f2(chain, rates, fission_yields=None): + return (1 / 12 * chain.form_matrix(rates[0], fission_yields) + + 5 / 12 * chain.form_matrix(rates[1], fission_yields)) + + +def cf4_f1(chain, rates, fission_yields=None): + return 1 / 2 * chain.form_matrix(rates, fission_yields) + + +def cf4_f2(chain, rates, fission_yields=None): + return (-1 / 2 * chain.form_matrix(rates[0], fission_yields) + + chain.form_matrix(rates[1], fission_yields)) + + +def cf4_f3(chain, rates, fission_yields=None): + return (1 / 4 * chain.form_matrix(rates[0], fission_yields) + + 1 / 6 * chain.form_matrix(rates[1], fission_yields) + + 1 / 6 * chain.form_matrix(rates[2], fission_yields) + - 1 / 12 * chain.form_matrix(rates[3], fission_yields)) + + +def cf4_f4(chain, rates, fission_yields=None): + return (-1 / 12 * chain.form_matrix(rates[0], fission_yields) + + 1 / 6 * chain.form_matrix(rates[1], fission_yields) + + 1 / 6 * chain.form_matrix(rates[2], fission_yields) + + 1 / 4 * chain.form_matrix(rates[3], fission_yields)) + + +def rk4_f1(chain, rates, fission_yields=None): + return 1 / 2 * chain.form_matrix(rates, fission_yields) + + +def rk4_f4(chain, rates, fission_yields=None): + return (1 / 6 * chain.form_matrix(rates[0], fission_yields) + + 1 / 3 * chain.form_matrix(rates[1], fission_yields) + + 1 / 3 * chain.form_matrix(rates[2], fission_yields) + + 1 / 6 * chain.form_matrix(rates[3], fission_yields)) + + +def leqi_f1(chain, inputs, fission_yields): + f1 = chain.form_matrix(inputs[0], fission_yields) + f2 = chain.form_matrix(inputs[1], fission_yields) + dt_l, dt = inputs[2], inputs[3] + return -dt / (12 * dt_l) * f1 + (dt + 6 * dt_l) / (12 * dt_l) * f2 + + +def leqi_f2(chain, inputs, fission_yields=None): + f1 = chain.form_matrix(inputs[0], fission_yields) + f2 = chain.form_matrix(inputs[1], fission_yields) + dt_l, dt = inputs[2], inputs[3] + return -5 * dt / (12 * dt_l) * f1 + (5 * dt + 6 * dt_l) / (12 * dt_l) * f2 + + +def leqi_f3(chain, inputs, fission_yields=None): + f1 = chain.form_matrix(inputs[0], fission_yields) + f2 = chain.form_matrix(inputs[1], fission_yields) + f3 = chain.form_matrix(inputs[2], fission_yields) + dt_l, dt = inputs[3], inputs[4] + return (-dt ** 2 / (12 * dt_l * (dt + dt_l)) * f1 + + (dt ** 2 + 6 * dt * dt_l + 5 * dt_l ** 2) + / (12 * dt_l * (dt + dt_l)) * f2 + dt_l / (12 * (dt + dt_l)) * f3) + + +def leqi_f4(chain, inputs, fission_yields=None): + f1 = chain.form_matrix(inputs[0], fission_yields) + f2 = chain.form_matrix(inputs[1], fission_yields) + f3 = chain.form_matrix(inputs[2], fission_yields) + dt_l, dt = inputs[3], inputs[4] + return (-dt ** 2 / (12 * dt_l * (dt + dt_l)) * f1 + + (dt ** 2 + 2 * dt * dt_l + dt_l ** 2) + / (12 * dt_l * (dt + dt_l)) * f2 + + (4 * dt * dt_l + 5 * dt_l ** 2) / (12 * dt_l * (dt + dt_l)) * f3) diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py index a33d794441..fb638e5c21 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -5,18 +5,23 @@ to run a full depletion simulation. """ from collections import namedtuple +from collections.abc import Iterable import os from pathlib import Path from abc import ABC, abstractmethod -from xml.etree import ElementTree as ET +from copy import deepcopy from warnings import warn -from numbers import Real +from numbers import Real, Integral -from numpy import nonzero, empty +from numpy import nonzero, empty, asarray +from uncertainties import ufloat from openmc.data import DataLibrary, JOULE_PER_EV +from openmc.capi import MaterialFilter, Tally from openmc.checkvalue import check_type, check_greater_than +from .results import Results from .chain import Chain +from .results_list import ResultsList OperatorResult = namedtuple('OperatorResult', ['k', 'rates']) OperatorResult.__doc__ = """\ @@ -24,8 +29,8 @@ Result of applying transport operator Parameters ---------- -k : float - Resulting eigenvalue +k : uncertainties.ufloat + Resulting eigenvalue and standard deviation rates : openmc.deplete.ReactionRates Resulting reaction rates @@ -43,8 +48,8 @@ class TransportOperator(ABC): Each depletion integrator is written to work with a generic transport operator that takes a vector of material compositions and returns an - eigenvalue and reaction rates. This abstract class sets the requirements for - such a transport operator. Users should instantiate + eigenvalue and reaction rates. This abstract class sets the requirements + for such a transport operator. Users should instantiate :class:`openmc.deplete.Operator` rather than this class. Parameters @@ -61,6 +66,8 @@ class TransportOperator(ABC): in initial condition to ensure they exist in the decay chain. Only done for nuclides with reaction rates. Defaults to 1.0e3. + prev_results : ResultsList, optional + Results from a previous depletion calculation. Attributes ---------- @@ -68,8 +75,12 @@ class TransportOperator(ABC): Initial atom density [atoms/cm^3] to add for nuclides that are zero in initial condition to ensure they exist in the decay chain. Only done for nuclides with reaction rates. + prev_res : ResultsList or None + 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=None, fission_q=None, dilute_initial=1.0e3, + prev_results=None): self.dilute_initial = dilute_initial self.output_dir = '.' @@ -93,6 +104,11 @@ class TransportOperator(ABC): "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 + else: + check_type("previous results", prev_results, ResultsList) + self.prev_results = prev_results @property def dilute_initial(self): @@ -106,15 +122,15 @@ class TransportOperator(ABC): self._dilute_initial = value @abstractmethod - def __call__(self, vec, print_out=True): + def __call__(self, vec, power): """Runs a simulation. Parameters ---------- vec : list of numpy.ndarray Total atoms to be used in function. - print_out : bool, optional - Whether or not to print out time. + power : float + Power of the reactor in [W] Returns ------- @@ -122,7 +138,6 @@ class TransportOperator(ABC): Eigenvalue and reaction rates resulting from transport operator """ - pass def __enter__(self): # Save current directory and move to specific output directory @@ -157,8 +172,6 @@ class TransportOperator(ABC): Total density for initial conditions. """ - pass - @abstractmethod def get_results_info(self): """Returns volume list, cell lists, and nuc lists. @@ -170,16 +183,28 @@ class TransportOperator(ABC): nuc_list : list of str A list of all nuclide names. Used for sorting the simulation. burn_list : list of int - A list of all cell IDs to be burned. Used for sorting the simulation. + A list of all cell IDs to be burned. Used for sorting the + simulation. full_burn_list : list of int All burnable materials in the geometry. """ - pass - def finalize(self): pass + @abstractmethod + def write_bos_data(self, step): + """Document beginning of step data for a given step + + Called at the beginning of a depletion step and at + the final point in the simulation. + + Parameters + ---------- + step : int + Current depletion step including restarts + """ + class ReactionRateHelper(ABC): """Abstract class for generating reaction rates for operators @@ -246,7 +271,8 @@ class ReactionRateHelper(ABC): Parameters ---------- number : iterable of float - Number density [atoms/b-cm] of each nuclide tracked in the calculation. + Number density [atoms/b-cm] of each nuclide tracked in the + calculation. Returns ------- @@ -337,3 +363,482 @@ class EnergyHelper(ABC): def nuclides(self, nuclides): check_type("nuclides", nuclides, list, str) self._nuclides = nuclides + + +class FissionYieldHelper(ABC): + """Abstract class for processing energy dependent fission yields + + Parameters + ---------- + chain_nuclides : iterable of openmc.deplete.Nuclide + Nuclides tracked in the depletion chain. All nuclides are + not required to have fission yield data. + + Attributes + ---------- + constant_yields : dict of str to :class:`openmc.deplete.FissionYield` + Fission yields for all nuclides that only have one set of + fission yield data. Can be accessed as ``{parent: {product: yield}}`` + """ + + def __init__(self, chain_nuclides): + self._chain_nuclides = {} + self._constant_yields = {} + + # Get all nuclides with fission yield data + for nuc in chain_nuclides: + if nuc.yield_data is None: + continue + if len(nuc.yield_data) == 1: + self._constant_yields[nuc.name] = ( + nuc.yield_data[nuc.yield_energies[0]]) + elif len(nuc.yield_data) > 1: + self._chain_nuclides[nuc.name] = nuc + self._chain_set = set(self._chain_nuclides) | set(self._constant_yields) + + @property + def constant_yields(self): + return deepcopy(self._constant_yields) + + @abstractmethod + def weighted_yields(self, local_mat_index): + """Return fission yields for a specific material + + Parameters + ---------- + local_mat_index : int + Index for material tracked on this process that + exists in :attr:`local_mat_index` and fits within + the first axis in :attr:`results` + + Returns + ------- + library : dict + Dictionary of ``{parent: {product: fyield}}`` + """ + + @staticmethod + def unpack(): + """Unpack tally data prior to compute fission yields. + + Called after a :meth:`openmc.deplete.Operator.__call__` + routine during the normalization of reaction rates. + + Not necessary for all subclasses to implement, unless tallies + are used. + """ + + @staticmethod + def generate_tallies(materials, mat_indexes): + """Construct tallies necessary for computing fission yields + + Called during the operator set up phase prior to depleting. + Not necessary for subclasses to implement + + Parameters + ---------- + materials : iterable of C-API materials + Materials to be used in :class:`openmc.capi.MaterialFilter` + mat_indexes : iterable of int + Indices of tallied materials that will have their fission + yields computed by this helper. Necessary as the + :class:`openmc.deplete.Operator` that uses this helper + may only burn a subset of all materials when running + in parallel mode. + """ + + def update_tally_nuclides(self, nuclides): + """Return nuclides with non-zero densities and yield data + + Parameters + ---------- + nuclides : iterable of str + Nuclides with non-zero densities from the + :class:`openmc.deplete.Operator` + + Returns + ------- + nuclides : list of str + Union of nuclides that the :class:`openmc.deplete.Operator` + says have non-zero densities at this stage and those that + have yield data. Sorted by nuclide name + + """ + return sorted(self._chain_set & set(nuclides)) + + @classmethod + def from_operator(cls, operator, **kwargs): + """Create a new instance by pulling data from the operator + + All keyword arguments should be identical to their counterpart + in the main ``__init__`` method + + Parameters + ---------- + operator : openmc.deplete.TransportOperator + Operator with a depletion chain + kwargs: optional + Additional keyword arguments to be used in constuction + """ + return cls(operator.chain.nuclides, **kwargs) + + +class TalliedFissionYieldHelper(FissionYieldHelper): + """Abstract class for computing fission yields with tallies + + Generates a basic fission rate tally in all burnable materials with + :meth:`generate_tallies`, and set nuclides to be tallied with + :meth:`update_tally_nuclides`. Subclasses will need to implement + :meth:`unpack` and :meth:`weighted_yields`. + + Parameters + ---------- + chain_nuclides : iterable of openmc.deplete.Nuclide + Nuclides tracked in the depletion chain. Not necessary + that all have yield data. + + Attributes + ---------- + constant_yields : dict of str to :class:`openmc.deplete.FissionYield` + Fission yields for all nuclides that only have one set of + fission yield data. Can be accessed as ``{parent: {product: yield}}`` + results : None or numpy.ndarray + Tally results shaped in a manner useful to this helper. + """ + + _upper_energy = 20.0e6 # upper energy for tallies + + def __init__(self, chain_nuclides): + super().__init__(chain_nuclides) + self._local_indexes = None + self._fission_rate_tally = None + self._tally_nucs = [] + self.results = None + + def generate_tallies(self, materials, mat_indexes): + """Construct the fission rate tally + + Parameters + ---------- + materials : iterable of :class:`openmc.capi.Material` + Materials to be used in :class:`openmc.capi.MaterialFilter` + mat_indexes : iterable of int + Indices of tallied materials that will have their fission + yields computed by this helper. Necessary as the + :class:`openmc.deplete.Operator` that uses this helper + may only burn a subset of all materials when running + in parallel mode. + """ + self._local_indexes = asarray(mat_indexes) + + # Tally group-wise fission reaction rates + self._fission_rate_tally = Tally() + self._fission_rate_tally.scores = ['fission'] + + self._fission_rate_tally.filters = [MaterialFilter(materials)] + + def update_tally_nuclides(self, nuclides): + """Tally nuclides with non-zero density and multiple yields + + Must be run after :meth:`generate_tallies`. + + Parameters + ---------- + nuclides : iterable of str + Potential nuclides to be tallied, such as those with + non-zero density at this stage. + + Returns + ------- + nuclides : list of str + Union of input nuclides and those that have multiple sets + of yield data. Sorted by nuclide name + + Raises + ------ + AttributeError + If tallies not generated + """ + assert self._fission_rate_tally is not None, ( + "Run generate_tallies first") + overlap = set(self._chain_nuclides).intersection(set(nuclides)) + nuclides = sorted(overlap) + self._tally_nucs = [self._chain_nuclides[n] for n in nuclides] + self._fission_rate_tally.nuclides = nuclides + return nuclides + + @abstractmethod + def unpack(self): + """Unpack tallies after a transport run. + + Abstract because each subclass will need to arrange its + tally data. + """ + + +class Integrator(ABC): + """Abstract class for solving the time-integration for depletion + + Parameters + ---------- + operator : openmc.deplete.TransportOperator + Operator to perform transport simulations + timesteps : iterable of float + Array of timesteps in units of [s]. Note that values are not + cumulative. + power : float or iterable of float, optional + Power of the reactor in [W]. A single value indicates that + the power is constant over all timesteps. An iterable + indicates potentially different power levels for each timestep. + For a 2D problem, the power can be given in [W/cm] as long + as the "volume" assigned to a depletion material is actually + an area in [cm^2]. Either ``power`` or ``power_density`` must be + specified. + power_density : float or iterable of float, optional + Power density of the reactor in [W/gHM]. It is multiplied by + initial heavy metal inventory to get total power if ``power`` + is not speficied. + + Attributes + ---------- + operator : openmc.deplete.TransportOperator + Operator to perform transport simulations + chain : openmc.deplete.Chain + Depletion chain + timesteps : iterable of float + Size of each depletion interval in [s] + power : iterable of float + Power of the reactor in [W] for each interval in :attr:`timesteps` + """ + + def __init__(self, operator, timesteps, power=None, power_density=None): + # Check number of stages previously used + if operator.prev_res is not None: + res = operator.prev_res[-1] + if res.data.shape[0] != self._num_stages: + raise ValueError( + "{} incompatible with previous restart calculation. " + "Previous scheme used {} intermediate solutions, while " + "this uses {}".format( + self.__class__.__name__, res.data.shape[0], + self._num_stages)) + self.operator = operator + self.chain = operator.chain + if not isinstance(timesteps, Iterable): + self.timesteps = [timesteps] + else: + self.timesteps = timesteps + if power is None: + if power_density is None: + raise ValueError("Either power or power density must be set") + if not isinstance(power_density, Iterable): + power = power_density * operator.heavy_metal + else: + power = [p * operator.heavy_metal for p in power_density] + + if not isinstance(power, Iterable): + # Ensure that power is single value if that is the case + power = [power] * len(self.timesteps) + elif len(power) != len(self.timesteps): + raise ValueError( + "Number of time steps != number of powers. {} vs {}".format( + len(self.timesteps), len(power))) + + self.power = power + + @abstractmethod + def __call__(self, conc, rates, dt, power, i): + """Perform the integration across one time step + + Parameters + ---------- + conc : numpy.ndarray + Initial concentrations for all nuclides in [atom] + rates : openmc.deplete.ReactionRates + Reaction rates from operator + dt : float + Time in [s] for the entire depletion interval + power : float + Power of the system in [W] + i : int + Current depletion step index + + Returns + ------- + proc_time : float + Time spent in CRAM routines for all materials in [s] + conc_list : list of numpy.ndarray + Concentrations at each of the intermediate points with + the final concentration as the last element + op_results : list of openmc.deplete.OperatorResult + Eigenvalue and reaction rates from intermediate transport + simulations + """ + + @property + @abstractmethod + def _num_stages(self): + """Number of intermediate transport solutions + + Needed to ensure schemes are consistent with restarts + """ + + def __iter__(self): + """Return pairs of time steps in [s] and powers in [W]""" + return zip(self.timesteps, self.power) + + def __len__(self): + """Return integer number of depletion intervals""" + return len(self.timesteps) + + def _get_bos_data_from_operator(self, step_index, step_power, bos_conc): + """Get beginning of step concentrations, reaction rates from Operator + """ + x = deepcopy(bos_conc) + res = self.operator(x, step_power) + self.operator.write_bos_data(step_index + self._i_res) + return x, res + + def _get_bos_data_from_restart(self, step_index, step_power, bos_conc): + """Get beginning of step concentrations, reaction rates from restart""" + res = self.operator.prev_res[-1] + # Depletion methods expect list of arrays + bos_conc = list(res.data[0]) + rates = res.rates[0] + k = ufloat(res.k[0, 0], res.k[0, 1]) + + # Scale rates by ratio of powers + rates *= step_power / res.power[0] + return bos_conc, OperatorResult(k, rates) + + def _get_start_data(self): + if self.operator.prev_res is None: + return 0.0, 0 + return (self.operator.prev_res[-1].time[-1], + len(self.operator.prev_res) - 1) + + def integrate(self): + """Perform the entire depletion process across all steps""" + with self.operator as conc: + t, self._i_res = self._get_start_data() + + for i, (dt, p) in enumerate(self): + if i > 0 or self.operator.prev_res is None: + conc, res = self._get_bos_data_from_operator(i, p, conc) + else: + conc, res = self._get_bos_data_from_restart(i, p, conc) + proc_time, conc_list, res_list = self(conc, res.rates, dt, p, i) + + # Insert BOS concentration, transport results + conc_list.insert(0, conc) + res_list.insert(0, res) + + # Remove actual EOS concentration for next step + conc = conc_list.pop() + + Results.save(self.operator, conc_list, res_list, [t, t + dt], + p, self._i_res + i, proc_time) + + t += dt + + # Final simulation + res_list = [self.operator(conc, p)] + Results.save(self.operator, [conc], res_list, [t, t], + p, self._i_res + len(self), proc_time) + self.operator.write_bos_data(len(self) + self._i_res) + + +class SIIntegrator(Integrator): + """Abstract class for the Stochastic Implicit Euler integrators + + Does not provide a ``__call__`` method, but scales and resets + the number of particles used in initial transport calculation + + Parameters + ---------- + operator : openmc.deplete.TransportOperator + The operator object to simulate on. + timesteps : iterable of float + Array of timesteps in units of [s]. Note that values are not + cumulative. + power : float or iterable of float, optional + Power of the reactor in [W]. A single value indicates that + the power is constant over all timesteps. An iterable + indicates potentially different power levels for each timestep. + For a 2D problem, the power can be given in [W/cm] as long + as the "volume" assigned to a depletion material is actually + an area in [cm^2]. Either ``power`` or ``power_density`` must be + specified. + power_density : float or iterable of float, optional + Power density of the reactor in [W/gHM]. It is multiplied by + initial heavy metal inventory to get total power if ``power`` + is not speficied. + n_steps : int, optional + Number of stochastic iterations per depletion interval. + Must be greater than zero. Default : 10 + + Attributes + ---------- + operator : openmc.deplete.TransportOperator + Operator to perform transport simulations + chain : openmc.deplete.Chain + Depletion chain + timesteps : iterable of float + Size of each depletion interval in [s] + power : iterable of float + Power of the reactor in [W] for each interval in :attr:`timesteps` + n_steps : int + Number of stochastic iterations per depletion interval + """ + def __init__(self, operator, timesteps, power=None, power_density=None, + n_steps=10): + check_type("n_steps", n_steps, Integral) + check_greater_than("n_steps", n_steps, 0) + super().__init__(operator, timesteps, power, power_density) + self.n_steps = n_steps + + def _get_bos_data_from_operator(self, step_index, step_power, bos_conc): + reset_particles = False + if step_index == 0 and hasattr(self.operator, "settings"): + reset_particles = True + self.operator.settings.particles *= self.n_stages + inherited = super()._get_bos_data_from_operator( + step_index, step_power, bos_conc) + if reset_particles: + self.operator.settings.particles //= self.n_stages + return inherited + + def integrate(self): + """Perform the entire depletion process across all steps""" + with self.operator as conc: + t, self._i_res = self._get_start_data() + + for i, (dt, p) in enumerate(self): + if i == 0: + if self.operator.prev_res is None: + conc, res = self._get_bos_data_from_operator(i, p, conc) + else: + conc, res = self._get_bos_data_from_restart(i, p, conc) + else: + # Pull rates, k from previous iteration w/o + # re-running transport + res = res_list[-1] # defined in previous i iteration + + proc_time, conc_list, res_list = self(conc, res.rates, dt, p, i) + + # Insert BOS concentration, transport results + conc_list.insert(0, conc) + res_list.insert(0, res) + + # Remove actual EOS concentration for next step + conc = conc_list.pop() + + Results.save(self.operator, conc_list, res_list, [t, t + dt], + p, self._i_res + i, proc_time) + + t += dt + + # No final simulation for SIE, use last iteration results + Results.save(self.operator, [conc], [res_list[-1]], [t, t], + p, self._i_res + len(self), proc_time) + self.operator.write_bos_data(self._i_res + len(self)) diff --git a/openmc/deplete/chain.py b/openmc/deplete/chain.py index 1f16d9cafa..c2b5b75b31 100644 --- a/openmc/deplete/chain.py +++ b/openmc/deplete/chain.py @@ -9,11 +9,13 @@ from itertools import chain import math import re from collections import OrderedDict, defaultdict -from collections.abc import Mapping +from collections.abc import Mapping, Iterable +from numbers import Real from warnings import warn -from openmc.checkvalue import check_type, check_less_than +from openmc.checkvalue import check_type, check_greater_than from openmc.data import gnd_name, zam +from .nuclide import FissionYieldDistribution # Try to use lxml if it is available. It preserves the order of attributes and # provides a pretty-printer by default. If not available, @@ -103,6 +105,19 @@ def replace_missing(product, decay_data): return product +_SECONDARY_PARTICLES = { + "(n,p)": ["H1"], "(n,d)": ["H2"], "(n,t)": ["H3"], "(n,3He)": ["He3"], + "(n,a)": ["He4"], "(n,2nd)": ["H2"], "(n,na)": ["He4"], "(n,3na)": ["He4"], + "(n,n3a)": ["He4"] * 3, "(n,2na)": ["He4"], "(n,np)": ["H1"], + "(n,n2a)": ["He4"] * 2, "(n,2n2a)": ["He4"] * 2, "(n,nd)": ["H2"], + "(n,nt)": ["H3"], "(n,nHe-3)": ["He3"], "(n,nd2a)": ["H2", "He4"], + "(n,nt2a)": ["H3", "He4", "He4"], "(n,2np)": ["H1"], "(n,3np)": ["H1"], + "(n,n2p)": ["H1"] * 2, "(n,2a)": ["He4"] * 2, "(n,3a)": ["He4"] * 3, + "(n,2p)": ["H1"] * 2, "(n,pa)": ["H1", "He4"], + "(n,t2a)": ["H3", "He4", "He4"], "(n,d2a)": ["H2", "He4", "He4"], + "(n,pd)": ["H1", "H2"], "(n,pt)": ["H1", "H3"], "(n,da)": ["H2", "He4"]} + + class Chain(object): """Full representation of a depletion chain. @@ -122,13 +137,22 @@ class Chain(object): Reactions that are tracked in the depletion chain nuclide_dict : OrderedDict of str to int Maps a nuclide name to an index in nuclides. - + fission_yields : None or iterable of dict + List of effective fission yields for materials. Each dictionary + should be of the form ``{parent: {product: yield}}`` with + types ``{str: {str: float}}``, where ``yield`` is the fission product + yield for isotope ``parent`` producing isotope ``product``. + A single entry indicates yields are constant across all materials. + Otherwise, an entry can be added for each material to be burned. + Ordering should be identical to how the operator orders reaction + rates for burnable materials. """ def __init__(self): self.nuclides = [] self.reactions = [] self.nuclide_dict = OrderedDict() + self._fission_yields = None def __contains__(self, nuclide): return nuclide in self.nuclide_dict @@ -196,8 +220,7 @@ class Chain(object): for idx, parent in enumerate(sorted(decay_data, key=openmc.data.zam)): data = decay_data[parent] - nuclide = Nuclide() - nuclide.name = parent + nuclide = Nuclide(parent) chain.nuclides.append(nuclide) chain.nuclide_dict[parent] = idx @@ -212,7 +235,8 @@ class Chain(object): if mode.daughter in decay_data: target = mode.daughter else: - print('missing {} {} {}'.format(parent, ','.join(mode.modes), mode.daughter)) + print('missing {} {} {}'.format( + parent, ','.join(mode.modes), mode.daughter)) target = replace_missing(mode.daughter, decay_data) # Write branching ratio, taking care to ensure sum is unity @@ -266,15 +290,16 @@ class Chain(object): fpy = fpy_data[parent] if fpy.energies is not None: - nuclide.yield_energies = fpy.energies + yield_energies = fpy.energies else: - nuclide.yield_energies = [0.0] + yield_energies = [0.0] - for E, table in zip(nuclide.yield_energies, fpy.independent): + yield_data = {} + for E, table in zip(yield_energies, fpy.independent): yield_replace = 0.0 yields = defaultdict(float) for product, y in table.items(): - # Handle fission products that have no decay data available + # Handle fission products that have no decay data if product not in decay_data: daughter = replace_missing(product, decay_data) product = daughter @@ -284,10 +309,9 @@ class Chain(object): if yield_replace > 0.0: missing_fp.append((parent, E, yield_replace)) + yield_data[E] = yields - nuclide.yield_data[E] = [] - for k in sorted(yields, key=openmc.data.zam): - nuclide.yield_data[E].append((k, yields[k])) + nuclide.yield_data = FissionYieldDistribution(yield_data) # Display warnings if missing_daughter: @@ -374,23 +398,56 @@ class Chain(object): clean_indentation(root_elem) tree.write(str(filename), encoding='utf-8') - def form_matrix(self, rates): + def get_thermal_fission_yields(self): + """Return fission yields at lowest incident neutron energy + + Used as the default set of fission yields for :meth:`form_matrix` + if ``fission_yields`` are not provided + + Returns + ------- + fission_yields : dict + Dictionary of ``{parent: {product: f_yield}}`` + where ``parent`` and ``product`` are both string + names of nuclides with yield data and ``f_yield`` + is a float for the fission yield. + """ + out = {} + for nuc in self.nuclides: + if nuc.yield_data is None: + continue + yield_obj = nuc.yield_data[min(nuc.yield_energies)] + out[nuc.name] = dict(yield_obj) + return out + + def form_matrix(self, rates, fission_yields=None): """Forms depletion matrix. Parameters ---------- rates : numpy.ndarray 2D array indexed by (nuclide, reaction) + fission_yields : dict, optional + Option to use a custom set of fission yields. Expected + to be of the form ``{parent : {product : f_yield}}`` + with string nuclide names for ``parent`` and ``product``, + and ``f_yield`` as the respective fission yield Returns ------- scipy.sparse.csr_matrix Sparse matrix representing depletion. + See Also + -------- + :meth:`get_thermal_fission_yields` """ matrix = defaultdict(float) reactions = set() + if fission_yields is None: + fission_yields = self.get_thermal_fission_yields() + for i, nuc in enumerate(self.nuclides): if nuc.n_decay_modes != 0: @@ -435,11 +492,7 @@ class Chain(object): k = self.nuclide_dict[target] matrix[k, i] += path_rate * br else: - # Assume that we should always use thermal fission - # yields. At some point it would be nice to account - # for the energy-dependence.. - energy, data = sorted(nuc.yield_data.items())[0] - for product, y in data: + for product, y in fission_yields[nuc.name].items(): yield_val = y * path_rate if yield_val != 0.0: k = self.nuclide_dict[product] @@ -454,70 +507,99 @@ class Chain(object): dict.update(matrix_dok, matrix) return matrix_dok.tocsr() - def get_capture_branches(self): - """Return a dictionary with capture branching ratios + def get_branch_ratios(self, reaction="(n,gamma)"): + """Return a dictionary with reaction branching ratios + + Parameters + ---------- + reaction : str, optional + Reaction name like ``"(n,gamma)"`` [default], or + ``"(n,alpha)"``. Returns ------- - capt : - nested dict of parent nuclide keys with capture targets and - branching ratios:: + branches : dict + nested dict of parent nuclide keys with reaction targets and + branching ratios. Consider the capture, ``"(n,gamma)"``, + reaction for Am241:: {"Am241": {"Am242": 0.91, "Am242_m1": 0.09}} See Also -------- - :meth:`set_capture_branches` - + :meth:`set_branch_ratios` """ capt = {} for nuclide in self.nuclides: nuc_capt = {} for rx in nuclide.reactions: - if rx.type == "(n,gamma)" and rx.branching_ratio != 1.0: + if rx.type == reaction and rx.branching_ratio != 1.0: nuc_capt[rx.target] = rx.branching_ratio if len(nuc_capt) > 0: capt[nuclide.name] = nuc_capt return capt - def set_capture_branches(self, branch_ratios, strict=True): - """Set the capture branching ratios - - To provide a buffer around floating point precisions, - the sum of all branching ratios from a single parent - cannot be greater than 1.00001. + def set_branch_ratios(self, branch_ratios, reaction="(n,gamma)", + strict=True, tolerance=1e-5): + """Set the branching ratios for a given reactions Parameters ---------- branch_ratios : dict of {str: {str: float}} Capture branching ratios to be inserted. First layer keys are names of parent nuclides, e.g. - ``"Am241"``. The capture branching ratios for these + ``"Am241"``. The branching ratios for these parents will be modified. Corresponding values are dictionaries of ``{target: branching_ratio}`` - strict : bool - If this evalutes to ``True``, then all parents and - products must exist in the :class:`Chain`. A - :class:`KeyError` will be raised at the first - nuclide that does not exist. Otherwise, print - a warning message for missing parents and/or - products. + reaction : str, optional + Reaction name like ``"(n,gamma)"`` [default], or + ``"(n, alpha)"``. + strict : bool, optional + Error control. If this evalutes to ``True``, then errors will + be raised if inconsistencies are found. Otherwise, warnings + will be raised for most issues. + tolerance : float, optional + Tolerance on the sum of all branching ratios for a + single parent. Will be checked with:: + + 1 - tol < sum_br < 1 + tol + + Raises + ------ + IndexError + If no isotopes were found on the chain that have the requested + reaction + KeyError + If ``strict`` evaluates to ``False`` and a parent isotope in + ``branch_ratios`` does not exist on the chain + AttributeError + If ``strict`` evaluates to ``False`` and a parent isotope in + ``branch_ratios`` does not have the requested reaction + ValueError + If ``strict`` evalutes to ``False`` and the sum of one parents + branch ratios is outside 1 +/- ``tolerance`` See Also -------- - :meth:`get_capture_branches` + :meth:`get_branch_ratios` """ # Store some useful information through the validation stage sums = {} - capt_ix_map = {} + rxn_ix_map = {} grounds = {} + tolerance = abs(tolerance) + missing_parents = set() missing_products = {} - no_capture = set() + missing_reaction = set() + bad_sums = {} + + # Secondary products, like alpha particles, should not be modified + secondary = _SECONDARY_PARTICLES.get(reaction, []) # Check for validity before manipulation @@ -543,11 +625,11 @@ class Chain(object): if prod_flag: continue - # Make sure this nuclide has capture reactions + # Make sure this nuclide has the reaction indexes = [] for ix, rx in enumerate(self[parent].reactions): - if rx.type == "(n,gamma)": + if rx.type == reaction and rx.target not in secondary: indexes.append(ix) if "_m" not in rx.target: grounds[parent] = rx.target @@ -555,24 +637,39 @@ class Chain(object): if len(indexes) == 0: if strict: raise AttributeError( - "Nuclide {} does not have capture reactions in " - "this {}".format(parent, self.__class__.__name__)) - no_capture.add(parent) + "Nuclide {} does not have {} reactions".format( + parent, reaction)) + missing_reaction.add(parent) continue - capt_ix_map[parent] = indexes - this_sum = sum(sub.values()) - check_less_than(parent + " ratios", this_sum, 1.00001) - sums[parent] = this_sum + # sum of branching ratios can be lower than 1 if no ground + # target is given, but never greater + if (this_sum >= 1 + tolerance or (grounds[parent] in sub + and this_sum <= 1 - tolerance)): + if strict: + msg = ("Sum of {} branching ratios for {} " + "({:7.3f}) outside tolerance of 1 +/- " + "{:5.3e}".format( + reaction, parent, this_sum, tolerance)) + raise ValueError(msg) + bad_sums[parent] = this_sum + else: + rxn_ix_map[parent] = indexes + sums[parent] = this_sum + + if len(rxn_ix_map) == 0: + raise IndexError( + "No {} reactions found in this {}".format( + reaction, self.__class__.__name__)) if len(missing_parents) > 0: warn("The following nuclides were not found in {}: {}".format( self.__class__.__name__, ", ".join(sorted(missing_parents)))) - if len(no_capture) > 0: - warn("The following nuclides did not have capture reactions: " - "{}".format(", ".join(sorted(no_capture)))) + if len(missing_reaction) > 0: + warn("The following nuclides did not have {} reactions: " + "{}".format(reaction, ", ".join(sorted(missing_reaction)))) if len(missing_products) > 0: tail = ("{} -> {}".format(k, v) @@ -581,28 +678,35 @@ class Chain(object): "parents were unmodified: \n{}".format( self.__class__.__name__, ", ".join(tail))) + if len(bad_sums) > 0: + tail = ("{}: {:5.3f}".format(k, s) + for k, s in sorted(bad_sums.items())) + warn("The following parent nuclides were given {} branch ratios " + "with a sum outside tolerance of 1 +/- {:5.3e}:\n{}".format( + reaction, tolerance, "\n".join(tail))) + # Insert new ReactionTuples with updated branch ratios - for parent_name, capt_index in capt_ix_map.items(): + for parent_name, rxn_index in rxn_ix_map.items(): parent = self[parent_name] new_ratios = branch_ratios[parent_name] - capt_index = capt_ix_map[parent_name] + rxn_index = rxn_ix_map[parent_name] # Assume Q value is independent of target state - capt_Q = parent.reactions[capt_index[0]].Q + rxn_Q = parent.reactions[rxn_index[0]].Q - # Remove existing capture reactions + # Remove existing reactions - for ix in reversed(capt_index): + for ix in reversed(rxn_index): parent.reactions.pop(ix) all_meta = True for tgt, br in new_ratios.items(): - all_meta = all_meta and ("_m" in tgt) + all_meta = all_meta and ("_m" in tgt) parent.reactions.append(ReactionTuple( - "(n,gamma)", tgt, capt_Q, br)) + reaction, tgt, rxn_Q, br)) if all_meta and sums[parent_name] != 1.0: ground_br = 1.0 - sums[parent_name] @@ -612,4 +716,69 @@ class Chain(object): ground_tgt = gnd_name(pz, pa + 1, 0) new_ratios[ground_tgt] = ground_br parent.reactions.append(ReactionTuple( - "(n,gamma)", ground_tgt, capt_Q, ground_br)) + reaction, ground_tgt, rxn_Q, ground_br)) + + @property + def fission_yields(self): + if self._fission_yields is None: + self._fission_yields = [self.get_thermal_fission_yields()] + return self._fission_yields + + @fission_yields.setter + def fission_yields(self, yields): + if yields is not None: + if isinstance(yields, Mapping): + yields = [yields] + check_type("fission_yields", yields, Iterable, Mapping) + self._fission_yields = yields + + def validate(self, strict=True, quiet=False, tolerance=1e-4): + """Search for possible inconsistencies + + The following checks are performed for all nuclides present: + + 1) For all non-fission reactions, does the sum of branching + ratios equal about one? + 2) For fission reactions, does the sum of fission yield + fractions equal about two? + + Parameters + ---------- + strict : bool, optional + Raise exceptions at the first inconsistency if true. + Otherwise mark a warning + quiet : bool, optional + Flag to suppress warnings and return immediately at + the first inconsistency. Used only if + ``strict`` does not evaluate to ``True``. + tolerance : float, optional + Absolute tolerance for comparisons. Used to compare computed + value ``x`` to intended value ``y`` as:: + + valid = (y - tolerance <= x <= y + tolerance) + + Returns + ------- + valid : bool + True if no inconsistencies were found + + Raises + ------ + ValueError + If ``strict`` evaluates to ``True`` and an inconistency was + found + + See Also + -------- + openmc.deplete.Nuclide.validate + """ + check_type("tolerance", tolerance, Real) + check_greater_than("tolerance", tolerance, 0.0, True) + valid = True + # Sort through nuclides by name + for name in sorted(self.nuclide_dict): + stat = self[name].validate(strict, quiet, tolerance) + if quiet and not stat: + return stat + valid = valid and stat + return valid diff --git a/openmc/deplete/integrator/cram.py b/openmc/deplete/cram.py similarity index 86% rename from openmc/deplete/integrator/cram.py rename to openmc/deplete/cram.py index f5da6d696b..67a455cb35 100644 --- a/openmc/deplete/integrator/cram.py +++ b/openmc/deplete/cram.py @@ -11,10 +11,12 @@ import numpy as np import scipy.sparse as sp import scipy.sparse.linalg as sla -from .. import comm +from . import comm + +__all__ = ["deplete", "timed_deplete", "CRAM16", "CRAM48"] -def deplete(chain, x, rates, dt, print_out=True, matrix_func=None): +def deplete(chain, x, rates, dt, matrix_func=None): """Deplete materials using given reaction rates for a specified time Parameters @@ -27,28 +29,37 @@ def deplete(chain, x, rates, dt, print_out=True, matrix_func=None): Reaction rates (from transport operator) dt : float Time in [s] to deplete for - print_out : bool, optional - Whether to show elapsed time - maxtrix_func : function, optional - Function to form the depletion matrix + maxtrix_func : Callable, optional + Function to form the depletion matrix after calling + ``matrix_func(chain, rates, fission_yields)``, where + ``fission_yields = {parent: {product: yield_frac}}`` + Expected to return the depletion matrix required by + :func:`CRAM48`. Returns ------- x_result : list of numpy.ndarray Updated atom number vectors for each material - """ - t_start = time.time() + + fission_yields = chain.fission_yields + if len(fission_yields) == 1: + fission_yields = repeat(fission_yields[0]) + elif len(fission_yields) != len(x): + raise ValueError( + "Number of material fission yield distributions {} is not equal " + "to the number of compositions {}".format(len(fission_yields), + len(x))) + + if matrix_func is None: + matrices = map(chain.form_matrix, rates, fission_yields) + else: + matrices = map(matrix_func, repeat(chain), rates, fission_yields) # Use multiprocessing pool to distribute work with Pool() as pool: - iters = zip(repeat(chain), x, rates, repeat(dt), repeat(matrix_func)) - x_result = list(pool.starmap(_cram_wrapper, iters)) - - t_end = time.time() - if comm.rank == 0: - if print_out: - print("Time to matexp: ", t_end - t_start) + inputs = zip(matrices, x, repeat(dt)) + x_result = list(pool.starmap(CRAM48, inputs)) return x_result @@ -72,35 +83,6 @@ def timed_deplete(*args, **kwargs): return time.time() - start, results -def _cram_wrapper(chain, n0, rates, dt, matrix_func=None): - """Wraps depletion matrix creation / CRAM solve for multiprocess execution - - Parameters - ---------- - chain : openmc.deplete.Chain - Depletion chain used to construct the burnup matrix - n0 : numpy.array - Vector to operate a matrix exponent on. - rates : numpy.ndarray - 2D array indexed by nuclide then by cell. - dt : float - Time to integrate to. - maxtrix_func : function, optional - Function to form the depletion matrix - - Returns - ------- - numpy.array - Results of the matrix exponent. - """ - - if matrix_func is None: - A = chain.form_matrix(rates) - else: - A = matrix_func(chain, rates) - return CRAM48(A, n0, dt) - - def CRAM16(A, n0, dt): """Chebyshev Rational Approximation Method, order 16 diff --git a/openmc/deplete/helpers.py b/openmc/deplete/helpers.py index 66722c00a2..56644a3bba 100644 --- a/openmc/deplete/helpers.py +++ b/openmc/deplete/helpers.py @@ -1,12 +1,24 @@ """ Class for normalizing fission energy deposition """ +from copy import deepcopy from itertools import product +from numbers import Real +import bisect -from numpy import dot, zeros +from numpy import dot, zeros, newaxis -from openmc.capi import Tally, MaterialFilter -from .abc import ReactionRateHelper, EnergyHelper +from openmc.checkvalue import check_type, check_greater_than +from openmc.capi import ( + Tally, MaterialFilter, EnergyFilter, EnergyFunctionFilter) +from .abc import ( + ReactionRateHelper, EnergyHelper, FissionYieldHelper, + TalliedFissionYieldHelper) + +__all__ = ( + "DirectReactionRateHelper", "ChainFissionHelper", + "ConstantFissionYieldHelper", "FissionYieldCutoffHelper", + "AveragedFissionYieldHelper") # ------------------------------------- # Helpers for generating reaction rates @@ -142,3 +154,465 @@ class ChainFissionHelper(EnergyHelper): isotopes in all materials have the same Q value. """ self._energy += dot(fission_rates, self._fission_q_vector) + + +# ------------------------------------ +# Helper for collapsing fission yields +# ------------------------------------ + + +class ConstantFissionYieldHelper(FissionYieldHelper): + """Class that uses a single set of fission yields on each isotope + + Parameters + ---------- + chain_nuclides : iterable of openmc.deplete.Nuclide + Nuclides tracked in the depletion chain. All nuclides are + not required to have fission yield data. + energy : float, optional + Key in :attr:`openmc.deplete.Nuclide.yield_data` corresponding + to the desired set of fission yield data. Typically one of + ``{0.0253, 500000, 14000000}`` corresponding to 0.0253 eV, + 500 keV, and 14 MeV yield libraries. If the specific key is not + found, will fall back to closest energy present. + Default: 0.0253 eV for thermal yields + + Attributes + ---------- + constant_yields : dict of str to :class:`openmc.deplete.FissionYield` + Fission yields for all nuclides that only have one set of + fission yield data. Can be accessed as ``{parent: {product: yield}}`` + energy : float + Energy of fission yield libraries. + """ + + def __init__(self, chain_nuclides, energy=0.0253): + check_type("energy", energy, Real) + check_greater_than("energy", energy, 0.0, equality=True) + self._energy = energy + super().__init__(chain_nuclides) + # Iterate over all nuclides with > 1 set of yields + for name, nuc in self._chain_nuclides.items(): + yield_data = nuc.yield_data.get(energy) + if yield_data is not None: + self._constant_yields[name] = yield_data + continue + # Specific energy not found, use closest energy + distances = [abs(energy - ene) for ene in nuc.yield_energies] + min_E = min(nuc.yield_energies, key=lambda e: abs(e - energy)) + self._constant_yields[name] = nuc.yield_data[min_E] + + @classmethod + def from_operator(cls, operator, **kwargs): + """Return a new ConstantFissionYieldHelper using operator data + + All keyword arguments should be identical to their counterpart + in the main ``__init__`` method + + Parameters + ---------- + operator : openmc.deplete.TransportOperator + operator with a depletion chain + kwargs: + Additional keyword arguments to be used in construction + + Returns + ------- + ConstantFissionYieldHelper + """ + return cls(operator.chain.nuclides, **kwargs) + + @property + def energy(self): + return self._energy + + def weighted_yields(self, _local_mat_index=None): + """Return fission yields for all nuclides requested + + Parameters + ---------- + _local_mat_index : int, optional + Current material index. Not used since all yields are + constant + + Returns + ------- + library : dict + Dictionary of ``{parent: {product: fyield}}`` + """ + return self.constant_yields + + +class FissionYieldCutoffHelper(TalliedFissionYieldHelper): + """Helper that computes fission yields based on a cutoff energy + + Tally fission rates above and below the cutoff energy. + Assume that all fissions below cutoff energy have use thermal fission + product yield distributions, while all fissions above use a faster + set of yield distributions. + + Uses a limit of 20 MeV for tallying fission. + + Parameters + ---------- + chain_nuclides : iterable of openmc.deplete.Nuclide + Nuclides tracked in the depletion chain. All nuclides are + not required to have fission yield data. + n_bmats : int, optional + Number of burnable materials tracked in the problem + cutoff : float, optional + Cutoff energy in [eV] below which all fissions will be + use thermal yields. All other fissions will use a + faster set of yields. Default: 112 [eV] + thermal_energy : float, optional + Energy of yield data corresponding to thermal yields. + Default: 0.0253 [eV] + fast_energy : float, optional + Energy of yield data corresponding to fast yields. + Default: 500 [kev] + + Attributes + ---------- + n_bmats : int + Number of burnable materials tracked in the problem. + Must be set prior to generating tallies + thermal_yields : dict + Dictionary of the form ``{parent: {product: yield}}`` + with thermal yields + fast_yields : dict + Dictionary of the form ``{parent: {product: yield}}`` + with fast yields + results : numpy.ndarray + Array of fission rate fractions with shape + ``(n_mats, 2, n_nucs)``. ``results[:, 0]`` + corresponds to the fraction of all fissions + that occured below ``cutoff``. The number + of materials in the first axis corresponds + to the number of materials burned by the + :class:`openmc.deplete.Operator` + """ + + def __init__(self, chain_nuclides, n_bmats, cutoff=112.0, + thermal_energy=0.0253, fast_energy=500.0e3): + check_type("cutoff", cutoff, Real) + check_type("thermal_energy", thermal_energy, Real) + check_type("fast_energy", fast_energy, Real) + check_greater_than("thermal_energy", thermal_energy, 0.0, equality=True) + check_greater_than("cutoff", cutoff, thermal_energy, equality=False) + check_greater_than("fast_energy", fast_energy, cutoff, equality=False) + self.n_bmats = n_bmats + super().__init__(chain_nuclides) + self._cutoff = cutoff + self._thermal_yields = {} + self._fast_yields = {} + convert_to_constant = set() + for name, nuc in self._chain_nuclides.items(): + yields = nuc.yield_data + energies = nuc.yield_energies + thermal = yields.get(thermal_energy) + fast = yields.get(fast_energy) + if thermal is None or fast is None: + if cutoff <= energies[0]: + # use lowest energy yields as constant + self._constant_yields[name] = yields[energies[0]] + convert_to_constant.add(name) + continue + if cutoff >= energies[-1]: + # use highest energy yields as constant + self._constant_yields[name] = yields[energies[-1]] + convert_to_constant.add(name) + continue + cutoff_ix = bisect.bisect_left(energies, cutoff) + # find closest energy to requested thermal, fast energies + if thermal is None: + min_E = min(energies[:cutoff_ix], + key=lambda e: abs(e - thermal_energy)) + thermal = yields[min_E] + if fast is None: + min_E = min(energies[cutoff_ix:], + key=lambda e: abs(e - fast_energy)) + fast = yields[min_E] + self._thermal_yields[name] = thermal + self._fast_yields[name] = fast + for name in convert_to_constant: + self._chain_nuclides.pop(name) + + @classmethod + def from_operator(cls, operator, **kwargs): + """Construct a helper from an operator + + All keyword arguments should be identical to their counterpart + in the main ``__init__`` method + + Parameters + ---------- + operator : openmc.deplete.Operator + Operator with a chain and burnable materials + kwargs: + Additional keyword arguments to be used in construction + + Returns + ------- + FissionYieldCutoffHelper + + """ + return cls(operator.chain.nuclides, len(operator.burnable_mats), + **kwargs) + + def generate_tallies(self, materials, mat_indexes): + """Use C API to produce a fission rate tally in burnable materials + + Include a :class:`openmc.capi.EnergyFilter` to tally fission rates + above and below cutoff energy. + + Parameters + ---------- + materials : iterable of :class:`openmc.capi.Material` + Materials to be used in :class:`openmc.capi.MaterialFilter` + mat_indexes : iterable of int + Indices of tallied materials that will have their fission + yields computed by this helper. Necessary as the + :class:`openmc.deplete.Operator` that uses this helper + may only burn a subset of all materials when running + in parallel mode. + """ + super().generate_tallies(materials, mat_indexes) + energy_filter = EnergyFilter([0.0, self._cutoff, self._upper_energy]) + self._fission_rate_tally.filters = ( + self._fission_rate_tally.filters + [energy_filter]) + + def unpack(self): + """Obtain fast and thermal fission fractions from tally""" + if not self._tally_nucs: + self.results = None + return + fission_rates = self._fission_rate_tally.results[..., 1].reshape( + self.n_bmats, 2, len(self._tally_nucs)) + self.results = fission_rates[self._local_indexes] + total_fission = self.results.sum(axis=1) + nz_mat, nz_nuc = total_fission.nonzero() + self.results[nz_mat, :, nz_nuc] /= total_fission[nz_mat, newaxis, nz_nuc] + + def weighted_yields(self, local_mat_index): + """Return fission yields for a specific material + + For nuclides with both yield data above and below + the cutoff energy, the effective yield for nuclide ``A`` + will be a weighted sum of fast and thermal yields. The + weights will be the fraction of ``A`` fission events + in the above and below the cutoff energy. + + If ``A`` has fission product distribution ``F`` + for fast fissions and ``T`` for thermal fissions, and + 70% of ``A`` fissions are considered thermal, then + the effective fission product yield distributions + for ``A`` is ``0.7 * T + 0.3 * F`` + + Parameters + ---------- + local_mat_index : int + Index for specific burnable material. Effective + yields will be produced using + ``self.results[local_mat_index]`` + + Returns + ------- + library : dict + Dictionary of ``{parent: {product: fyield}}`` + """ + yields = self.constant_yields + if not self._tally_nucs: + return yields + rates = self.results[local_mat_index] + # iterate over thermal then fast yields, prefer __mul__ to __rmul__ + for therm_frac, fast_frac, nuc in zip(rates[0], rates[1], self._tally_nucs): + yields[nuc.name] = (self._thermal_yields[nuc.name] * therm_frac + + self._fast_yields[nuc.name] * fast_frac) + return yields + + @property + def thermal_yields(self): + return deepcopy(self._thermal_yields) + + @property + def fast_yields(self): + return deepcopy(self._fast_yields) + + +class AveragedFissionYieldHelper(TalliedFissionYieldHelper): + r"""Class that computes fission yields based on average fission energy + + Computes average energy at which fission events occured with + + .. math:: + + \bar{E} = \frac{ + \int_0^\infty E\sigma_f(E)\phi(E)dE + }{ + \int_0^\infty\sigma_f(E)\phi(E)dE + } + + If the average energy for a nuclide is below the lowest energy + with yield data, that set of fission yields is taken. + Conversely, if the average energy is above the highest energy + with yield data, that set of fission yields is used. + For the case where the average energy is between two sets + of yields, the effective fission yield computed by + linearly interpolating between yields provided at the + nearest energies above and below the average. + + Parameters + ---------- + chain_nuclides : iterable of openmc.deplete.Nuclide + Nuclides tracked in the depletion chain. All nuclides are + not required to have fission yield data. + + Attributes + ---------- + constant_yields : dict of str to :class:`openmc.deplete.FissionYield` + Fission yields for all nuclides that only have one set of + fission yield data. Can be accessed as ``{parent: {product: yield}}`` + results : None or numpy.ndarray + If tallies have been generated and unpacked, then the array will + have shape ``(n_mats, n_tnucs)``, where ``n_mats`` is the number + of materials where fission reactions were tallied and ``n_tnucs`` + is the number of nuclides with multiple sets of fission yields. + Data in the array are the average energy of fission events for + tallied nuclides across burnable materials. + """ + + def __init__(self, chain_nuclides): + super().__init__(chain_nuclides) + self._weighted_tally = None + + def generate_tallies(self, materials, mat_indexes): + """Construct tallies to determine average energy of fissions + + Parameters + ---------- + materials : iterable of :class:`openmc.capi.Material` + Materials to be used in :class:`openmc.capi.MaterialFilter` + mat_indexes : iterable of int + Indices of tallied materials that will have their fission + yields computed by this helper. Necessary as the + :class:`openmc.deplete.Operator` that uses this helper + may only burn a subset of all materials when running + in parallel mode. + """ + super().generate_tallies(materials, mat_indexes) + fission_tally = self._fission_rate_tally + filters = fission_tally.filters + + ene_filter = EnergyFilter([0, self._upper_energy]) + fission_tally.filters = filters + [ene_filter] + + func_filter = EnergyFunctionFilter() + func_filter.set_data((0, self._upper_energy), (0, self._upper_energy)) + weighted_tally = Tally() + weighted_tally.scores = ['fission'] + weighted_tally.filters = filters + [func_filter] + self._weighted_tally = weighted_tally + + def update_tally_nuclides(self, nuclides): + """Tally nuclides with non-zero density and multiple yields + + Must be run after :meth:`generate_tallies`. + + Parameters + ---------- + nuclides : iterable of str + Potential nuclides to be tallied, such as those with + non-zero density at this stage. + + Returns + ------- + nuclides : tuple of str + Union of input nuclides and those that have multiple sets + of yield data. Sorted by nuclide name + + Raises + ------ + AttributeError + If tallies not generated + """ + tally_nucs = super().update_tally_nuclides(nuclides) + self._weighted_tally.nuclides = tally_nucs + return tally_nucs + + def unpack(self): + """Unpack tallies and populate :attr:`results` with average energies""" + if not self._tally_nucs: + self.results = None + return + fission_results = ( + self._fission_rate_tally.results[self._local_indexes, :, 1]) + self.results = ( + self._weighted_tally.results[self._local_indexes, :, 1]).copy() + nz_mat, nz_nuc = fission_results.nonzero() + self.results[nz_mat, nz_nuc] /= fission_results[nz_mat, nz_nuc] + + def weighted_yields(self, local_mat_index): + """Return fission yields for a specific material + + Use the computed average energy of fission + events to determine fission yields. If average + energy is between two sets of yields, linearly + interpolate bewteen the two. + Otherwise take the closet set of yields. + + Parameters + ---------- + local_mat_index : int + Index for specific burnable material. Effective + yields will be produced using + ``self.results[local_mat_index]`` + + Returns + ------- + library : dict + Dictionary of ``{parent: {product: fyield}}`` + """ + if not self._tally_nucs: + return self.constant_yields + mat_yields = {} + average_energies = self.results[local_mat_index] + for avg_e, nuc in zip(average_energies, self._tally_nucs): + nuc_energies = nuc.yield_energies + if avg_e <= nuc_energies[0]: + mat_yields[nuc.name] = nuc.yield_data[nuc_energies[0]] + continue + if avg_e >= nuc_energies[-1]: + mat_yields[nuc.name] = nuc.yield_data[nuc_energies[-1]] + continue + # in-between two energies + # linear search since there are usually ~3 energies + for ix, ene in enumerate(nuc_energies[:-1]): + if nuc_energies[ix + 1] > avg_e: + break + lower, upper = nuc_energies[ix:ix + 2] + fast_frac = (avg_e - lower) / (upper - lower) + mat_yields[nuc.name] = ( + nuc.yield_data[lower] * (1 - fast_frac) + + nuc.yield_data[upper] * fast_frac) + mat_yields.update(self.constant_yields) + return mat_yields + + @classmethod + def from_operator(cls, operator, **kwargs): + """Return a new helper with data from an operator + + All keyword arguments should be identical to their counterpart + in the main ``__init__`` method + + Parameters + ---------- + operator : openmc.deplete.TransportOperator + Operator with a depletion chain + kwargs : + Additional keyword arguments to be used in construction + + Returns + ------- + AveragedFissionYieldHelper + """ + return cls(operator.chain.nuclides) diff --git a/openmc/deplete/integrator/__init__.py b/openmc/deplete/integrator/__init__.py deleted file mode 100644 index db906b3c13..0000000000 --- a/openmc/deplete/integrator/__init__.py +++ /dev/null @@ -1,16 +0,0 @@ -""" -Integrator -=========== - -The integrator subcomponents. -""" - -from .cf4 import * -from .cecm import * -from .celi import * -from .cram import * -from .epc_rk4 import * -from .leqi import * -from .predictor import * -from .si_celi import * -from .si_leqi import * diff --git a/openmc/deplete/integrator/cecm.py b/openmc/deplete/integrator/cecm.py deleted file mode 100644 index 758302b027..0000000000 --- a/openmc/deplete/integrator/cecm.py +++ /dev/null @@ -1,123 +0,0 @@ -"""The CE/CM integrator.""" - -import copy -from collections.abc import Iterable - -from .cram import timed_deplete -from ..results import Results - - -def cecm(operator, timesteps, power=None, power_density=None, print_out=True): - r"""Deplete using the CE/CM algorithm. - - Implements the second order `CE/CM predictor-corrector algorithm - `_. - - "CE/CM" stands for constant extrapolation on predictor and constant - midpoint on corrector. This algorithm is mathematically defined as: - - .. math:: - \begin{aligned} - y' &= A(y, t) y(t) \\ - A_p &= A(y_n, t_n) \\ - y_m &= \text{expm}(A_p h/2) y_n \\ - A_c &= A(y_m, t_n + h/2) \\ - y_{n+1} &= \text{expm}(A_c h) y_n - \end{aligned} - - Parameters - ---------- - operator : openmc.deplete.TransportOperator - The operator object to simulate on. - timesteps : iterable of float - Array of timesteps in units of [s]. Note that values are not cumulative. - power : float or iterable of float, optional - Power of the reactor in [W]. A single value indicates that the power is - constant over all timesteps. An iterable indicates potentially different - power levels for each timestep. For a 2D problem, the power can be given - in [W/cm] as long as the "volume" assigned to a depletion material is - actually an area in [cm^2]. Either `power` or `power_density` must be - specified. - power_density : float or iterable of float, optional - Power density of the reactor in [W/gHM]. It is multiplied by initial - heavy metal inventory to get total power if `power` is not speficied. - print_out : bool, optional - Whether or not to print out time. - - """ - if power is None: - if power_density is None: - raise ValueError( - "Neither power nor power density was specified.") - if not isinstance(power_density, Iterable): - power = power_density*operator.heavy_metal - else: - power = [i*operator.heavy_metal for i in power_density] - - if not isinstance(power, Iterable): - power = [power]*len(timesteps) - - # Generate initial conditions - with operator as vec: - # Initialize time and starting index - if operator.prev_res is None: - t = 0.0 - i_res = 0 - else: - t = operator.prev_res[-1].time[-1] - i_res = len(operator.prev_res) - - chain = operator.chain - - for i, (dt, p) in enumerate(zip(timesteps, power)): - # Get beginning-of-timestep concentrations and reaction rates - # Avoid doing first transport run if already done in previous - # calculation - if i > 0 or operator.prev_res is None: - x = [copy.deepcopy(vec)] - op_results = [operator(x[0], p)] - - else: - # Get initial concentration - x = [operator.prev_res[-1].data[0]] - - # Get rates - op_results = [operator.prev_res[-1]] - op_results[0].rates = op_results[0].rates[0] - - # Set first stage value of keff - op_results[0].k = op_results[0].k[0] - - # Scale reaction rates by ratio of powers - power_res = operator.prev_res[-1].power - ratio_power = p / power_res - op_results[0].rates *= ratio_power[0] - - # Deplete for first half of timestep - proc_time, x_middle = timed_deplete( - chain, x[0], op_results[0].rates, dt/2, print_out) - - # Get middle-of-timestep reaction rates - x.append(x_middle) - op_results.append(operator(x_middle, p)) - - # Deplete for full timestep using beginning-of-step materials - # and middle-of-timestep reaction rates - pt_end, x_end = timed_deplete( - chain, x[0], op_results[1].rates, dt, print_out) - - # Create results, write to disk - Results.save( - operator, x, op_results, [t, t + dt], p, i_res + i, - proc_time + pt_end) - - # Advance time, update vector - t += dt - vec = copy.deepcopy(x_end) - - # Perform one last simulation - x = [copy.deepcopy(vec)] - op_results = [operator(x[0], power[-1])] - - # Create results, write to disk - Results.save(operator, x, op_results, [t, t], p, i_res + len(timesteps)) diff --git a/openmc/deplete/integrator/celi.py b/openmc/deplete/integrator/celi.py deleted file mode 100644 index 2e80c8b739..0000000000 --- a/openmc/deplete/integrator/celi.py +++ /dev/null @@ -1,167 +0,0 @@ -"""The CE/LI CFQ4 integrator.""" - -import copy -from collections.abc import Iterable - -from .cram import timed_deplete -from ..results import Results - - -# Functions to form the special matrix for depletion -def _celi_f1(chain, rates): - return 5/12 * chain.form_matrix(rates[0]) + \ - 1/12 * chain.form_matrix(rates[1]) - - -def _celi_f2(chain, rates): - return 1/12 * chain.form_matrix(rates[0]) + \ - 5/12 * chain.form_matrix(rates[1]) - - -def celi(operator, timesteps, power=None, power_density=None, - print_out=True): - r"""Deplete using the CE/LI CFQ4 algorithm. - - Implements the CE/LI Predictor-Corrector algorithm using the `fourth order - commutator-free integrator `_. - - "CE/LI" stands for constant extrapolation on predictor and linear - interpolation on corrector. This algorithm is mathematically defined as: - - .. math:: - \begin{aligned} - y' &= A(y, t) y(t) \\ - A_0 &= A(y_n, t_n) \\ - y_p &= \text{expm}(h A_0) y_n \\ - A_1 &= A(y_p, t_n + h) \\ - y_{n+1} &= \text{expm}(\frac{h}{12} A_0 + \frac{5h}{12} A1) - \text{expm}(\frac{5h}{12} A_0 + \frac{h}{12} A1) y_n - \end{aligned} - - Parameters - ---------- - operator : openmc.deplete.TransportOperator - The operator object to simulate on. - timesteps : iterable of float - Array of timesteps in units of [s]. Note that values are not cumulative. - power : float or iterable of float, optional - Power of the reactor in [W]. A single value indicates that the power is - constant over all timesteps. An iterable indicates potentially different - power levels for each timestep. For a 2D problem, the power can be given - in [W/cm] as long as the "volume" assigned to a depletion material is - actually an area in [cm^2]. Either `power` or `power_density` must be - specified. - power_density : float or iterable of float, optional - Power density of the reactor in [W/gHM]. It is multiplied by initial - heavy metal inventory to get total power if `power` is not speficied. - print_out : bool, optional - Whether or not to print out time. - """ - if power is None: - if power_density is None: - raise ValueError( - "Neither power nor power density was specified.") - if not isinstance(power_density, Iterable): - power = power_density*operator.heavy_metal - else: - power = [i*operator.heavy_metal for i in power_density] - - if not isinstance(power, Iterable): - power = [power]*len(timesteps) - - # Generate initial conditions - with operator as vec: - # Initialize time and starting index - if operator.prev_res is None: - t = 0.0 - i_res = 0 - else: - t = operator.prev_res[-1].time[-1] - i_res = len(operator.prev_res) - - for i, (dt, p) in enumerate(zip(timesteps, power)): - vec, t, _ = celi_inner(operator, vec, p, i, i_res, t, dt, - print_out) - - # Perform one last simulation - x = [copy.deepcopy(vec)] - op_results = [operator(x[0], power[-1])] - - # Create results, write to disk - Results.save(operator, x, op_results, [t, t], p, i_res + len(timesteps)) - - -def celi_inner(operator, vec, p, i, i_res, t, dt, print_out): - """ The inner loop of CE/LI CFQ4. - - Parameters - ---------- - operator : Operator - The operator object to simulate on. - x : list of nuclide vector - Nuclide vector, beginning of time. - p : float - Power of the reactor in [W] - i : int - Current iteration number. - i_res : int - Starting index, for restart calculation. - t : float - Time at start of step. - dt : float - Time step. - print_out : bool - Whether or not to print out time. - - Returns - ------- - list of numpy.array - Nuclide vector, end of time. - float - Next time - OperatorResult - Operator result from beginning of step. - """ - - chain = operator.chain - - # Get beginning-of-timestep concentrations and reaction rates - # Avoid doing first transport run if already done in previous - # calculation - if i > 0 or operator.prev_res is None: - x = [copy.deepcopy(vec)] - op_results = [operator(x[0], p)] - - else: - # Get initial concentration - x = [operator.prev_res[-1].data[0]] - - # Get rates - op_results = [operator.prev_res[-1]] - op_results[0].rates = op_results[0].rates[0] - - # Set first stage value of keff - op_results[0].k = op_results[0].k[0] - - # Scale reaction rates by ratio of powers - power_res = operator.prev_res[-1].power - ratio_power = p / power_res - op_results[0].rates *= ratio_power[0] - - # Deplete to end - proc_time, x_new = timed_deplete(chain, x[0], op_results[0].rates, dt, print_out) - x.append(x_new) - op_results.append(operator(x[1], p)) - - # Deplete with two matrix exponentials - rates = list(zip(op_results[0].rates, op_results[1].rates)) - time_1, x_end = timed_deplete(chain, x[0], rates, dt, print_out, - matrix_func=_celi_f1) - time_2, x_end = timed_deplete(chain, x_end, rates, dt, print_out, - matrix_func=_celi_f2) - - # Create results, write to disk - Results.save(operator, x, op_results, [t, t + dt], p, i_res + i, proc_time + time_1 + time_2) - - # return updated time and vectors - return x_end, t + dt, op_results[0] diff --git a/openmc/deplete/integrator/cf4.py b/openmc/deplete/integrator/cf4.py deleted file mode 100644 index 8b7f3a05a0..0000000000 --- a/openmc/deplete/integrator/cf4.py +++ /dev/null @@ -1,164 +0,0 @@ -"""The CF4 integrator.""" - -import copy -from collections.abc import Iterable - -from .cram import timed_deplete -from ..results import Results - - -# Functions to form the special matrix for depletion -def _cf4_f1(chain, rates): - return 1/2 * chain.form_matrix(rates) - - -def _cf4_f2(chain, rates): - return -1/2 * chain.form_matrix(rates[0]) + \ - chain.form_matrix(rates[1]) - - -def _cf4_f3(chain, rates): - return 1/4 * chain.form_matrix(rates[0]) + \ - 1/6 * chain.form_matrix(rates[1]) + \ - 1/6 * chain.form_matrix(rates[2]) + \ - -1/12 * chain.form_matrix(rates[3]) - - -def _cf4_f4(chain, rates): - return -1/12 * chain.form_matrix(rates[0]) + \ - 1/6 * chain.form_matrix(rates[1]) + \ - 1/6 * chain.form_matrix(rates[2]) + \ - 1/4 * chain.form_matrix(rates[3]) - - -def cf4(operator, timesteps, power=None, power_density=None, print_out=True): - r"""Deplete using the CF4 algorithm. - - Implements the fourth order `commutator-free Lie algorithm - `_. - This algorithm is mathematically defined as: - - .. math:: - \begin{aligned} - F_1 &= h A(y_0) \\ - y_1 &= \text{expm}(1/2 F_1) y_0 \\ - F_2 &= h A(y_1) \\ - y_2 &= \text{expm}(1/2 F_2) y_0 \\ - F_3 &= h A(y_2) \\ - y_3 &= \text{expm}(-1/2 F_1 + F_3) y_1 \\ - F_4 &= h A(y_3) \\ - y_4 &= \text{expm}( 1/4 F_1 + 1/6 F_2 + 1/6 F_3 - 1/12 F_4) - \text{expm}(-1/12 F_1 + 1/6 F_2 + 1/6 F_3 + 1/4 F_4) y_0 - \end{aligned} - - Parameters - ---------- - operator : openmc.deplete.TransportOperator - The operator object to simulate on. - timesteps : iterable of float - Array of timesteps in units of [s]. Note that values are not cumulative. - power : float or iterable of float, optional - Power of the reactor in [W]. A single value indicates that the power is - constant over all timesteps. An iterable indicates potentially different - power levels for each timestep. For a 2D problem, the power can be given - in [W/cm] as long as the "volume" assigned to a depletion material is - actually an area in [cm^2]. Either `power` or `power_density` must be - specified. - power_density : float or iterable of float, optional - Power density of the reactor in [W/gHM]. It is multiplied by initial - heavy metal inventory to get total power if `power` is not speficied. - print_out : bool, optional - Whether or not to print out time. - """ - if power is None: - if power_density is None: - raise ValueError( - "Neither power nor power density was specified.") - if not isinstance(power_density, Iterable): - power = power_density*operator.heavy_metal - else: - power = [i*operator.heavy_metal for i in power_density] - - if not isinstance(power, Iterable): - power = [power]*len(timesteps) - - # Generate initial conditions - with operator as vec: - # Initialize time and starting index - if operator.prev_res is None: - t = 0.0 - i_res = 0 - else: - t = operator.prev_res[-1].time[-1] - i_res = len(operator.prev_res) - - chain = operator.chain - - for i, (dt, p) in enumerate(zip(timesteps, power)): - # Get beginning-of-timestep concentrations and reaction rates - # Avoid doing first transport run if already done in previous - # calculation - if i > 0 or operator.prev_res is None: - x = [copy.deepcopy(vec)] - op_results = [operator(x[0], p)] - - else: - # Get initial concentration - x = [operator.prev_res[-1].data[0]] - - # Get rates - op_results = [operator.prev_res[-1]] - op_results[0].rates = op_results[0].rates[0] - - # Set first stage value of keff - op_results[0].k = op_results[0].k[0] - - # Scale reaction rates by ratio of powers - power_res = operator.prev_res[-1].power - ratio_power = p / power_res - op_results[0].rates *= ratio_power[0] - - # Step 1: deplete with matrix 1/2*A(y0) - time_1, x_new = timed_deplete( - chain, x[0], op_results[0].rates, dt, print_out, - matrix_func=_cf4_f1) - x.append(x_new) - op_results.append(operator(x_new, p)) - - # Step 2: deplete with matrix 1/2*A(y1) - time_2, x_new = timed_deplete( - chain, x[0], op_results[1].rates, dt, print_out, - matrix_func=_cf4_f1) - x.append(x_new) - op_results.append(operator(x_new, p)) - - # Step 3: deplete with matrix -1/2*A(y0)+A(y2) - rates = list(zip(op_results[0].rates, op_results[2].rates)) - time_3, x_new = timed_deplete( - chain, x[1], rates, dt, print_out, matrix_func=_cf4_f2) - x.append(x_new) - op_results.append(operator(x_new, p)) - - # Step 4: deplete with two matrix exponentials - rates = list(zip(op_results[0].rates, op_results[1].rates, - op_results[2].rates, op_results[3].rates)) - time_4, x_end = timed_deplete( - chain, x[0], rates, dt, print_out, matrix_func=_cf4_f3) - time_5, x_end = timed_deplete( - chain, x_end, rates, dt, print_out, matrix_func=_cf4_f4) - - # Create results, write to disk - Results.save( - operator, x, op_results, [t, t + dt], p, i_res + i, - time_1 + time_2 + time_3 + time_4 + time_5) - - # Advance time, update vector - t += dt - vec = copy.deepcopy(x_end) - - # Perform one last simulation - x = [copy.deepcopy(vec)] - op_results = [operator(x[0], power[-1])] - - # Create results, write to disk - Results.save(operator, x, op_results, [t, t], p, i_res + len(timesteps)) diff --git a/openmc/deplete/integrator/epc_rk4.py b/openmc/deplete/integrator/epc_rk4.py deleted file mode 100644 index 373c323e42..0000000000 --- a/openmc/deplete/integrator/epc_rk4.py +++ /dev/null @@ -1,148 +0,0 @@ -"""The EPC-RK4 integrator.""" - -import copy -from collections.abc import Iterable - -from .cram import timed_deplete -from ..results import Results - - -# Functions to form the special matrix for depletion -def _rk4_f1(chain, rates): - return 1/2 * chain.form_matrix(rates) - -def _rk4_f4(chain, rates): - return 1/6 * chain.form_matrix(rates[0]) + \ - 1/3 * chain.form_matrix(rates[1]) + \ - 1/3 * chain.form_matrix(rates[2]) + \ - 1/6 * chain.form_matrix(rates[3]) - -def epc_rk4(operator, timesteps, power=None, power_density=None, print_out=True): - r"""Deplete using the EPC-RK4 algorithm. - - Implements an extended predictor-corrector algorithm with traditional - Runge-Kutta 4 method. - This algorithm is mathematically defined as: - - .. math:: - \begin{aligned} - F_1 &= h A(y_0) \\ - y_1 &= \text{expm}(1/2 F_1) y_0 \\ - F_2 &= h A(y_1) \\ - y_2 &= \text{expm}(1/2 F_2) y_0 \\ - F_3 &= h A(y_2) \\ - y_3 &= \text{expm}(F_3) y_0 \\ - F_4 &= h A(y_3) \\ - y_4 &= \text{expm}(1/6 F_1 + 1/3 F_2 + 1/3 F_3 + 1/6 F_4) y_0 - \end{aligned} - - Parameters - ---------- - operator : openmc.deplete.TransportOperator - The operator object to simulate on. - timesteps : iterable of float - Array of timesteps in units of [s]. Note that values are not cumulative. - power : float or iterable of float, optional - Power of the reactor in [W]. A single value indicates that the power is - constant over all timesteps. An iterable indicates potentially different - power levels for each timestep. For a 2D problem, the power can be given - in [W/cm] as long as the "volume" assigned to a depletion material is - actually an area in [cm^2]. Either `power` or `power_density` must be - specified. - power_density : float or iterable of float, optional - Power density of the reactor in [W/gHM]. It is multiplied by initial - heavy metal inventory to get total power if `power` is not speficied. - print_out : bool, optional - Whether or not to print out time. - - """ - if power is None: - if power_density is None: - raise ValueError( - "Neither power nor power density was specified.") - if not isinstance(power_density, Iterable): - power = power_density*operator.heavy_metal - else: - power = [i*operator.heavy_metal for i in power_density] - - if not isinstance(power, Iterable): - power = [power]*len(timesteps) - - # Generate initial conditions - with operator as vec: - # Initialize time and starting index - if operator.prev_res is None: - t = 0.0 - i_res = 0 - else: - t = operator.prev_res[-1].time[-1] - i_res = len(operator.prev_res) - - chain = operator.chain - - for i, (dt, p) in enumerate(zip(timesteps, power)): - # Get beginning-of-timestep concentrations and reaction rates - # Avoid doing first transport run if already done in previous - # calculation - if i > 0 or operator.prev_res is None: - x = [copy.deepcopy(vec)] - op_results = [operator(x[0], p)] - - else: - # Get initial concentration - x = [operator.prev_res[-1].data[0]] - - # Get rates - op_results = [operator.prev_res[-1]] - op_results[0].rates = op_results[0].rates[0] - - # Set first stage value of keff - op_results[0].k = op_results[0].k[0] - - # Scale reaction rates by ratio of powers - power_res = operator.prev_res[-1].power - ratio_power = p / power_res - op_results[0].rates *= ratio_power[0] - - # Step 1: deplete with matrix 1/2*A(y0) - time_1, x_new = timed_deplete( - chain, x[0], op_results[0].rates, dt, print_out, - matrix_func=_rk4_f1) - x.append(x_new) - op_results.append(operator(x[1], p)) - - # Step 2: deplete with matrix 1/2*A(y1) - time_2, x_new = timed_deplete( - chain, x[0], op_results[1].rates, dt, print_out, - matrix_func=_rk4_f1) - x.append(x_new) - op_results.append(operator(x[2], p)) - - # Step 3: deplete with matrix A(y2) - time_3, x_new = timed_deplete( - chain, x[0], op_results[2].rates, dt, print_out) - x.append(x_new) - op_results.append(operator(x[3], p)) - - # Step 4: deplete with matrix 1/6*A(y0)+1/3*A(y1)+1/3*A(y2)+1/6*A(y3) - rates = list(zip(op_results[0].rates, op_results[1].rates, - op_results[2].rates, op_results[3].rates)) - time_4, x_end = timed_deplete( - chain, x[0], rates, dt, print_out, matrix_func=_rk4_f4) - - # Create results, write to disk - Results.save( - operator, x, op_results, [t, t + dt], p, i_res + i, - time_1 + time_2 + time_3 + time_4) - - # Advance time, update vector - t += dt - vec = copy.deepcopy(x_end) - - # Perform one last simulation - x = [copy.deepcopy(vec)] - op_results = [operator(x[0], power[-1])] - - # Create results, write to disk - Results.save( - operator, x, op_results, [t, t], p, i_res + len(timesteps)) diff --git a/openmc/deplete/integrator/leqi.py b/openmc/deplete/integrator/leqi.py deleted file mode 100644 index 19b1025e5c..0000000000 --- a/openmc/deplete/integrator/leqi.py +++ /dev/null @@ -1,170 +0,0 @@ -"""The LE/QI CFQ4 integrator.""" - -import copy -from collections.abc import Iterable -from itertools import repeat - -from .celi import celi_inner -from .cram import timed_deplete -from ..results import Results - - -# Functions to form the special matrix for depletion -def _leqi_f1(chain, inputs): - f1 = chain.form_matrix(inputs[0]) - f2 = chain.form_matrix(inputs[1]) - dt_l, dt = inputs[2], inputs[3] - return -dt / (12 * dt_l) * f1 + (dt + 6 * dt_l) / (12 * dt_l) * f2 - - -def _leqi_f2(chain, inputs): - f1 = chain.form_matrix(inputs[0]) - f2 = chain.form_matrix(inputs[1]) - dt_l, dt = inputs[2], inputs[3] - return -5 * dt / (12 * dt_l) * f1 + (5 * dt + 6 * dt_l) / (12 * dt_l) * f2 - - -def _leqi_f3(chain, inputs): - f1 = chain.form_matrix(inputs[0]) - f2 = chain.form_matrix(inputs[1]) - f3 = chain.form_matrix(inputs[2]) - dt_l, dt = inputs[3], inputs[4] - return -dt**2 / (12 * dt_l * (dt + dt_l)) * f1 + \ - (dt**2 + 6*dt*dt_l + 5*dt_l**2) / (12 * dt_l * (dt + dt_l)) * f2 + \ - dt_l / (12 * (dt + dt_l)) * f3 - - -def _leqi_f4(chain, inputs): - f1 = chain.form_matrix(inputs[0]) - f2 = chain.form_matrix(inputs[1]) - f3 = chain.form_matrix(inputs[2]) - dt_l, dt = inputs[3], inputs[4] - return -dt**2 / (12 * dt_l * (dt + dt_l)) * f1 + \ - (dt**2 + 2*dt*dt_l + dt_l**2) / (12 * dt_l * (dt + dt_l)) * f2 + \ - (4 * dt * dt_l + 5 * dt_l**2) / (12 * dt_l * (dt + dt_l)) * f3 - - -def leqi(operator, timesteps, power=None, power_density=None, print_out=True): - r"""Deplete using the LE/QI CFQ4 algorithm. - - Implements the LE/QI Predictor-Corrector algorithm using the `fourth order - commutator-free integrator `_. - - "LE/QI" stands for linear extrapolation on predictor and quadratic - interpolation on corrector. This algorithm is mathematically defined as: - - .. math:: - \begin{aligned} - y' &= A(y, t) y(t) \\ - A_{last} &= A(y_{n-1}, t_n - h_1) \\ - A_0 &= A(y_n, t_n) \\ - F_1 &= \frac{-h_2^2}{12h_1} A_{last} + \frac{h_2(6h_1+h_2)}{12h_1} A_0 \\ - F_2 &= \frac{-5h_2^2}{12h_1} A_{last} + \frac{h_2(6h_1+5h_2)}{12h_1} A_0 \\ - y_p &= \text{expm}(F_2) \text{expm}(F_1) y_n \\ - A_1 &= A(y_p, t_n + h_2) \\ - F_3 &= \frac{-h_2^3}{12 h_1 (h_1 + h_2)} A_{last} + - \frac{h_2 (5 h_1^2 + 6 h_2 h_1 + h_2^2)}{12 h_1 (h_1 + h_2)} A_0 + - \frac{h_2 h_1)}{12 (h_1 + h_2)} A_1 \\ - F_4 &= \frac{-h_2^3}{12 h_1 (h_1 + h_2)} A_{last} + - \frac{h_2 (h_1^2 + 2 h_2 h_1 + h_2^2)}{12 h_1 (h_1 + h_2)} A_0 + - \frac{h_2 (5 h_1^2 + 4 h_2 h_1)}{12 h_1 (h_1 + h_2)} A_1 \\ - y_{n+1} &= \text{expm}(F_4) \text{expm}(F_3) y_n - \end{aligned} - - It is initialized using the CE/LI algorithm. - - Parameters - ---------- - operator : openmc.deplete.TransportOperator - The operator object to simulate on. - timesteps : iterable of float - Array of timesteps in units of [s]. Note that values are not cumulative. - power : float or iterable of float, optional - Power of the reactor in [W]. A single value indicates that the power is - constant over all timesteps. An iterable indicates potentially different - power levels for each timestep. For a 2D problem, the power can be given - in [W/cm] as long as the "volume" assigned to a depletion material is - actually an area in [cm^2]. Either `power` or `power_density` must be - specified. - power_density : float or iterable of float, optional - Power density of the reactor in [W/gHM]. It is multiplied by initial - heavy metal inventory to get total power if `power` is not speficied. - print_out : bool, optional - Whether or not to print out time. - """ - if power is None: - if power_density is None: - raise ValueError( - "Neither power nor power density was specified.") - if not isinstance(power_density, Iterable): - power = power_density*operator.heavy_metal - else: - power = [i*operator.heavy_metal for i in power_density] - - if not isinstance(power, Iterable): - power = [power]*len(timesteps) - - # Generate initial conditions - with operator as vec: - # Initialize time and starting index - if operator.prev_res is None: - t = 0.0 - i_res = 0 - else: - t = operator.prev_res[-1].time[-1] - i_res = len(operator.prev_res) - - chain = operator.chain - - for i, (dt, p) in enumerate(zip(timesteps, power)): - # LE/QI needs the last step results to start - # Perform CE/LI CFQ4 or restore results for the first step - if i == 0: - if i_res <= 1: - dt_l = dt - x_new, t, op_res_last = celi_inner(operator, vec, p, i, - i_res, t, dt, print_out) - continue - else: - dt_l = t - operator.prev_res[-2].time[0] - op_res_last = operator.prev_res[-2] - op_res_last.rates = op_res_last.rates[0] - x_new = operator.prev_res[-1].data[0] - - # Perform remaining LE/QI - x = [copy.deepcopy(x_new)] - op_results = [operator(x[0], p)] - - inputs = list(zip(op_res_last.rates, op_results[0].rates, - repeat(dt_l), repeat(dt))) - time_1, x_new = timed_deplete( - chain, x[0], inputs, dt, print_out, matrix_func=_leqi_f1) - time_2, x_new = timed_deplete( - chain, x_new, inputs, dt, print_out, matrix_func=_leqi_f2) - x.append(x_new) - op_results.append(operator(x[1], p)) - - inputs = list(zip(op_res_last.rates, op_results[0].rates, - op_results[1].rates, repeat(dt_l), repeat(dt))) - time_3, x_new = timed_deplete( - chain, x[0], inputs, dt, print_out, matrix_func=_leqi_f3) - time_4, x_new = timed_deplete( - chain, x_new, inputs, dt, print_out, matrix_func=_leqi_f4) - - # Create results, write to disk - Results.save( - operator, x, op_results, [t, t+dt], p, i_res+i, - time_1 + time_2 + time_3 + time_4) - - # update results - op_res_last = copy.deepcopy(op_results[0]) - t += dt - dt_l = dt - - # Perform one last simulation - x = [copy.deepcopy(x_new)] - op_results = [operator(x[0], power[-1])] - - # Create results, write to disk - Results.save( - operator, x, op_results, [t, t], p, i_res + len(timesteps)) diff --git a/openmc/deplete/integrator/predictor.py b/openmc/deplete/integrator/predictor.py deleted file mode 100644 index b30484c695..0000000000 --- a/openmc/deplete/integrator/predictor.py +++ /dev/null @@ -1,106 +0,0 @@ -"""First-order predictor algorithm.""" - -import copy -from collections.abc import Iterable - -from .cram import timed_deplete -from ..results import Results - - -def predictor(operator, timesteps, power=None, power_density=None, - print_out=True): - r"""Deplete using a first-order predictor algorithm. - - Implements the first-order predictor algorithm. This algorithm is - mathematically defined as: - - .. math:: - \begin{aligned} - y' &= A(y, t) y(t) \\ - A_p &= A(y_n, t_n) \\ - y_{n+1} &= \text{expm}(A_p h) y_n - \end{aligned} - - Parameters - ---------- - operator : openmc.deplete.TransportOperator - The operator object to simulate on. - timesteps : iterable of float - Array of timesteps in units of [s]. Note that values are not cumulative. - power : float or iterable of float, optional - Power of the reactor in [W]. A single value indicates that the power is - constant over all timesteps. An iterable indicates potentially different - power levels for each timestep. For a 2D problem, the power can be given - in [W/cm] as long as the "volume" assigned to a depletion material is - actually an area in [cm^2]. Either `power` or `power_density` must be - specified. - power_density : float or iterable of float, optional - Power density of the reactor in [W/gHM]. It is multiplied by initial - heavy metal inventory to get total power if `power` is not speficied. - print_out : bool, optional - Whether or not to print out time. - - """ - if power is None: - if power_density is None: - raise ValueError( - "Neither power nor power density was specified.") - if not isinstance(power_density, Iterable): - power = power_density*operator.heavy_metal - else: - power = [i*operator.heavy_metal for i in power_density] - - if not isinstance(power, Iterable): - power = [power]*len(timesteps) - - proc_time = None - - # Generate initial conditions - with operator as vec: - # Initialize time and starting index - if operator.prev_res is None: - t = 0.0 - i_res = 0 - else: - t = operator.prev_res[-1].time[-1] - i_res = len(operator.prev_res) - 1 - - chain = operator.chain - - for i, (dt, p) in enumerate(zip(timesteps, power)): - # Get beginning-of-timestep concentrations and reaction rates - # Avoid doing first transport run if already done in previous - # calculation - if i > 0 or operator.prev_res is None: - x = [copy.deepcopy(vec)] - op_results = [operator(x[0], p)] - - # Create results, write to disk - Results.save(operator, x, op_results, [t, t + dt], p, i_res + i, proc_time) - else: - # Get initial concentration - x = [operator.prev_res[-1].data[0]] - - # Get rates - op_results = [operator.prev_res[-1]] - op_results[0].rates = op_results[0].rates[0] - - # Scale reaction rates by ratio of powers - power_res = operator.prev_res[-1].power - ratio_power = p / power_res - op_results[0].rates *= ratio_power[0] - - # Deplete for full timestep - proc_time, x_end = timed_deplete( - chain, x[0], op_results[0].rates, dt, print_out) - - # Advance time, update vector - t += dt - vec = copy.deepcopy(x_end) - - # Perform one last simulation - x = [copy.deepcopy(vec)] - op_results = [operator(x[0], power[-1])] - - # Create results, write to disk - Results.save(operator, x, op_results, [t, t], p, i_res + len(timesteps), proc_time) diff --git a/openmc/deplete/integrator/si_celi.py b/openmc/deplete/integrator/si_celi.py deleted file mode 100644 index 26087c1f2c..0000000000 --- a/openmc/deplete/integrator/si_celi.py +++ /dev/null @@ -1,166 +0,0 @@ -"""The SI-CE/LI CFQ4 integrator.""" - -import copy -from collections.abc import Iterable - -from .cram import timed_deplete -from ..results import Results -from ..abc import OperatorResult -from .celi import _celi_f1, _celi_f2 - - -def si_celi(operator, timesteps, power=None, power_density=None, - print_out=True, m=10): - r"""Deplete using the SI-CE/LI CFQ4 algorithm. - - Implements the Stochastic Implicit CE/LI Predictor-Corrector algorithm using - the `fourth order commutator-free integrator `_. - - Detailed algorithm can be found in Section 3.2 in `Colin Josey's thesis - `_. - - Parameters - ---------- - operator : openmc.deplete.TransportOperator - The operator object to simulate on. - timesteps : iterable of float - Array of timesteps in units of [s]. Note that values are not cumulative. - power : float or iterable of float, optional - Power of the reactor in [W]. A single value indicates that the power is - constant over all timesteps. An iterable indicates potentially different - power levels for each timestep. For a 2D problem, the power can be given - in [W/cm] as long as the "volume" assigned to a depletion material is - actually an area in [cm^2]. Either `power` or `power_density` must be - specified. - power_density : float or iterable of float, optional - Power density of the reactor in [W/gHM]. It is multiplied by initial - heavy metal inventory to get total power if `power` is not speficied. - print_out : bool, optional - Whether or not to print out time. - m : int, optional - Number of stages. - """ - if power is None: - if power_density is None: - raise ValueError( - "Neither power nor power density was specified.") - if not isinstance(power_density, Iterable): - power = power_density*operator.heavy_metal - else: - power = [i*operator.heavy_metal for i in power_density] - - if not isinstance(power, Iterable): - power = [power]*len(timesteps) - - # Generate initial conditions - with operator as vec: - # Initialize time and starting index - if operator.prev_res is None: - t = 0.0 - i_res = 0 - else: - t = operator.prev_res[-1].time[-1] - i_res = len(operator.prev_res) - - # Get the concentrations and reaction rates for the first - # beginning-of-timestep (BOS). Compute with m (stage number) times as - # many neutrons as later simulations for statistics reasons if no - # previous calculation results present - if operator.prev_res is None: - x = [copy.deepcopy(vec)] - if hasattr(operator, "settings"): - operator.settings.particles *= m - op_results = [operator(x[0], power[0])] - if hasattr(operator, "settings"): - operator.settings.particles //= m - else: - # Get initial concentration - x = [operator.prev_res[-1].data[0]] - - # Get rates - op_results = [operator.prev_res[-1]] - op_results[0].rates = op_results[0].rates[0] - - # Set first stage value of keff - op_results[0].k = op_results[0].k[0] - - # Scale reaction rates by ratio of powers - power_res = operator.prev_res[-1].power - ratio_power = power[0] / power_res - op_results[0].rates *= ratio_power[0] - - for i, (dt, p) in enumerate(zip(timesteps, power)): - x, t, op_results = si_celi_inner(operator, x, op_results, p, - i, i_res, t, dt, print_out, m) - - # Create results for last point, write to disk - Results.save( - operator, x, op_results, [t, t], p, i_res + len(timesteps)) - - -def si_celi_inner(operator, x, op_results, p, i, i_res, t, dt, print_out, m=10): - """ The inner loop of SI-CE/LI CFQ4. - - Parameters - ---------- - operator : Operator - The operator object to simulate on. - x : list of nuclide vector - Nuclide vector, beginning of time. - op_results : list of OperatorResult - Operator result at BOS. - p : float - Power of the reactor in [W] - i : int - Current iteration number. - i_res : int - Starting index, for restart calculation. - t : float - Time at start of step. - dt : float - Time step. - print_out : bool - Whether or not to print out time. - m : int, optional - Number of stages. - - Returns - ------- - list of nuclide vector (numpy.array) - Nuclide vector, end of time. - float - Next time - list of OperatorResult - Operator result at end of time. - """ - - chain = operator.chain - - # Deplete to end - proc_time, x_new = timed_deplete( - chain, x[0], op_results[0].rates, dt, print_out) - x.append(x_new) - - for j in range(m + 1): - op_res = operator(x_new, p) - - if j <= 1: - op_res_bar = copy.deepcopy(op_res) - else: - rates = 1/j * op_res.rates + (1 - 1/j) * op_res_bar.rates - k = 1/j * op_res.k + (1 - 1/j) * op_res_bar.k - op_res_bar = OperatorResult(k, rates) - - rates = list(zip(op_results[0].rates, op_res_bar.rates)) - time_1, x_new = timed_deplete( - chain, x[0], rates, dt, print_out, matrix_func=_celi_f1) - time_2, x_new = timed_deplete( - chain, x_new, rates, dt, print_out, matrix_func=_celi_f2) - proc_time += time_1 + time_2 - - # Create results, write to disk - op_results.append(op_res_bar) - Results.save(operator, x, op_results, [t, t+dt], p, i_res+i, proc_time) - - # return updated time and vectors - return [x_new], t + dt, [op_res_bar] diff --git a/openmc/deplete/integrator/si_leqi.py b/openmc/deplete/integrator/si_leqi.py deleted file mode 100644 index 7d01782bae..0000000000 --- a/openmc/deplete/integrator/si_leqi.py +++ /dev/null @@ -1,157 +0,0 @@ -"""The SI-LE/QI CFQ4 integrator.""" - -import copy -from collections.abc import Iterable -from itertools import repeat - -from .si_celi import si_celi_inner -from .leqi import _leqi_f1, _leqi_f2, _leqi_f3, _leqi_f4 -from .cram import timed_deplete -from ..results import Results -from ..abc import OperatorResult - - -def si_leqi(operator, timesteps, power=None, power_density=None, - print_out=True, m=10): - r"""Deplete using the SI-LE/QI CFQ4 algorithm. - - Implements the Stochastic Implicit LE/QI Predictor-Corrector algorithm using - the `fourth order commutator-free integrator `_. - - Detailed algorithm can be found in Section 3.2 in `Colin Josey's thesis - `_. - - Parameters - ---------- - operator : openmc.deplete.TransportOperator - The operator object to simulate on. - timesteps : iterable of float - Array of timesteps in units of [s]. Note that values are not cumulative. - power : float or iterable of float, optional - Power of the reactor in [W]. A single value indicates that the power is - constant over all timesteps. An iterable indicates potentially different - power levels for each timestep. For a 2D problem, the power can be given - in [W/cm] as long as the "volume" assigned to a depletion material is - actually an area in [cm^2]. Either `power` or `power_density` must be - specified. - power_density : float or iterable of float, optional - Power density of the reactor in [W/gHM]. It is multiplied by initial - heavy metal inventory to get total power if `power` is not speficied. - print_out : bool, optional - Whether or not to print out time. - m : int, optional - Number of stages. - """ - if power is None: - if power_density is None: - raise ValueError( - "Neither power nor power density was specified.") - if not isinstance(power_density, Iterable): - power = power_density*operator.heavy_metal - else: - power = [i*operator.heavy_metal for i in power_density] - - if not isinstance(power, Iterable): - power = [power]*len(timesteps) - - # Generate initial conditions - with operator as vec: - # Initialize time and starting index - if operator.prev_res is None: - t = 0.0 - i_res = 0 - else: - t = operator.prev_res[-1].time[-1] - i_res = len(operator.prev_res) - - # Get the concentrations and reaction rates for the first - # beginning-of-timestep (BOS). Compute with m (stage number) times as - # many neutrons as later simulations for statistics reasons if no - # previous calculation results present - if operator.prev_res is None: - x = [copy.deepcopy(vec)] - if hasattr(operator, "settings"): - operator.settings.particles *= m - op_results = [operator(x[0], power[0])] - if hasattr(operator, "settings"): - operator.settings.particles //= m - else: - # Get initial concentration - x = [operator.prev_res[-1].data[0]] - - # Get rates - op_results = [operator.prev_res[-1]] - op_results[0].rates = op_results[0].rates[0] - - # Set first stage value of keff - op_results[0].k = op_results[0].k[0] - - # Scale reaction rates by ratio of powers - power_res = operator.prev_res[-1].power - ratio_power = power[0] / power_res - op_results[0].rates *= ratio_power[0] - - chain = operator.chain - - for i, (dt, p) in enumerate(zip(timesteps, power)): - # LE/QI needs the last step results to start - # Perform SI-CE/LI CFQ4 or restore results for the first step - if i == 0: - dt_l = dt - if i_res <= 1: - op_res_last = copy.deepcopy(op_results[0]) - x, t, op_results = si_celi_inner(operator, x, op_results, p, - i, i_res, t, dt, print_out) - continue - else: - dt_l = t - operator.prev_res[-2].time[0] - op_res_last = operator.prev_res[-2] - op_res_last.rates = op_res_last.rates[0] - x = [operator.prev_res[-1].data[0]] - - # Perform remaining LE/QI - inputs = list(zip(op_res_last.rates, op_results[0].rates, - repeat(dt_l), repeat(dt))) - proc_time, x_new = timed_deplete( - chain, x[0], inputs, dt, print_out, matrix_func=_leqi_f1) - time_1, x_new = timed_deplete( - chain, x_new, inputs, dt, print_out, matrix_func=_leqi_f2) - x.append(x_new) - - proc_time += time_1 - - # Loop on inner - for j in range(m + 1): - op_res = operator(x_new, p) - - if j <= 1: - op_res_bar = copy.deepcopy(op_res) - else: - rates = 1/j * op_res.rates + (1 - 1/j) * op_res_bar.rates - k = 1/j * op_res.k + (1 - 1/j) * op_res_bar.k - op_res_bar = OperatorResult(k, rates) - - inputs = list(zip(op_res_last.rates, op_results[0].rates, - op_res_bar.rates, repeat(dt_l), repeat(dt))) - time_1, x_new = timed_deplete( - chain, x[0], inputs, dt, print_out, matrix_func=_leqi_f3) - time_2, x_new = timed_deplete( - chain, x_new, inputs, dt, print_out, matrix_func=_leqi_f4) - - proc_time += time_1 + time_2 - - # Create results, write to disk - op_results.append(op_res_bar) - Results.save( - operator, x, op_results, [t, t+dt], p, i_res+i, proc_time) - - # update results - x = [x_new] - op_res_last = copy.deepcopy(op_results[0]) - op_results = [op_res_bar] - t += dt - dt_l = dt - - # Create results for last point, write to disk - Results.save( - operator, x, op_results, [t, t], p, i_res+len(timesteps)) diff --git a/openmc/deplete/integrators.py b/openmc/deplete/integrators.py new file mode 100644 index 0000000000..67106aa3e1 --- /dev/null +++ b/openmc/deplete/integrators.py @@ -0,0 +1,836 @@ +import copy +from itertools import repeat + +from .abc import Integrator, SIIntegrator, OperatorResult +from .cram import timed_deplete +from ._matrix_funcs import ( + cf4_f1, cf4_f2, cf4_f3, cf4_f4, celi_f1, celi_f2, + leqi_f1, leqi_f2, leqi_f3, leqi_f4, rk4_f1, rk4_f4 +) + +__all__ = [ + "PredictorIntegrator", "CECMIntegrator", "CF4Integrator", + "CELIIntegrator", "EPCRK4Integrator", "LEQIIntegrator", + "SICELIIntegrator", "SILEQIIntegrator"] + + +class PredictorIntegrator(Integrator): + r"""Deplete using a first-order predictor algorithm. + + Implements the first-order predictor algorithm. This algorithm is + mathematically defined as: + + .. math:: + \begin{aligned} + y' &= A(y, t) y(t) \\ + A_p &= A(y_n, t_n) \\ + y_{n+1} &= \text{expm}(A_p h) y_n + \end{aligned} + + Parameters + ---------- + operator : openmc.deplete.TransportOperator + Operator to perform transport simulations + timesteps : iterable of float + Array of timesteps in units of [s]. Note that values are not + cumulative. + power : float or iterable of float, optional + Power of the reactor in [W]. A single value indicates that + the power is constant over all timesteps. An iterable + indicates potentially different power levels for each timestep. + For a 2D problem, the power can be given in [W/cm] as long + as the "volume" assigned to a depletion material is actually + an area in [cm^2]. Either ``power`` or ``power_density`` must be + specified. + power_density : float or iterable of float, optional + Power density of the reactor in [W/gHM]. It is multiplied by + initial heavy metal inventory to get total power if ``power`` + is not speficied. + + Attributes + ---------- + operator : openmc.deplete.TransportOperator + Operator to perform transport simulations + chain : openmc.deplete.Chain + Depletion chain + timesteps : iterable of float + Size of each depletion interval in [s] + power : iterable of float + Power of the reactor in [W] for each interval in :attr:`timesteps` + """ + _num_stages = 1 + + def __call__(self, conc, rates, dt, power, _i=None): + """Perform the integration across one time step + + Parameters + ---------- + conc : numpy.ndarray + Initial concentrations for all nuclides in [atom] + rates : openmc.deplete.ReactionRates + Reaction rates from operator + dt : float + Time in [s] for the entire depletion interval + power : float + Power of the system in [W] + _i : int or None + Iteration index. Not used + + Returns + ------- + proc_time : float + Time spent in CRAM routines for all materials in [s] + conc_list : list of numpy.ndarray + Concentrations at end of interval + op_results : empty list + Kept for consistency with API. No intermediate calls to + operator with predictor + + """ + proc_time, conc_end = timed_deplete(self.chain, conc, rates, dt) + return proc_time, [conc_end], [] + + +class CECMIntegrator(Integrator): + r"""Deplete using the CE/CM algorithm. + + Implements the second order `CE/CM predictor-corrector algorithm + `_. + + "CE/CM" stands for constant extrapolation on predictor and constant + midpoint on corrector. This algorithm is mathematically defined as: + + .. math:: + \begin{aligned} + y' &= A(y, t) y(t) \\ + A_p &= A(y_n, t_n) \\ + y_m &= \text{expm}(A_p h/2) y_n \\ + A_c &= A(y_m, t_n + h/2) \\ + y_{n+1} &= \text{expm}(A_c h) y_n + \end{aligned} + + Parameters + ---------- + operator : openmc.deplete.TransportOperator + Operator to perform transport simulations + timesteps : iterable of float + Array of timesteps in units of [s]. Note that values are not + cumulative. + power : float or iterable of float, optional + Power of the reactor in [W]. A single value indicates that + the power is constant over all timesteps. An iterable + indicates potentially different power levels for each timestep. + For a 2D problem, the power can be given in [W/cm] as long + as the "volume" assigned to a depletion material is actually + an area in [cm^2]. Either ``power`` or ``power_density`` must be + specified. + power_density : float or iterable of float, optional + Power density of the reactor in [W/gHM]. It is multiplied by + initial heavy metal inventory to get total power if ``power`` + is not speficied. + + Attributes + ---------- + operator : openmc.deplete.TransportOperator + Operator to perform transport simulations + chain : openmc.deplete.Chain + Depletion chain + timesteps : iterable of float + Size of each depletion interval in [s] + power : iterable of float + Power of the reactor in [W] for each interval in :attr:`timesteps` + """ + _num_stages = 2 + + def __call__(self, conc, rates, dt, power, _i=None): + """Integrate using CE/CM + + Parameters + ---------- + conc : numpy.ndarray + Initial concentrations for all nuclides in [atom] + rates : openmc.deplete.ReactionRates + Reaction rates from operator + dt : float + Time in [s] for the entire depletion interval + power : float + Power of the system [W] + _i : int, optional + Current iteration count. Not used + + Returns + ------- + proc_time : float + Time spent in CRAM routines for all materials in [s] + conc_list : list of numpy.ndarray + Concentrations at each of the intermediate points with + the final concentration as the last element + op_results : list of openmc.deplete.OperatorResult + Eigenvalue and reaction rates from transport simulations + """ + # deplete across first half of inteval + time0, x_middle = timed_deplete(self.chain, conc, rates, dt / 2) + res_middle = self.operator(x_middle, power) + + # deplete across entire interval with BOS concentrations, + # MOS reaction rates + time1, x_end = timed_deplete(self.chain, conc, res_middle.rates, dt) + + return time0 + time1, [x_middle, x_end], [res_middle] + + +class CF4Integrator(Integrator): + r"""Deplete using the CF4 algorithm. + + Implements the fourth order `commutator-free Lie algorithm + `_. + This algorithm is mathematically defined as: + + .. math:: + \begin{aligned} + F_1 &= h A(y_0) \\ + y_1 &= \text{expm}(1/2 F_1) y_0 \\ + F_2 &= h A(y_1) \\ + y_2 &= \text{expm}(1/2 F_2) y_0 \\ + F_3 &= h A(y_2) \\ + y_3 &= \text{expm}(-1/2 F_1 + F_3) y_1 \\ + F_4 &= h A(y_3) \\ + y_4 &= \text{expm}( 1/4 F_1 + 1/6 F_2 + 1/6 F_3 - 1/12 F_4) + \text{expm}(-1/12 F_1 + 1/6 F_2 + 1/6 F_3 + 1/4 F_4) y_0 + \end{aligned} + + Parameters + ---------- + operator : openmc.deplete.TransportOperator + Operator to perform transport simulations + timesteps : iterable of float + Array of timesteps in units of [s]. Note that values are not + cumulative. + power : float or iterable of float, optional + Power of the reactor in [W]. A single value indicates that + the power is constant over all timesteps. An iterable + indicates potentially different power levels for each timestep. + For a 2D problem, the power can be given in [W/cm] as long + as the "volume" assigned to a depletion material is actually + an area in [cm^2]. Either ``power`` or ``power_density`` must be + specified. + power_density : float or iterable of float, optional + Power density of the reactor in [W/gHM]. It is multiplied by + initial heavy metal inventory to get total power if ``power`` + is not speficied. + + Attributes + ---------- + operator : openmc.deplete.TransportOperator + Operator to perform transport simulations + chain : openmc.deplete.Chain + Depletion chain + timesteps : iterable of float + Size of each depletion interval in [s] + power : iterable of float + Power of the reactor in [W] for each interval in :attr:`timesteps` + """ + _num_stages = 4 + + def __call__(self, bos_conc, bos_rates, dt, power, _i=None): + """Perform the integration across one time step + + Parameters + ---------- + bos_conc : numpy.ndarray + Initial concentrations for all nuclides in [atom] + bos_rates : openmc.deplete.ReactionRates + Reaction rates from operator + dt : float + Time in [s] for the entire depletion interval + power : float + Power of the system in [W] + _i : int, optional + Current depletion step index. Not used + + Returns + ------- + proc_time : float + Time spent in CRAM routines for all materials in [s] + conc_list : list of numpy.ndarray + Concentrations at each of the intermediate points with + the final concentration as the last element + op_results : list of openmc.deplete.OperatorResult + Eigenvalue and reaction rates from intermediate transport + simulations + """ + # Step 1: deplete with matrix 1/2*A(y0) + time1, conc_eos1 = timed_deplete( + self.chain, bos_conc, bos_rates, dt, matrix_func=cf4_f1) + res1 = self.operator(conc_eos1, power) + + # Step 2: deplete with matrix 1/2*A(y1) + time2, conc_eos2 = timed_deplete( + self.chain, bos_conc, res1.rates, dt, matrix_func=cf4_f1) + res2 = self.operator(conc_eos2, power) + + # Step 3: deplete with matrix -1/2*A(y0)+A(y2) + list_rates = list(zip(bos_rates, res2.rates)) + time3, conc_eos3 = timed_deplete( + self.chain, conc_eos1, list_rates, dt, matrix_func=cf4_f2) + res3 = self.operator(conc_eos3, power) + + # Step 4: deplete with two matrix exponentials + list_rates = list(zip(bos_rates, res1.rates, res2.rates, res3.rates)) + time4, conc_inter = timed_deplete( + self.chain, bos_conc, list_rates, dt, matrix_func=cf4_f3) + time5, conc_eos5 = timed_deplete( + self.chain, conc_inter, list_rates, dt, matrix_func=cf4_f4) + + return (time1 + time2 + time3 + time4 + time5, + [conc_eos1, conc_eos2, conc_eos3, conc_eos5], + [res1, res2, res3]) + + +class CELIIntegrator(Integrator): + r"""Deplete using the CE/LI CFQ4 algorithm. + + Implements the CE/LI Predictor-Corrector algorithm using the `fourth order + commutator-free integrator `_. + + "CE/LI" stands for constant extrapolation on predictor and linear + interpolation on corrector. This algorithm is mathematically defined as: + + .. math:: + \begin{aligned} + y' &= A(y, t) y(t) \\ + A_0 &= A(y_n, t_n) \\ + y_p &= \text{expm}(h A_0) y_n \\ + A_1 &= A(y_p, t_n + h) \\ + y_{n+1} &= \text{expm}(\frac{h}{12} A_0 + \frac{5h}{12} A1) + \text{expm}(\frac{5h}{12} A_0 + \frac{h}{12} A1) y_n + \end{aligned} + + Parameters + ---------- + operator : openmc.deplete.TransportOperator + Operator to perform transport simulations + timesteps : iterable of float + Array of timesteps in units of [s]. Note that values are not + cumulative. + power : float or iterable of float, optional + Power of the reactor in [W]. A single value indicates that + the power is constant over all timesteps. An iterable + indicates potentially different power levels for each timestep. + For a 2D problem, the power can be given in [W/cm] as long + as the "volume" assigned to a depletion material is actually + an area in [cm^2]. Either ``power`` or ``power_density`` must be + specified. + power_density : float or iterable of float, optional + Power density of the reactor in [W/gHM]. It is multiplied by + initial heavy metal inventory to get total power if ``power`` + is not speficied. + + Attributes + ---------- + operator : openmc.deplete.TransportOperator + Operator to perform transport simulations + chain : openmc.deplete.Chain + Depletion chain + timesteps : iterable of float + Size of each depletion interval in [s] + power : iterable of float + Power of the reactor in [W] for each interval in :attr:`timesteps` + """ + _num_stages = 2 + + def __call__(self, bos_conc, rates, dt, power, _i=None): + """Perform the integration across one time step + + Parameters + ---------- + bos_conc : numpy.ndarray + Initial concentrations for all nuclides in [atom] + rates : openmc.deplete.ReactionRates + Reaction rates from operator + dt : float + Time in [s] for the entire depletion interval + power : float + Power of the system in [W] + _i : int, optional + Current iteration count. Not used + + Returns + ------- + proc_time : float + Time spent in CRAM routines for all materials in [s] + conc_list : list of numpy.ndarray + Concentrations at each of the intermediate points with + the final concentration as the last element + op_results : list of openmc.deplete.OperatorResult + Eigenvalue and reaction rates from intermediate transport + simulation + """ + # deplete to end using BOS rates + proc_time, conc_ce = timed_deplete(self.chain, bos_conc, rates, dt) + res_ce = self.operator(conc_ce, power) + + # deplete using two matrix exponentials + list_rates = list(zip(rates, res_ce.rates)) + + time_le1, conc_inter = timed_deplete( + self.chain, bos_conc, list_rates, dt, matrix_func=celi_f1) + + time_le2, conc_end = timed_deplete( + self.chain, conc_inter, list_rates, dt, matrix_func=celi_f2) + + return proc_time + time_le1 + time_le1, [conc_ce, conc_end], [res_ce] + + +class EPCRK4Integrator(Integrator): + r"""Deplete using the EPC-RK4 algorithm. + + Implements an extended predictor-corrector algorithm with traditional + Runge-Kutta 4 method. This algorithm is mathematically defined as: + + .. math:: + \begin{aligned} + F_1 &= h A(y_0) \\ + y_1 &= \text{expm}(1/2 F_1) y_0 \\ + F_2 &= h A(y_1) \\ + y_2 &= \text{expm}(1/2 F_2) y_0 \\ + F_3 &= h A(y_2) \\ + y_3 &= \text{expm}(F_3) y_0 \\ + F_4 &= h A(y_3) \\ + y_4 &= \text{expm}(1/6 F_1 + 1/3 F_2 + 1/3 F_3 + 1/6 F_4) y_0 + \end{aligned} + + Parameters + ---------- + operator : openmc.deplete.TransportOperator + Operator to perform transport simulations + timesteps : iterable of float + Array of timesteps in units of [s]. Note that values are not + cumulative. + power : float or iterable of float, optional + Power of the reactor in [W]. A single value indicates that + the power is constant over all timesteps. An iterable + indicates potentially different power levels for each timestep. + For a 2D problem, the power can be given in [W/cm] as long + as the "volume" assigned to a depletion material is actually + an area in [cm^2]. Either ``power`` or ``power_density`` must be + specified. + power_density : float or iterable of float, optional + Power density of the reactor in [W/gHM]. It is multiplied by + initial heavy metal inventory to get total power if ``power`` + is not speficied. + + Attributes + ---------- + operator : openmc.deplete.TransportOperator + Operator to perform transport simulations + chain : openmc.deplete.Chain + Depletion chain + timesteps : iterable of float + Size of each depletion interval in [s] + power : iterable of float + Power of the reactor in [W] for each interval in :attr:`timesteps` + """ + _num_stages = 4 + + def __call__(self, conc, rates, dt, power, _i=None): + """Perform the integration across one time step + + Parameters + ---------- + conc : numpy.ndarray + Initial concentrations for all nuclides in [atom] + rates : openmc.deplete.ReactionRates + Reaction rates from operator + dt : float + Time in [s] for the entire depletion interval + power : float + Power of the system in [W] + _i : int, optional + Current depletion step index, unused. + + Returns + ------- + proc_time : float + Time spent in CRAM routines for all materials in [s] + conc_list : list of numpy.ndarray + Concentrations at each of the intermediate points with + the final concentration as the last element + op_results : list of openmc.deplete.OperatorResult + Eigenvalue and reaction rates from intermediate transport + simulations + """ + + # Step 1: deplete with matrix A(y0) / 2 + time1, conc1 = timed_deplete( + self.chain, conc, rates, dt, matrix_func=rk4_f1) + res1 = self.operator(conc1, power) + + # Step 2: deplete with matrix A(y1) / 2 + time2, conc2 = timed_deplete( + self.chain, conc, res1.rates, dt, matrix_func=rk4_f1) + res2 = self.operator(conc2, power) + + # Step 3: deplete with matrix A(y2) + time3, conc3 = timed_deplete( + self.chain, conc, res2.rates, dt) + res3 = self.operator(conc3, power) + + # Step 4: deplete with matrix built from weighted rates + list_rates = list(zip(rates, res1.rates, res2.rates, res3.rates)) + time4, conc4 = timed_deplete( + self.chain, conc, list_rates, dt, matrix_func=rk4_f4) + + return (time1 + time2 + time3 + time4, [conc1, conc2, conc3, conc4], + [res1, res2, res3]) + + +class LEQIIntegrator(Integrator): + r"""Deplete using the LE/QI CFQ4 algorithm. + + Implements the LE/QI Predictor-Corrector algorithm using the `fourth order + commutator-free integrator `_. + + "LE/QI" stands for linear extrapolation on predictor and quadratic + interpolation on corrector. This algorithm is mathematically defined as: + + .. math:: + \begin{aligned} + y' &= A(y, t) y(t) \\ + A_{last} &= A(y_{n-1}, t_n - h_1) \\ + A_0 &= A(y_n, t_n) \\ + F_1 &= \frac{-h_2^2}{12h_1} A_{last} + \frac{h_2(6h_1+h_2)}{12h_1} A_0 \\ + F_2 &= \frac{-5h_2^2}{12h_1} A_{last} + \frac{h_2(6h_1+5h_2)}{12h_1} A_0 \\ + y_p &= \text{expm}(F_2) \text{expm}(F_1) y_n \\ + A_1 &= A(y_p, t_n + h_2) \\ + F_3 &= \frac{-h_2^3}{12 h_1 (h_1 + h_2)} A_{last} + + \frac{h_2 (5 h_1^2 + 6 h_2 h_1 + h_2^2)}{12 h_1 (h_1 + h_2)} A_0 + + \frac{h_2 h_1)}{12 (h_1 + h_2)} A_1 \\ + F_4 &= \frac{-h_2^3}{12 h_1 (h_1 + h_2)} A_{last} + + \frac{h_2 (h_1^2 + 2 h_2 h_1 + h_2^2)}{12 h_1 (h_1 + h_2)} A_0 + + \frac{h_2 (5 h_1^2 + 4 h_2 h_1)}{12 h_1 (h_1 + h_2)} A_1 \\ + y_{n+1} &= \text{expm}(F_4) \text{expm}(F_3) y_n + \end{aligned} + + It is initialized using the CE/LI algorithm. + + Parameters + ---------- + operator : openmc.deplete.TransportOperator + Operator to perform transport simulations + timesteps : iterable of float + Array of timesteps in units of [s]. Note that values are not + cumulative. + power : float or iterable of float, optional + Power of the reactor in [W]. A single value indicates that + the power is constant over all timesteps. An iterable + indicates potentially different power levels for each timestep. + For a 2D problem, the power can be given in [W/cm] as long + as the "volume" assigned to a depletion material is actually + an area in [cm^2]. Either ``power`` or ``power_density`` must be + specified. + power_density : float or iterable of float, optional + Power density of the reactor in [W/gHM]. It is multiplied by + initial heavy metal inventory to get total power if ``power`` + is not speficied. + + Attributes + ---------- + operator : openmc.deplete.TransportOperator + Operator to perform transport simulations + chain : openmc.deplete.Chain + Depletion chain + timesteps : iterable of float + Size of each depletion interval in [s] + power : iterable of float + Power of the reactor in [W] for each interval in :attr:`timesteps` + """ + _num_stages = 2 + + def __call__(self, bos_conc, bos_rates, dt, power, i): + """Perform the integration across one time step + + Parameters + ---------- + conc : numpy.ndarray + Initial concentrations for all nuclides in [atom] + rates : openmc.deplete.ReactionRates + Reaction rates from operator + dt : float + Time in [s] for the entire depletion interval + power : float + Power of the system in [W] + i : int + Current depletion step index + + Returns + ------- + proc_time : float + Time spent in CRAM routines for all materials in [s] + conc_list : list of numpy.ndarray + Concentrations at each of the intermediate points with + the final concentration as the last element + op_results : list of openmc.deplete.OperatorResult + Eigenvalue and reaction rates from intermediate transport + simulation + """ + if i == 0: + if self._i_res < 1: # need at least previous transport solution + self._prev_rates = bos_rates + return CELIIntegrator.__call__( + self, bos_conc, bos_rates, dt, power, i) + prev_res = self.operator.prev_res[-2] + prev_dt = self.timesteps[i] - prev_res.time[0] + self._prev_rates = prev_res.rates[0] + else: + prev_dt = self.timesteps[i - 1] + + # Remaining LE/QI + bos_res = self.operator(bos_conc, power) + + le_inputs = list(zip( + self._prev_rates, bos_res.rates, repeat(prev_dt), repeat(dt))) + + time1, conc_inter = timed_deplete( + self.chain, bos_conc, le_inputs, dt, matrix_func=leqi_f1) + time2, conc_eos0 = timed_deplete( + self.chain, conc_inter, le_inputs, dt, matrix_func=leqi_f2) + + res_inter = self.operator(conc_eos0, power) + + qi_inputs = list(zip( + self._prev_rates, bos_res.rates, res_inter.rates, + repeat(prev_dt), repeat(dt))) + + time3, conc_inter = timed_deplete( + self.chain, bos_conc, qi_inputs, dt, matrix_func=leqi_f3) + time4, conc_eos1 = timed_deplete( + self.chain, conc_inter, qi_inputs, dt, matrix_func=leqi_f4) + + # store updated rates + self._prev_rates = copy.deepcopy(bos_res.rates) + + return ( + time1 + time2 + time3 + time4, [conc_eos0, conc_eos1], + [bos_res, res_inter]) + + +class SICELIIntegrator(SIIntegrator): + r"""Deplete using the SI-CE/LI CFQ4 algorithm. + + Implements the stochastic implicit CE/LI predictor-corrector algorithm + using the `fourth order commutator-free integrator + `_. + + Detailed algorithm can be found in section 3.2 in `Colin Josey's thesis + `_. + + Parameters + ---------- + operator : openmc.deplete.TransportOperator + The operator object to simulate on. + timesteps : iterable of float + Array of timesteps in units of [s]. Note that values are not + cumulative. + power : float or iterable of float, optional + Power of the reactor in [W]. A single value indicates that + the power is constant over all timesteps. An iterable + indicates potentially different power levels for each timestep. + For a 2D problem, the power can be given in [W/cm] as long + as the "volume" assigned to a depletion material is actually + an area in [cm^2]. Either ``power`` or ``power_density`` must be + specified. + power_density : float or iterable of float, optional + Power density of the reactor in [W/gHM]. It is multiplied by + initial heavy metal inventory to get total power if ``power`` + is not speficied. + n_steps : int, optional + Number of stochastic iterations per depletion interval. + Must be greater than zero. Default : 10 + + Attributes + ---------- + operator : openmc.deplete.TransportOperator + Operator to perform transport simulations + chain : openmc.deplete.Chain + Depletion chain + timesteps : iterable of float + Size of each depletion interval in [s] + power : iterable of float + Power of the reactor in [W] for each interval in :attr:`timesteps` + n_steps : int + Number of stochastic iterations per depletion interval + """ + _num_stages = 2 + + def __call__(self, bos_conc, bos_rates, dt, power, _i=None): + """Perform the integration across one time step + + Parameters + ---------- + bos_conc : numpy.ndarray + Initial bos_concentrations for all nuclides in [atom] + bos_rates : openmc.deplete.ReactionRates + Reaction rates from operator + dt : float + Time in [s] for the entire depletion interval + power : float + Power of the system in [W] + _i : int, optional + Current depletion step index. Not used + + Returns + ------- + proc_time : float + Time spent in CRAM routines for all materials in [s] + bos_conc_list : list of numpy.ndarray + Concentrations at each of the intermediate points with + the final bos_concentration as the last element + op_results : list of openmc.deplete.OperatorResult + Eigenvalue and reaction rates from intermediate transport + simulations + """ + proc_time, eos_conc = timed_deplete( + self.chain, bos_conc, bos_rates, dt) + inter_conc = copy.deepcopy(eos_conc) + + # Begin iteration + for j in range(self.n_steps + 1): + inter_res = self.operator(inter_conc, power) + + if j <= 1: + res_bar = copy.deepcopy(inter_res) + else: + rates = 1/j * inter_res.rates + (1 - 1 / j) * res_bar.rates + k = 1/j * inter_res.k + (1 - 1 / j) * res_bar.k + res_bar = OperatorResult(k, rates) + + list_rates = list(zip(bos_rates, res_bar.rates)) + time1, inter_conc = timed_deplete( + self.chain, bos_conc, list_rates, dt, matrix_func=celi_f1) + time2, inter_conc = timed_deplete( + self.chain, inter_conc, list_rates, dt, matrix_func=celi_f2) + proc_time += time1 + time2 + + # end iteration + return proc_time, [eos_conc, inter_conc], [res_bar] + + +class SILEQIIntegrator(SIIntegrator): + r"""Deplete using the SI-LE/QI CFQ4 algorithm. + + Implements the Stochastic Implicit LE/QI Predictor-Corrector algorithm + using the `fourth order commutator-free integrator + `_. + + Detailed algorithm can be found in Section 3.2 in `Colin Josey's thesis + `_. + + Parameters + ---------- + operator : openmc.deplete.TransportOperator + The operator object to simulate on. + timesteps : iterable of float + Array of timesteps in units of [s]. Note that values are not + cumulative. + power : float or iterable of float, optional + Power of the reactor in [W]. A single value indicates that + the power is constant over all timesteps. An iterable + indicates potentially different power levels for each timestep. + For a 2D problem, the power can be given in [W/cm] as long + as the "volume" assigned to a depletion material is actually + an area in [cm^2]. Either ``power`` or ``power_density`` must be + specified. + power_density : float or iterable of float, optional + Power density of the reactor in [W/gHM]. It is multiplied by + initial heavy metal inventory to get total power if ``power`` + is not speficied. + n_steps : int, optional + Number of stochastic iterations per depletion interval. + Must be greater than zero. Default : 10 + + Attributes + ---------- + operator : openmc.deplete.TransportOperator + Operator to perform transport simulations + chain : openmc.deplete.Chain + Depletion chain + timesteps : iterable of float + Size of each depletion interval in [s] + power : iterable of float + Power of the reactor in [W] for each interval in :attr:`timesteps` + n_steps : int + Number of stochastic iterations per depletion interval + """ + _num_stages = 2 + + def __call__(self, bos_conc, bos_rates, dt, power, i): + """Perform the integration across one time step + + Parameters + ---------- + bos_conc : list of numpy.ndarray + Initial concentrations for all nuclides in [atom] for + all depletable materials + bos_rates : list of openmc.deplete.ReactionRates + Reaction rates from operator for all depletable materials + dt : float + Time in [s] for the entire depletion interval + power : float + Power of the system in [W] + i : int + Current depletion step index + + Returns + ------- + proc_time : float + Time spent in CRAM routines for all materials in [s] + conc_list : list of numpy.ndarray + Concentrations at each of the intermediate points with + the final concentration as the last element + op_results : list of openmc.deplete.OperatorResult + Eigenvalue and reaction rates from intermediate transport + simulation + """ + if i == 0: + if self._i_res < 1: + self._prev_rates = bos_rates + # Perform CELI for initial steps + return SICELIIntegrator.__call__( + self, bos_conc, bos_rates, dt, power, i) + prev_res = self.operator.prev_res[-2] + prev_dt = self.timesteps[i] - prev_res.time[0] + self._prev_rates = prev_res.rates[0] + else: + prev_dt = self.timesteps[i - 1] + + # Perform remaining LE/QI + inputs = list(zip(self._prev_rates, bos_rates, + repeat(prev_dt), repeat(dt))) + proc_time, inter_conc = timed_deplete( + self.chain, bos_conc, inputs, dt, matrix_func=leqi_f1) + time1, eos_conc = timed_deplete( + self.chain, inter_conc, inputs, dt, matrix_func=leqi_f2) + + proc_time += time1 + inter_conc = copy.deepcopy(eos_conc) + + for j in range(self.n_steps + 1): + inter_res = self.operator(inter_conc, power) + + if j <= 1: + res_bar = copy.deepcopy(inter_res) + else: + rates = 1 / j * inter_res.rates + (1 - 1 / j) * res_bar.rates + k = 1 / j * inter_res.k + (1 - 1 / j) * res_bar.k + res_bar = OperatorResult(k, rates) + + inputs = list(zip(self._prev_rates, bos_rates, res_bar.rates, + repeat(prev_dt), repeat(dt))) + time1, inter_conc = timed_deplete( + self.chain, bos_conc, inputs, dt, matrix_func=leqi_f3) + time2, inter_conc = timed_deplete( + self.chain, inter_conc, inputs, dt, matrix_func=leqi_f4) + proc_time += time1 + time2 + + return proc_time, [eos_conc, inter_conc], [res_bar] diff --git a/openmc/deplete/nuclide.py b/openmc/deplete/nuclide.py index 88d5ac6eae..8896180a88 100644 --- a/openmc/deplete/nuclide.py +++ b/openmc/deplete/nuclide.py @@ -3,12 +3,20 @@ Contains the per-nuclide components of a depletion chain. """ -from collections import namedtuple +import bisect +from collections.abc import Mapping +from collections import namedtuple, defaultdict +from warnings import warn +from numbers import Real try: import lxml.etree as ET except ImportError: import xml.etree.ElementTree as ET +from numpy import empty + +from openmc.checkvalue import check_type + DecayTuple = namedtuple('DecayTuple', 'type target branching_ratio') DecayTuple.__doc__ = """\ @@ -61,11 +69,16 @@ except AttributeError: class Nuclide(object): """Decay modes, reactions, and fission yields for a single nuclide. + Parameters + ---------- + name : str, optional + GND name of this nuclide, e.g. ``"He4"``, ``"Am242_m1"`` + Attributes ---------- - name : str + name : str or None Name of nuclide. - half_life : float + half_life : float or None Half life of nuclide in [s]. decay_energy : float Energy deposited from decay in [eV]. @@ -79,17 +92,16 @@ class Nuclide(object): reactions : list of openmc.deplete.ReactionTuple Reaction information. Each element of the list is a named tuple with attribute 'type', 'target', 'Q', and 'branching_ratio'. - yield_data : dict of float to list - Maps tabulated energy to list of (product, yield) for all - neutron-induced fission products. - yield_energies : list of float - Energies at which fission product yiels exist - + yield_data : FissionYieldDistribution or None + Fission product yields at tabulated energies for this nuclide. Can be + treated as a nested dictionary ``{energy: {product: yield}}`` + yield_energies : tuple of float or None + Energies at which fission product yields exist """ - def __init__(self): + def __init__(self, name=None): # Information about the nuclide - self.name = None + self.name = name self.half_life = None self.decay_energy = 0.0 @@ -100,8 +112,7 @@ class Nuclide(object): self.reactions = [] # Neutron fission yields, if present - self.yield_data = {} - self.yield_energies = [] + self._yield_data = None @property def n_decay_modes(self): @@ -111,6 +122,29 @@ class Nuclide(object): def n_reaction_paths(self): return len(self.reactions) + @property + def yield_data(self): + if self._yield_data is None: + return None + return self._yield_data + + @yield_data.setter + def yield_data(self, fission_yields): + if fission_yields is None: + self._yield_data = None + else: + check_type("fission_yields", fission_yields, Mapping) + if isinstance(fission_yields, FissionYieldDistribution): + self._yield_data = fission_yields + else: + self._yield_data = FissionYieldDistribution(fission_yields) + + @property + def yield_energies(self): + if self._yield_data is None: + return None + return self.yield_data.energies + @classmethod def from_xml(cls, element, fission_q=None): """Read nuclide from an XML element. @@ -165,13 +199,7 @@ class Nuclide(object): fpy_elem = element.find('neutron_fission_yields') if fpy_elem is not None: - for yields_elem in fpy_elem.iter('fission_yields'): - E = float(yields_elem.get('energy')) - products = yields_elem.find('products').text.split() - yields = [float(y) for y in - yields_elem.find('data').text.split()] - nuc.yield_data[E] = list(zip(products, yields)) - nuc.yield_energies = list(sorted(nuc.yield_data.keys())) + nuc.yield_data = FissionYieldDistribution.from_xml_element(fpy_elem) return nuc @@ -211,14 +239,328 @@ class Nuclide(object): fpy_elem = ET.SubElement(elem, 'neutron_fission_yields') energy_elem = ET.SubElement(fpy_elem, 'energies') energy_elem.text = ' '.join(str(E) for E in self.yield_energies) - - for E in self.yield_energies: - yields_elem = ET.SubElement(fpy_elem, 'fission_yields') - yields_elem.set('energy', str(E)) - - products_elem = ET.SubElement(yields_elem, 'products') - products_elem.text = ' '.join(x[0] for x in self.yield_data[E]) - data_elem = ET.SubElement(yields_elem, 'data') - data_elem.text = ' '.join(str(x[1]) for x in self.yield_data[E]) + self.yield_data.to_xml_element(fpy_elem) return elem + + def validate(self, strict=True, quiet=False, tolerance=1e-4): + """Search for possible inconsistencies + + The following checks are performed: + + 1) for all non-fission reactions and decay modes, + does the sum of branching ratios equal about one? + 2) for fission reactions, does the sum of fission yield + fractions equal about two? + + Parameters + ---------- + strict : bool, optional + Raise exceptions at the first inconsistency if true. + Otherwise mark a warning + quiet : bool, optional + Flag to suppress warnings and return immediately at + the first inconsistency. Used only if + ``strict`` does not evaluate to ``True``. + tolerance : float, optional + Absolute tolerance for comparisons. Used to compare computed + value ``x`` to intended value ``y`` as:: + + valid = (y - tolerance <= x <= y + tolerance) + + Returns + ------- + valid : bool + True if no inconsistencies were found + + Raises + ------ + ValueError + If ``strict`` evaluates to ``True`` and an inconistency was + found + + See Also + -------- + openmc.deplete.Chain.validate + """ + + msg_func = ("Nuclide {name} has {prop} that sum to {actual} " + "instead of {expected} +/- {tol:7.4e}").format + valid = True + + # check decay modes + if self.decay_modes: + sum_br = sum(m.branching_ratio for m in self.decay_modes) + stat = 1.0 - tolerance <= sum_br <= 1.0 + tolerance + if not stat: + msg = msg_func( + name=self.name, actual=sum_br, expected=1.0, tol=tolerance, + prop="decay mode branch ratios") + if strict: + raise ValueError(msg) + elif quiet: + return False + warn(msg) + valid = False + + if self.reactions: + type_map = defaultdict(set) + for reaction in self.reactions: + type_map[reaction.type].add(reaction) + for rxn_type, reactions in type_map.items(): + sum_rxn = sum(rx.branching_ratio for rx in reactions) + stat = 1.0 - tolerance <= sum_rxn <= 1.0 + tolerance + if stat: + continue + msg = msg_func( + name=self.name, actual=sum_br, expected=1.0, tol=tolerance, + prop="{} reaction branch ratios".format(rxn_type)) + if strict: + raise ValueError(msg) + elif quiet: + return False + warn(msg) + valid = False + + if self.yield_data: + for energy, fission_yield in self.yield_data.items(): + sum_yield = fission_yield.yields.sum() + stat = 2.0 - tolerance <= sum_yield <= 2.0 + tolerance + if stat: + continue + msg = msg_func( + name=self.name, actual=sum_yield, + expected=2.0, tol=tolerance, + prop="fission yields (E = {:7.4e} eV)".format(energy)) + if strict: + raise ValueError(msg) + elif quiet: + return False + warn(msg) + valid = False + + return valid + + +class FissionYieldDistribution(Mapping): + """Energy-dependent fission product yields for a single nuclide + + Can be used as a dictionary mapping energies and products to fission + yields:: + + >>> fydist = FissionYieldDistribution{ + ... {0.0253: {"Xe135": 0.021}}) + >>> fydist[0.0253]["Xe135"] + 0.021 + + Parameters + ---------- + fission_yields : dict + Dictionary of energies and fission product yields for that energy. + Expected to be of the form ``{float: {str: float}}``. The first + float is the energy, typically in eV, that represents this + distribution. The underlying dictionary maps fission products + to their respective yields. + + Attributes + ---------- + energies : tuple + Energies for which fission yields exist. Sorted by + increasing energy + products : tuple + Fission products produced at all energies. Sorted by name. + yield_matrix : numpy.ndarray + Array ``(n_energy, n_products)`` where + ``yield_matrix[g, j]`` is the fission yield of product + ``j`` for energy group ``g``. + + See Also + -------- + * :meth:`from_xml_element` - Construction methods + * :class:`FissionYield` - Class used for storing yields at a given energy + """ + + def __init__(self, fission_yields): + # mapping {energy: {product: value}} + energies = sorted(fission_yields) + + # Get a consistent set of products to produce a matrix of yields + shared_prod = set.union(*(set(x) for x in fission_yields.values())) + ordered_prod = sorted(shared_prod) + + yield_matrix = empty((len(energies), len(shared_prod))) + + for g_index, energy in enumerate(energies): + prod_map = fission_yields[energy] + for prod_ix, product in enumerate(ordered_prod): + yield_val = prod_map.get(product) + yield_matrix[g_index, prod_ix] = ( + 0.0 if yield_val is None else yield_val) + self.energies = tuple(energies) + self.products = tuple(ordered_prod) + self.yield_matrix = yield_matrix + + def __len__(self): + return len(self.energies) + + def __getitem__(self, energy): + if energy not in self.energies: + raise KeyError(energy) + return FissionYield( + self.products, self.yield_matrix[self.energies.index(energy)]) + + def __iter__(self): + return iter(self.energies) + + def __repr__(self): + return "<{} with {} products at {} energies>".format( + self.__class__.__name__, self.yield_matrix.shape[1], + len(self.energies)) + + @classmethod + def from_xml_element(cls, element): + """Construct a distribution from a depletion chain xml file + + Parameters + ---------- + element : xml.etree.ElementTree.Element + XML element to pull fission yield data from + + Returns + ------- + FissionYieldDistribution + """ + all_yields = {} + for elem_index, yield_elem in enumerate(element.iter("fission_yields")): + energy = float(yield_elem.get("energy")) + products = yield_elem.find("products").text.split() + yields = map(float, yield_elem.find("data").text.split()) + # Get a map of products to their corresponding yield + all_yields[energy] = dict(zip(products, yields)) + + return cls(all_yields) + + def to_xml_element(self, root): + """Write fission yield data to an xml element + + Parameters + ---------- + root : xml.etree.ElementTree.Element + Element to write distribution data to + """ + for energy, yield_obj in self.items(): + yield_element = ET.SubElement(root, "fission_yields") + yield_element.set("energy", str(energy)) + product_elem = ET.SubElement(yield_element, "products") + product_elem.text = " ".join(map(str, yield_obj.products)) + data_elem = ET.SubElement(yield_element, "data") + data_elem.text = " ".join(map(str, yield_obj.yields)) + + +class FissionYield(Mapping): + """Mapping for fission yields of a parent at a specific energy + + Separated to support nested dictionary-like behavior for + :class:`FissionYieldDistribution`, and allowing math operations + on a single vector of yields. Can in turn be used like a + dictionary to fetch fission yields. + Supports multiplication of a scalar to scale the fission + yields and addition of another set of yields. + + Does not support resizing / inserting new products that do + not exist. + + Parameters + ---------- + products : tuple of str + Products for this specific distribution + yields : numpy.ndarray + Fission product yields for each product in ``products`` + + Attributes + ---------- + products : tuple of str + Products for this specific distribution + yields : numpy.ndarray + Fission product yields for each product in ``products`` + + Examples + -------- + >>> import numpy + >>> fy_vector = FissionYield( + ... ("Xe135", "I129", "Sm149"), + ... numpy.array((0.002, 0.001, 0.0003))) + >>> fy_vector["Xe135"] + 0.002 + >>> new = fy_vector.copy() + >>> fy_vector *= 2 + >>> fy_vector["Xe135"] + 0.004 + >>> new["Xe135"] + 0.002 + >>> (new + fy_vector)["Sm149"] + 0.0009 + >>> dict(new) + {"Xe135": 0.002, "I129": 0.001, "Sm149": 0.0003} + """ + + def __init__(self, products, yields): + self.products = products + self.yields = yields + + def __contains__(self, product): + ix = bisect.bisect_left(self.products, product) + return ix != len(self.products) and self.products[ix] == product + + def __getitem__(self, product): + ix = bisect.bisect_left(self.products, product) + if ix == len(self.products) or self.products[ix] != product: + raise KeyError(product) + return self.yields[ix] + + def __len__(self): + return len(self.products) + + def __iter__(self): + return iter(self.products) + + def items(self): + """Return pairs of product, yield""" + return zip(self.products, self.yields) + + def __add__(self, other): + if not isinstance(other, FissionYield): + return NotImplemented + new = FissionYield(self.products, self.yields.copy()) + new += other + return new + + def __iadd__(self, other): + """Increment value from other fission yield""" + if not isinstance(other, FissionYield): + return NotImplemented + self.yields += other.yields + return self + + def __radd__(self, other): + return self + other + + def __imul__(self, scalar): + if not isinstance(scalar, Real): + return NotImplemented + self.yields *= scalar + return self + + def __mul__(self, scalar): + if not isinstance(scalar, Real): + return NotImplemented + new = FissionYield(self.products, self.yields.copy()) + new *= scalar + return new + + def __rmul__(self, scalar): + return self * scalar + + def __repr__(self): + return "<{} containing {} products and yields>".format( + self.__class__.__name__, len(self)) diff --git a/openmc/deplete/operator.py b/openmc/deplete/operator.py index 76163f922c..01bf92dcc9 100644 --- a/openmc/deplete/operator.py +++ b/openmc/deplete/operator.py @@ -16,6 +16,7 @@ import xml.etree.ElementTree as ET import h5py import numpy as np +from uncertainties import ufloat import openmc import openmc.capi @@ -23,7 +24,10 @@ from . import comm from .abc import TransportOperator, OperatorResult from .atom_number import AtomNumber from .reaction_rates import ReactionRates -from .helpers import DirectReactionRateHelper, ChainFissionHelper +from .results_list import ResultsList +from .helpers import ( + DirectReactionRateHelper, ChainFissionHelper, ConstantFissionYieldHelper, + FissionYieldCutoffHelper, AveragedFissionYieldHelper) def _distribute(items): @@ -55,7 +59,7 @@ class Operator(TransportOperator): Instances of this class can be used to perform depletion using OpenMC as the transport operator. Normally, a user needn't call methods of this class directly. Instead, an instance of this class is passed to an integrator - function, such as :func:`openmc.deplete.integrator.cecm`. + class, such as :class:`openmc.deplete.CECMIntegrator`. Parameters ---------- @@ -72,7 +76,8 @@ class Operator(TransportOperator): specified, the depletion calculation will start from the latest state in the previous results. diff_burnable_mats : bool, optional - Whether to differentiate burnable materials with multiple instances + Whether to differentiate burnable materials with multiple instances. + Default: False. fission_q : dict, optional Dictionary of nuclides and their fission Q values [eV]. If not given, values will be pulled from the ``chain_file``. @@ -81,6 +86,21 @@ class Operator(TransportOperator): in initial condition to ensure they exist in the decay chain. Only done for nuclides with reaction rates. Defaults to 1.0e3. + fission_yield_mode : {"constant", "cutoff", "average"} + Key indicating what fission product yield scheme to use. The + key determines what fission energy helper is used: + + * "constant": :class:`~openmc.deplete.helpers.ConstantFissionYieldHelper` + * "cutoff": :class:`~openmc.deplete.helpers.FissionYieldCutoffHelper` + * "average": :class:`~openmc.deplete.helpers.AveragedFissionYieldHelper` + + The documentation on these classes describe their methodology + and differences. Default: ``"constant"`` + fission_yield_opts : dict of str to option, optional + Optional arguments to pass to the helper determined by + ``fission_yield_mode``. Will be passed directly on to the + helper. Passing a value of None will use the defaults for + the associated helper. Attributes ---------- @@ -111,29 +131,33 @@ class Operator(TransportOperator): Initial heavy metal inventory local_mats : list of str All burnable material IDs being managed by a single process - prev_res : ResultsList - Results from a previous depletion calculation + prev_res : ResultsList or None + Results from a previous depletion calculation. ``None`` if no + results are to be used. diff_burnable_mats : bool Whether to differentiate burnable materials with multiple instances """ + _fission_helpers = { + "average": AveragedFissionYieldHelper, + "constant": ConstantFissionYieldHelper, + "cutoff": FissionYieldCutoffHelper, + } + def __init__(self, geometry, settings, chain_file=None, prev_results=None, diff_burnable_mats=False, fission_q=None, - dilute_initial=1.0e3): - super().__init__(chain_file, fission_q, dilute_initial) + dilute_initial=1.0e3, fission_yield_mode="constant", + fission_yield_opts=None): + if fission_yield_mode not in self._fission_helpers: + raise KeyError( + "fission_yield_mode must be one of {}, not {}".format( + ", ".join(self._fission_helpers), fission_yield_mode)) + super().__init__(chain_file, fission_q, dilute_initial, prev_results) self.round_number = False + self.prev_res = None self.settings = settings self.geometry = geometry self.diff_burnable_mats = diff_burnable_mats - if prev_results is not None: - # Reload volumes into geometry - prev_results[-1].transfer_volumes(geometry) - - # Store previous results in operator - self.prev_res = prev_results - else: - self.prev_res = None - # Differentiate burnable materials with multiple instances if self.diff_burnable_mats: self._differentiate_burnable_mats() @@ -147,6 +171,21 @@ class Operator(TransportOperator): self._mat_index_map = { lm: self.burnable_mats.index(lm) for lm in self.local_mats} + if self.prev_res is not None: + # Reload volumes into geometry + prev_results[-1].transfer_volumes(geometry) + + # Store previous results in operator + # Distribute reaction rates according to those tracked + # on this process + if comm.size == 1: + self.prev_res = prev_results + else: + self.prev_res = ResultsList() + mat_indexes = _distribute(range(len(self.burnable_mats))) + for res_obj in prev_results: + new_res = res_obj.distribute(self.local_mats, mat_indexes) + self.prev_res.append(new_res) # Determine which nuclides have incident neutron data self.nuclides_with_data = self._get_nuclides_with_data() @@ -167,7 +206,14 @@ class Operator(TransportOperator): self.reaction_rates.n_nuc, self.reaction_rates.n_react) self._energy_helper = ChainFissionHelper() - def __call__(self, vec, power, print_out=True): + # Select and create fission yield helper + fission_helper = self._fission_helpers[fission_yield_mode] + fission_yield_opts = ( + {} if fission_yield_opts is None else fission_yield_opts) + self._yield_helper = fission_helper.from_operator( + self, **fission_yield_opts) + + def __call__(self, vec, power): """Runs a simulation. Parameters @@ -176,8 +222,6 @@ class Operator(TransportOperator): Total atoms to be used in function. power : float Power of the reactor in [W] - print_out : bool, optional - Whether or not to print out time. Returns ------- @@ -195,8 +239,10 @@ class Operator(TransportOperator): # Update material compositions and tally nuclides self._update_materials() - self._rate_helper.nuclides = self._get_tally_nuclides() - self._energy_helper.nuclides = self._rate_helper.nuclides + nuclides = self._get_tally_nuclides() + self._rate_helper.nuclides = nuclides + self._energy_helper.nuclides = nuclides + self._yield_helper.update_tally_nuclides(nuclides) # Run OpenMC openmc.capi.reset() @@ -207,15 +253,21 @@ class Operator(TransportOperator): # Extract results op_result = self._unpack_tallies_and_normalize(power) - if comm.rank == 0: - time_unpack = time.time() - - if print_out: - print("Time to openmc: ", time_openmc - time_start) - print("Time to unpack: ", time_unpack - time_openmc) - return copy.deepcopy(op_result) + @staticmethod + def write_bos_data(step): + """Write a state-point file with beginning of step data + + Parameters + ---------- + step : int + Current depletion step including restarts + """ + openmc.capi.statepoint_write( + "openmc_simulation_n{}.h5".format(step), + write_source=False) + def _differentiate_burnable_mats(self): """Assign distribmats for each burnable material @@ -394,6 +446,10 @@ class Operator(TransportOperator): self._rate_helper.generate_tallies(materials, self.chain.reactions) self._energy_helper.prepare( self.chain.nuclides, self.reaction_rates.index_nuc, materials) + # Tell fission yield helper what materials this process is + # responsible for + self._yield_helper.generate_tallies( + materials, tuple(sorted(self._mat_index_map.values()))) # Return number density vector return list(self.number.get_mat_slice(np.s_[:])) @@ -525,7 +581,7 @@ class Operator(TransportOperator): rates.fill(0.0) # Get k and uncertainty - k_combined = openmc.capi.keff() + k_combined = ufloat(*openmc.capi.keff()) # Extract tally bins nuclides = self._rate_helper.nuclides @@ -539,6 +595,10 @@ class Operator(TransportOperator): # Keep track of energy produced from all reactions in eV per source # particle self._energy_helper.reset() + self._yield_helper.unpack() + + # Store fission yield dictionaries + fission_yields = [] # Create arrays to store fission Q values, reaction rates, and nuclide # numbers, zeroed out in material iteration @@ -561,6 +621,9 @@ class Operator(TransportOperator): tally_rates = self._rate_helper.get_material_rates( mat_index, nuc_ind, react_ind) + # Compute fission yields for this material + fission_yields.append(self._yield_helper.weighted_yields(i)) + # Accumulate energy from fission self._energy_helper.update(tally_rates[:, fission_ind], mat_index) @@ -574,6 +637,9 @@ class Operator(TransportOperator): # Scale reaction rates to obtain units of reactions/sec rates *= power / energy + # Store new fission yields on the chain + self.chain.fission_yields = fission_yields + return OperatorResult(k_combined, rates) def _get_nuclides_with_data(self): diff --git a/openmc/deplete/results.py b/openmc/deplete/results.py index 3771f9683b..6593d5fc5f 100644 --- a/openmc/deplete/results.py +++ b/openmc/deplete/results.py @@ -21,8 +21,8 @@ class Results(object): Attributes ---------- - k : list of float - Eigenvalue for each substep. + k : list of (float, float) + Eigenvalue and uncertainty for each substep. time : list of float Time at beginning, end of step, in seconds. power : float @@ -153,6 +153,37 @@ class Results(object): # Create storage array self.data = np.zeros((stages, self.n_mat, self.n_nuc)) + def distribute(self, local_materials, ranges): + """Create a new object containing data for distributed materials + + Parameters + ---------- + local_materials : iterable of str + Materials for this process + ranges : iterable of int + Slice-like object indicating indicies of ``local_materials`` + in the material dimension of :attr:`data` and each element + in :attr:`rates` + + Returns + ------- + Results + New results object + """ + new = Results() + new.volume = {lm: self.volume[lm] for lm in local_materials} + new.mat_to_ind = dict(zip( + local_materials, range(len(local_materials)))) + # Direct transfer + direct_attrs = ("time", "k", "power", "nuc_to_ind", + "mat_to_hdf5_ind", "proc_time") + for attr in direct_attrs: + setattr(new, attr, getattr(self, attr)) + # Get applicable slice of data + new.data = self.data[:, ranges] + new.rates = [r[ranges] for r in self.rates] + return new + def export_to_hdf5(self, filename, step): """Export results to an HDF5 file @@ -425,16 +456,7 @@ class Results(object): # Get indexing terms vol_dict, nuc_list, burn_list, full_burn_list = op.get_results_info() - # For a restart calculation, limit number of stages saved to meet the - # format of the hdf5 file stages = len(x) - offset = 0 - if op.prev_res is not None and op.prev_res[0].n_stages < stages: - offset = stages - op.prev_res[0].n_stages - stages = min(stages, op.prev_res[0].n_stages) - warn("Number of restart integrator stages saved limited by initial" - " depletion integrator choice to {}" - .format(op.prev_res[0].n_stages)) # Create results results = Results() @@ -444,9 +466,9 @@ class Results(object): for i in range(stages): for mat_i in range(n_mat): - results[i, mat_i, :] = x[offset + i][mat_i][:] + results[i, mat_i, :] = x[i][mat_i] - results.k = [r.k for r in op_results] + results.k = [(r.k.nominal_value, r.k.std_dev) for r in op_results] results.rates = [r.rates for r in op_results] results.time = t results.power = power diff --git a/openmc/deplete/results_list.py b/openmc/deplete/results_list.py index 0ce5b01584..3b5166ed8a 100644 --- a/openmc/deplete/results_list.py +++ b/openmc/deplete/results_list.py @@ -8,22 +8,34 @@ from openmc.checkvalue import check_filetype_version class ResultsList(list): """A list of openmc.deplete.Results objects - Parameters - ---------- - filename : str - The filename to read from. - + It is recommended to use :meth:`from_hdf5` over + direct creation. """ - def __init__(self, filename): - super().__init__() + + @classmethod + def from_hdf5(cls, filename): + """Load in depletion results from a previous file + + Parameters + ---------- + filename : str + Path to depletion result file + + Returns + ------- + new : ResultsList + New instance of depletion results + """ with h5py.File(str(filename), "r") as fh: check_filetype_version(fh, 'depletion results', _VERSION_RESULTS[0]) + new = cls() # Get number of results stored n = fh["number"][...].shape[0] for i in range(n): - self.append(Results.from_hdf5(fh, i)) + new.append(Results.from_hdf5(fh, i)) + return new def get_atoms(self, mat, nuc): """Get number of nuclides over time from a single material diff --git a/scripts/casl_chain.py b/scripts/casl_chain.py index 86389e3ced..94fc9dd966 100755 --- a/scripts/casl_chain.py +++ b/scripts/casl_chain.py @@ -6,6 +6,8 @@ # Note 32 of the 255 nuclides appeare twice as they are both activation # nuclides (category 1) and fission product nuclides (category 3). +# Te129 has been added due to it's link to I129 production. + CASL_CHAIN = { # Nuclide: (Stable, CAT, IFPY, Special yield treatment) # Stable: True if nuclide has no decay reactions @@ -187,6 +189,7 @@ CASL_CHAIN = { 'Sb127': (False, 3, 2, None), 'Te127': (False, 3, -1, None), 'Te127_m1': (False, 3, -1, None), + 'Te129': (False, 3, 1, None), 'Te129_m1': (False, 3, 2, None), 'Te132': (False, 3, 2, None), 'I127': (True, 3, 1, None), diff --git a/scripts/openmc-make-depletion-chain b/scripts/openmc-make-depletion-chain index 7c08f984e9..51092a7224 100755 --- a/scripts/openmc-make-depletion-chain +++ b/scripts/openmc-make-depletion-chain @@ -1,7 +1,7 @@ #!/usr/bin/env python3 -import glob import os +from pathlib import Path from zipfile import ZipFile from openmc._utils import download @@ -15,15 +15,28 @@ URLS = [ ] def main(): - for url in URLS: - basename = download(url) - with ZipFile(basename, 'r') as zf: - print('Extracting {}...'.format(basename)) - zf.extractall() + endf_dir = os.environ.get("OPENMC_ENDF_DATA") + if endf_dir is not None: + endf_dir = Path(endf_dir) + elif all(os.path.isdir(lib) for lib in ("neutrons", "decay", "nfy")): + endf_dir = Path(".") + else: + for url in URLS: + basename = download(url) + with ZipFile(basename, 'r') as zf: + print('Extracting {}...'.format(basename)) + zf.extractall() + endf_dir = Path(".") - decay_files = glob.glob(os.path.join('decay', '*.endf')) - nfy_files = glob.glob(os.path.join('nfy', '*.endf')) - neutron_files = glob.glob(os.path.join('neutrons', '*.endf')) + decay_files = tuple((endf_dir / "decay").glob("*endf")) + neutron_files = tuple((endf_dir / "neutrons").glob("*endf")) + nfy_files = tuple((endf_dir / "nfy").glob("*endf")) + + # check files exist + for flist, ftype in [(decay_files, "decay"), (neutron_files, "neutron"), + (nfy_files, "neutron fission product yield")]: + if not flist: + raise IOError("No {} endf files found in {}".format(ftype, endf_dir)) chain = openmc.deplete.Chain.from_endf(decay_files, nfy_files, neutron_files) chain.export_to_xml('chain_endfb71.xml') diff --git a/src/cell.cpp b/src/cell.cpp index 3c079f7ae5..cfb717c85e 100644 --- a/src/cell.cpp +++ b/src/cell.cpp @@ -16,6 +16,7 @@ #include "openmc/hdf5_interface.h" #include "openmc/lattice.h" #include "openmc/material.h" +#include "openmc/nuclide.h" #include "openmc/settings.h" #include "openmc/surface.h" #include "openmc/xml_interface.h" @@ -244,6 +245,16 @@ Cell::temperature(int32_t instance) const void Cell::set_temperature(double T, int32_t instance) { + if (settings::temperature_method == TEMPERATURE_INTERPOLATION) { + if (T < data::temperature_min) { + throw std::runtime_error{"Temperature is below minimum temperature at " + "which data is available."}; + } else if (T > data::temperature_max) { + throw std::runtime_error{"Temperature is above maximum temperature at " + "which data is available."}; + } + } + if (instance >= 0) { sqrtkT_.at(instance) = std::sqrt(K_BOLTZMANN * T); } else { @@ -1057,7 +1068,6 @@ openmc_cell_set_fill(int32_t index, int type, int32_t n, return 0; } -//TODO: make sure data is loaded for this temperature extern "C" int openmc_cell_set_temperature(int32_t index, double T, const int32_t* instance) { diff --git a/src/cross_sections.cpp b/src/cross_sections.cpp index 8ea59c1f08..3c29dd8e6d 100644 --- a/src/cross_sections.cpp +++ b/src/cross_sections.cpp @@ -357,6 +357,12 @@ read_ce_cross_sections(const std::vector>& nuc_temps, } } + // Show minimum/maximum temperature + write_message("Minimum neutron data temperature: " + + std::to_string(data::temperature_min) + " K", 4); + write_message("Maximum neutron data temperature: " + + std::to_string(data::temperature_max) + " K", 4); + // If the user wants multipole, make sure we found a multipole library. if (settings::temperature_multipole) { bool mp_found = false; diff --git a/src/dagmc.cpp b/src/dagmc.cpp index 3f7c170cea..04d4f08c12 100644 --- a/src/dagmc.cpp +++ b/src/dagmc.cpp @@ -172,7 +172,7 @@ void load_dagmc_geometry() // notify user if UWUW materials are going to be used if (using_uwuw) { - std::cout << "Found UWUW Materials in the DAGMC geometry file.\n"; + write_message("Found UWUW Materials in the DAGMC geometry file.", 6); } int32_t dagmc_univ_id = 0; // universe is always 0 for DAGMC runs diff --git a/src/finalize.cpp b/src/finalize.cpp index 4d93c6c66c..efd3828306 100644 --- a/src/finalize.cpp +++ b/src/finalize.cpp @@ -119,6 +119,8 @@ int openmc_finalize() data::energy_max = {INFTY, INFTY}; data::energy_min = {0.0, 0.0}; + data::temperature_min = 0.0; + data::temperature_max = INFTY; model::root_universe = -1; openmc::openmc_set_seed(DEFAULT_SEED); @@ -127,7 +129,9 @@ int openmc_finalize() // Free all MPI types #ifdef OPENMC_MPI - MPI_Type_free(&mpi::bank); + int init_called; + MPI_Initialized(&init_called); + if (init_called) MPI_Type_free(&mpi::bank); #endif return 0; diff --git a/src/message_passing.cpp b/src/message_passing.cpp index 278bf924d3..aea17f9f49 100644 --- a/src/message_passing.cpp +++ b/src/message_passing.cpp @@ -8,8 +8,8 @@ int n_procs {1}; bool master {true}; #ifdef OPENMC_MPI -MPI_Comm intracomm; -MPI_Datatype bank; +MPI_Comm intracomm {MPI_COMM_NULL}; +MPI_Datatype bank {MPI_DATATYPE_NULL}; #endif extern "C" bool openmc_master() { return mpi::master; } diff --git a/src/nuclide.cpp b/src/nuclide.cpp index 6094b79831..3b092ddf4b 100644 --- a/src/nuclide.cpp +++ b/src/nuclide.cpp @@ -18,7 +18,7 @@ #include "xtensor/xbuilder.hpp" #include "xtensor/xview.hpp" -#include // for sort +#include // for sort, min_element #include // for to_string, stoi namespace openmc { @@ -30,6 +30,8 @@ namespace openmc { namespace data { std::array energy_min {0.0, 0.0}; std::array energy_max {INFTY, INFTY}; +double temperature_min {0.0}; +double temperature_max {INFTY}; std::vector> nuclides; std::unordered_map nuclide_map; } // namespace data @@ -154,6 +156,12 @@ Nuclide::Nuclide(hid_t group, const std::vector& temperature, int i_nucl // Sort temperatures to read std::sort(temps_to_read.begin(), temps_to_read.end()); + double T_min_read = *std::min_element(temps_to_read.cbegin(), temps_to_read.cend()); + double T_max_read = *std::max_element(temps_to_read.cbegin(), temps_to_read.cend()); + + data::temperature_min = std::max(data::temperature_min, T_min_read); + data::temperature_max = std::min(data::temperature_max, T_max_read); + hid_t energy_group = open_group(group, "energy"); for (const auto& T : temps_to_read) { std::string dset {std::to_string(T) + "K"}; diff --git a/src/output.cpp b/src/output.cpp index dff39c15fe..6516b48b61 100644 --- a/src/output.cpp +++ b/src/output.cpp @@ -211,6 +211,7 @@ extern "C" void print_particle(Particle* p) void print_plot() { header("PLOTTING SUMMARY", 5); + if (settings::verbosity < 5) return; for (auto pl : model::plots) { // Plot id @@ -453,6 +454,7 @@ void print_runtime() // display header block header("Timing Statistics", 6); + if (settings::verbosity < 6) return; // Save state of cout auto f {std::cout.flags()}; @@ -537,6 +539,7 @@ void print_results() // display header block for results header("Results", 4); + if (settings::verbosity < 4) return; // Calculate t-value for confidence intervals int n = simulation::n_realizations; diff --git a/src/reaction.cpp b/src/reaction.cpp index b733b20283..31bf8efd33 100644 --- a/src/reaction.cpp +++ b/src/reaction.cpp @@ -231,6 +231,10 @@ std::string reaction_name(int mt) return "(n,Xa)"; } else if (mt == 301) { return "heating"; + } else if (mt == 318) { + return "fission-heating"; + } else if (mt == 999) { + return "non-fission-heating"; } else if (mt == 444) { return "damage-energy"; } else if (mt == COHERENT) { diff --git a/src/state_point.cpp b/src/state_point.cpp index 6ea666fde2..7239f3284f 100644 --- a/src/state_point.cpp +++ b/src/state_point.cpp @@ -742,7 +742,7 @@ void write_tally_results_nr(hid_t file_id) } else { // Receive buffer not significant at other processors #ifdef OPENMC_MPI - MPI_Reduce(values.data(), nullptr, values.size(), MPI_REAL8, MPI_SUM, + MPI_Reduce(values.data(), nullptr, values.size(), MPI_DOUBLE, MPI_SUM, 0, mpi::intracomm); #endif } diff --git a/src/tallies/filter_energyfunc.cpp b/src/tallies/filter_energyfunc.cpp index 9b28e0f765..3e3fbd909c 100644 --- a/src/tallies/filter_energyfunc.cpp +++ b/src/tallies/filter_energyfunc.cpp @@ -21,12 +21,37 @@ EnergyFunctionFilter::from_xml(pugi::xml_node node) if (!check_for_node(node, "energy")) fatal_error("Energy grid not specified for EnergyFunction filter."); - energy_ = get_node_array(node, "energy"); + auto energy = get_node_array(node, "energy"); if (!check_for_node(node, "y")) fatal_error("y values not specified for EnergyFunction filter."); - y_ = get_node_array(node, "y"); + auto y = get_node_array(node, "y"); + + this->set_data(energy, y); +} + +void +EnergyFunctionFilter::set_data(gsl::span energy, + gsl::span y) +{ + // Check for consistent sizes with new data + if (energy.size() != y.size()) { + fatal_error("Energy grid and y values are not consistent"); + } + energy_.clear(); + energy_.reserve(energy.size()); + y_.clear(); + y_.reserve(y.size()); + + // Copy over energy values, ensuring they are valid + for (gsl::index i = 0; i < energy.size(); ++i) { + if (i > 0 && energy[i] <= energy[i - 1]) { + throw std::runtime_error{"Energy bins must be monotonically increasing."}; + } + energy_.push_back(energy[i]); + y_.push_back(y[i]); + } } void @@ -65,4 +90,72 @@ EnergyFunctionFilter::text_label(int bin) const return out.str(); } +//============================================================================== +// C-API functions +//============================================================================== + +extern "C" int +openmc_energyfunc_filter_set_data(int32_t index, size_t n, const double* energy, + const double* y) +{ + // Ensure this is a valid index to allocated filter + if (int err = verify_filter(index)) return err; + + // Get a pointer to the filter + const auto& filt_base = model::tally_filters[index].get(); + // Downcast to EnergyFunctionFilter + auto* filt = dynamic_cast(filt_base); + + // Check if a valid filter was produced + if (!filt) { + set_errmsg("Tried to set interpolation data for non-energy function filter."); + return OPENMC_E_INVALID_TYPE; + } + + filt->set_data({energy, n}, {y, n}); + return 0; +} + +extern "C" int +openmc_energyfunc_filter_get_energy(int32_t index, size_t *n, const double** energy) +{ + // ensure this is a valid index to allocated filter + if (int err = verify_filter(index)) return err; + + // get a pointer to the filter + const auto& filt_base = model::tally_filters[index].get(); + // downcast to EnergyFunctionFilter + auto* filt = dynamic_cast(filt_base); + + // check if a valid filter was produced + if (!filt) { + set_errmsg("Tried to set interpolation data for non-energy function filter."); + return OPENMC_E_INVALID_TYPE; + } + *energy = filt->energy().data(); + *n = filt->energy().size(); + return 0; +} + +extern "C" int +openmc_energyfunc_filter_get_y(int32_t index, size_t *n, const double** y) +{ + // ensure this is a valid index to allocated filter + if (int err = verify_filter(index)) return err; + + // get a pointer to the filter + const auto& filt_base = model::tally_filters[index].get(); + // downcast to EnergyFunctionFilter + auto* filt = dynamic_cast(filt_base); + + // check if a valid filter was produced + if (!filt) { + set_errmsg("Tried to set interpolation data for non-energy function filter."); + return OPENMC_E_INVALID_TYPE; + } + *y = filt->y().data(); + *n = filt->y().size(); + return 0; +} + } // namespace openmc diff --git a/src/tallies/tally_scoring.cpp b/src/tallies/tally_scoring.cpp index 6bf9ca6e9f..1f6a8010a5 100644 --- a/src/tallies/tally_scoring.cpp +++ b/src/tallies/tally_scoring.cpp @@ -171,6 +171,140 @@ score_fission_delayed_dg(int i_tally, int d_bin, double score, int score_index) dg_match.bins_[i_bin] = original_bin; } +//! Helper function to retrieve fission q value from a nuclide + +double get_nuc_fission_q(const Nuclide& nuc, const Particle* p, int score_bin) +{ + if (score_bin == SCORE_FISS_Q_PROMPT) { + if (nuc.fission_q_prompt_) { + return (*nuc.fission_q_prompt_)(p->E_last_); + } + } else if (score_bin == SCORE_FISS_Q_RECOV) { + if (nuc.fission_q_recov_) { + return (*nuc.fission_q_recov_)(p->E_last_); + } + } + return 0.0; +} + +//! Helper function to score fission energy +// +//! Pulled out to support both the fission_q scores and energy deposition +//! score + +double score_fission_q(const Particle* p, int score_bin, const Tally& tally, + double flux, int i_nuclide, double atom_density) +{ + if (tally.estimator_ == ESTIMATOR_ANALOG) { + const Nuclide& nuc {*data::nuclides[p->event_nuclide_]}; + if (settings::survival_biasing) { + // No fission events occur if survival biasing is on -- need to + // calculate fraction of absorptions that would have resulted in + // fission scaled by the Q-value + if (p->neutron_xs_[p->event_nuclide_].absorption > 0) { + return p->wgt_absorb_ * get_nuc_fission_q(nuc, p, score_bin) + * p->neutron_xs_[p->event_nuclide_].fission * flux + / p->neutron_xs_[p->event_nuclide_].absorption; + } + } else { + // Skip any non-absorption events + if (p->event_ == EVENT_SCATTER) return 0.0; + // All fission events will contribute, so again we can use particle's + // weight entering the collision as the estimate for the fission + // reaction rate + if (p->neutron_xs_[p->event_nuclide_].absorption > 0) { + return p->wgt_last_ * get_nuc_fission_q(nuc, p, score_bin) + * p->neutron_xs_[p->event_nuclide_].fission * flux + / p->neutron_xs_[p->event_nuclide_].absorption; + } + } + } else { + if (i_nuclide >= 0) { + const Nuclide& nuc {*data::nuclides[i_nuclide]}; + return get_nuc_fission_q(nuc, p, score_bin) * atom_density * flux + * p->neutron_xs_[i_nuclide].fission; + } else { + if (p->material_ != MATERIAL_VOID) { + const Material& material {*model::materials[p->material_]}; + double score {0.0}; + for (auto i = 0; i < material.nuclide_.size(); ++i) { + auto j_nuclide = material.nuclide_[i]; + auto atom_density = material.atom_density_(i); + const Nuclide& nuc {*data::nuclides[j_nuclide]}; + score += get_nuc_fission_q(nuc, p, score_bin) * atom_density + * p->neutron_xs_[j_nuclide].fission; + } + return score * flux; + } + } + } + return 0.0; +} + +//! Helper function to obtain the kerma coefficient for a given nuclide + +double get_nuclide_neutron_heating(const Particle* p, const Nuclide& nuc, + int rxn_index, int i_nuclide) +{ + size_t mt = nuc.reaction_index_[rxn_index]; + if (mt == C_NONE) return 0.0; + auto i_temp = p->neutron_xs_[i_nuclide].index_temp; + if (i_temp < 0) return 0.0; // Can be true due to multipole + const auto& rxn {*nuc.reactions_[mt]}; + const auto& xs {rxn.xs_[i_temp]}; + auto i_grid = p->neutron_xs_[i_nuclide].index_grid; + if (i_grid < xs.threshold) return 0.0; + auto f = p->neutron_xs_[i_nuclide].interp_factor; + return (1.0 - f) * xs.value[i_grid-xs.threshold] + + f * xs.value[i_grid-xs.threshold+1]; +} + +//! Helper function to obtain neutron heating [eV] + +double score_neutron_heating(const Particle* p, const Tally& tally, double flux, + int rxn_bin, int i_nuclide, double atom_density) +{ + double score; + // Get heating macroscopic "cross section" + double heating_xs; + if (i_nuclide >= 0) { + const Nuclide& nuc {*data::nuclides[i_nuclide]}; + heating_xs = get_nuclide_neutron_heating(p, nuc, rxn_bin, i_nuclide); + if (tally.estimator_ == ESTIMATOR_ANALOG) { + heating_xs /= p->neutron_xs_[i_nuclide].total; + } else { + heating_xs *= atom_density; + } + } else { + if (p->material_ != MATERIAL_VOID) { + heating_xs = 0.0; + const Material& material {*model::materials[p->material_]}; + for (auto i = 0; i< material.nuclide_.size(); ++i) { + int j_nuclide = material.nuclide_[i]; + double atom_density {material.atom_density_(i)}; + const Nuclide& nuc {*data::nuclides[j_nuclide]}; + heating_xs += atom_density * get_nuclide_neutron_heating(p, nuc, rxn_bin, j_nuclide); + } + if (tally.estimator_ == ESTIMATOR_ANALOG) { + heating_xs /= p->macro_xs_.total; + } + } + } + score = heating_xs * flux; + if (tally.estimator_ == ESTIMATOR_ANALOG) { + // All events score to a heating tally bin. We actually use a + // collision estimator in place of an analog one since there is no + // reaction-wise heating cross section + if (settings::survival_biasing) { + // Account for the fact that some weight has been absorbed + score *= p->wgt_last_ + p->wgt_absorb_; + } else { + score *= p->wgt_last_; + } + } + return score; +} + //! Helper function for nu-fission tallies with energyout filters. // //! In this case, we may need to score to multiple bins if there were multiple @@ -323,7 +457,7 @@ void score_general_ce(Particle* p, int i_tally, int start_index, int filter_index, int i_nuclide, double atom_density, double flux) { - auto& tally {*model::tallies[i_tally]}; + Tally& tally {*model::tallies[i_tally]}; // Get the pre-collision energy of the particle. auto E = p->E_last_; @@ -1032,83 +1166,8 @@ score_general_ce(Particle* p, int i_tally, int start_index, case SCORE_FISS_Q_PROMPT: case SCORE_FISS_Q_RECOV: - //continue; if (p->macro_xs_.absorption == 0.) continue; - score = 0.; - if (tally.estimator_ == ESTIMATOR_ANALOG) { - if (settings::survival_biasing) { - // No fission events occur if survival biasing is on -- need to - // calculate fraction of absorptions that would have resulted in - // fission scaled by the Q-value - const auto& nuc {*data::nuclides[p->event_nuclide_]}; - if (p->neutron_xs_[p->event_nuclide_].absorption > 0) { - double q_value = 0.; - if (score_bin == SCORE_FISS_Q_PROMPT) { - if (nuc.fission_q_prompt_) - q_value = (*nuc.fission_q_prompt_)(p->E_last_); - } else if (score_bin == SCORE_FISS_Q_RECOV) { - if (nuc.fission_q_recov_) - q_value = (*nuc.fission_q_recov_)(p->E_last_); - } - score = p->wgt_absorb_ * q_value - * p->neutron_xs_[p->event_nuclide_].fission - / p->neutron_xs_[p->event_nuclide_].absorption * flux; - } - } else { - // Skip any non-absorption events - if (p->event_ == EVENT_SCATTER) continue; - // All fission events will contribute, so again we can use particle's - // weight entering the collision as the estimate for the fission - // reaction rate - const auto& nuc {*data::nuclides[p->event_nuclide_]}; - if (p->neutron_xs_[p->event_nuclide_].absorption > 0) { - double q_value = 0.; - if (score_bin == SCORE_FISS_Q_PROMPT) { - if (nuc.fission_q_prompt_) - q_value = (*nuc.fission_q_prompt_)(p->E_last_); - } else if (score_bin == SCORE_FISS_Q_RECOV) { - if (nuc.fission_q_recov_) - q_value = (*nuc.fission_q_recov_)(p->E_last_); - } - score = p->wgt_last_ * q_value - * p->neutron_xs_[p->event_nuclide_].fission - / p->neutron_xs_[p->event_nuclide_].absorption * flux; - } - } - } else { - if (i_nuclide >= 0) { - const auto& nuc {*data::nuclides[i_nuclide]}; - double q_value = 0.; - if (score_bin == SCORE_FISS_Q_PROMPT) { - if (nuc.fission_q_prompt_) - q_value = (*nuc.fission_q_prompt_)(p->E_last_); - } else if (score_bin == SCORE_FISS_Q_RECOV) { - if (nuc.fission_q_recov_) - q_value = (*nuc.fission_q_recov_)(p->E_last_); - } - score = q_value * p->neutron_xs_[i_nuclide].fission - * atom_density * flux; - } else { - if (p->material_ != MATERIAL_VOID) { - const Material& material {*model::materials[p->material_]}; - for (auto i = 0; i < material.nuclide_.size(); ++i) { - auto j_nuclide = material.nuclide_[i]; - auto atom_density = material.atom_density_(i); - const auto& nuc {*data::nuclides[j_nuclide]}; - double q_value = 0.; - if (score_bin == SCORE_FISS_Q_PROMPT) { - if (nuc.fission_q_prompt_) - q_value = (*nuc.fission_q_prompt_)(p->E_last_); - } else if (score_bin == SCORE_FISS_Q_RECOV) { - if (nuc.fission_q_recov_) - q_value = (*nuc.fission_q_recov_)(p->E_last_); - } - score += q_value * p->neutron_xs_[j_nuclide].fission - * atom_density * flux; - } - } - } - } + score = score_fission_q(p, score_bin, tally, flux, i_nuclide, atom_density); break; @@ -1154,105 +1213,8 @@ score_general_ce(Particle* p, int i_tally, int start_index, case SCORE_HEATING: score = 0.; if (p->type_ == Particle::Type::neutron) { - if (tally.estimator_ == ESTIMATOR_ANALOG) { - // All events score to a heating tally bin. We actually use a - // collision estimator in place of an analog one since there is no - // reaction-wise heating cross section - if (settings::survival_biasing) { - // We need to account for the fact that some weight was already - // absorbed - score = p->wgt_last_ + p->wgt_absorb_; - } else { - score = p->wgt_last_; - } - if (i_nuclide >= 0) { - // Calculate nuclide heating cross section - double macro_heating = 0.; - const auto& nuc {*data::nuclides[i_nuclide]}; - auto m = nuc.reaction_index_[NEUTRON_HEATING]; - if (m == C_NONE) continue; - const auto& rxn {*nuc.reactions_[m]}; - auto i_temp = p->neutron_xs_[i_nuclide].index_temp; - if (i_temp >= 0) { // Can be false due to multipole - auto i_grid = p->neutron_xs_[i_nuclide].index_grid; - auto f = p->neutron_xs_[i_nuclide].interp_factor; - const auto& xs {rxn.xs_[i_temp]}; - if (i_grid >= xs.threshold) { - macro_heating = ((1.0 - f) * xs.value[i_grid-xs.threshold] - + f * xs.value[i_grid-xs.threshold+1]); - } - } - score *= macro_heating * flux / p->neutron_xs_[i_nuclide].total; - } else { - if (p->material_ != MATERIAL_VOID) { - // Calculate material heating cross section - double macro_heating = 0.; - const Material& material {*model::materials[p->material_]}; - for (auto i = 0; i < material.nuclide_.size(); ++i) { - auto j_nuclide = material.nuclide_[i]; - auto atom_density = material.atom_density_(i); - const auto& nuc {*data::nuclides[j_nuclide]}; - auto m = nuc.reaction_index_[NEUTRON_HEATING]; - if (m == C_NONE) continue; - const auto& rxn {*nuc.reactions_[m]}; - auto i_temp = p->neutron_xs_[j_nuclide].index_temp; - if (i_temp >= 0) { // Can be false due to multipole - auto i_grid = p->neutron_xs_[j_nuclide].index_grid; - auto f = p->neutron_xs_[j_nuclide].interp_factor; - const auto& xs {rxn.xs_[i_temp]}; - if (i_grid >= xs.threshold) { - macro_heating += ((1.0 - f) * xs.value[i_grid-xs.threshold] - + f * xs.value[i_grid-xs.threshold+1]) * atom_density; - } - } - } - score *= macro_heating * flux / p->macro_xs_.total; - } else { - score = 0.; - } - } - } else { - // Calculate neutron heating cross section on-the-fly - if (i_nuclide >= 0) { - const auto& nuc {*data::nuclides[i_nuclide]}; - auto m = nuc.reaction_index_[NEUTRON_HEATING]; - if (m == C_NONE) continue; - const auto& rxn {*nuc.reactions_[m]}; - auto i_temp = p->neutron_xs_[i_nuclide].index_temp; - if (i_temp >= 0) { // Can be false due to multipole - auto i_grid = p->neutron_xs_[i_nuclide].index_grid; - auto f = p->neutron_xs_[i_nuclide].interp_factor; - const auto& xs {rxn.xs_[i_temp]}; - if (i_grid >= xs.threshold) { - score = ((1.0 - f) * xs.value[i_grid-xs.threshold] - + f * xs.value[i_grid-xs.threshold+1]) * atom_density * flux; - } - } - } else { - if (p->material_ != MATERIAL_VOID) { - const Material& material {*model::materials[p->material_]}; - for (auto i = 0; i < material.nuclide_.size(); ++i) { - auto j_nuclide = material.nuclide_[i]; - auto atom_density = material.atom_density_(i); - const auto& nuc {*data::nuclides[j_nuclide]}; - auto m = nuc.reaction_index_[NEUTRON_HEATING]; - if (m == C_NONE) continue; - const auto& rxn {*nuc.reactions_[m]}; - auto i_temp = p->neutron_xs_[j_nuclide].index_temp; - if (i_temp >= 0) { // Can be false due to multipole - auto i_grid = p->neutron_xs_[j_nuclide].index_grid; - auto f = p->neutron_xs_[j_nuclide].interp_factor; - const auto& xs {rxn.xs_[i_temp]}; - if (i_grid >= xs.threshold) { - score += ((1.0 - f) * xs.value[i_grid-xs.threshold] - + f * xs.value[i_grid-xs.threshold+1]) * atom_density - * flux; - } - } - } - } - } - } + score = score_neutron_heating(p, tally, flux, NEUTRON_HEATING, + i_nuclide, atom_density); } else if (p->type_ == Particle::Type::photon) { if (tally.estimator_ == ESTIMATOR_ANALOG) { // Score direct energy deposition in the collision diff --git a/tests/dummy_operator.py b/tests/dummy_operator.py index c66070ad40..85c689743a 100644 --- a/tests/dummy_operator.py +++ b/tests/dummy_operator.py @@ -1,7 +1,119 @@ +from collections import namedtuple + import numpy as np import scipy.sparse as sp +from uncertainties import ufloat + from openmc.deplete.reaction_rates import ReactionRates from openmc.deplete.abc import TransportOperator, OperatorResult +from openmc.deplete import ( + CECMIntegrator, PredictorIntegrator, CELIIntegrator, LEQIIntegrator, + EPCRK4Integrator, CF4Integrator, SICELIIntegrator, SILEQIIntegrator +) + +# Bundle for nicely passing test data to depletion unit tests +# solver should be a concrete subclass of openmc.deplete.abc.Integrator +# atoms_1 should be the number of atoms of type 1 through the simulation +# similar for atoms_2, but for type 2. This includes the first step +# Solutions should be the exact solution that can be obtained using +# the DummyOperator depletion matrix with two 0.75 second time steps +DepletionSolutionTuple = namedtuple( + "DepletionSolutionTuple", "solver atoms_1 atoms_2") + + +predictor_solution = DepletionSolutionTuple( + PredictorIntegrator, np.array([1.0, 2.46847546272295, 4.11525874568034]), + np.array([1.0, 0.986431226850467, -0.0581692232513460])) + + +cecm_solution = DepletionSolutionTuple( + CECMIntegrator, np.array([1.0, 1.86872629872102, 2.18097439443550]), + np.array([1.0, 1.395525772416039, 2.69429754646747])) + + +cf4_solution = DepletionSolutionTuple( + CF4Integrator, np.array([1.0, 2.06101629, 2.57241318]), + np.array([1.0, 1.37783588, 2.63731630])) + + +epc_rk4_solution = DepletionSolutionTuple( + EPCRK4Integrator, np.array([1.0, 2.01978516, 2.05246421]), + np.array([1.0, 1.42038037, 3.06177191])) + + +celi_solution = DepletionSolutionTuple( + CELIIntegrator, np.array([1.0, 1.82078767, 2.68441779]), + np.array([1.0, 0.97122898, 0.05125966])) + + +si_celi_solution = DepletionSolutionTuple( + SICELIIntegrator, np.array([1.0, 2.03325094, 2.69291933]), + np.array([1.0, 1.16826254, 0.37907772])) + + +leqi_solution = DepletionSolutionTuple( + LEQIIntegrator, np.array([1.0, 1.82078767, 2.74526197]), + np.array([1.0, 0.97122898, 0.23339915])) + + +si_leqi_solution = DepletionSolutionTuple( + SILEQIIntegrator, np.array([1.0, 2.03325094, 2.92711288]), + np.array([1.0, 1.16826254, 0.53753236])) + + +SCHEMES = { + "predictor": predictor_solution, + "cecm": cecm_solution, + "celi": celi_solution, + "cf4": cf4_solution, + "epc_rk4": epc_rk4_solution, + "leqi": leqi_solution, + "si_leqi": si_leqi_solution, + "si_celi": si_celi_solution, +} + + +class TestChain(object): + """Empty chain to assist with unit testing depletion routines + + Only really provides the form_matrix function, but acts like + a real Chain + """ + + fission_yields = [None] + + @staticmethod + def get_thermal_fission_yields(): + return None + + def form_matrix(self, rates, _fission_yields=None): + """Forms the f(y) matrix in y' = f(y)y. + + Nominally a depletion matrix, this is abstracted on the off chance + that the function f has nothing to do with depletion at all. + + Parameters + ---------- + rates : numpy.ndarray + Slice of reaction rates for a single material + _fission_yields : optional + Not used + + Returns + ------- + scipy.sparse.csr_matrix + Sparse matrix representing f(y). + """ + + y_1 = rates[0, 0] + y_2 = rates[1, 0] + + a11 = np.sin(y_2) + a12 = np.cos(y_1) + a21 = -np.cos(y_2) + a22 = np.sin(y_1) + + return sp.csr_matrix(np.array([[a11, a12], [a21, a22]])) class DummyOperator(TransportOperator): @@ -19,6 +131,8 @@ class DummyOperator(TransportOperator): """ def __init__(self, previous_results=None): self.prev_res = previous_results + self.chain = TestChain() + self.output_dir = "." def __call__(self, vec, power, print_out=False): """Evaluates F(y) @@ -48,39 +162,7 @@ class DummyOperator(TransportOperator): reaction_rates[0, 1, 0] = vec[0][1] # Create a fake rates object - return OperatorResult(0.0, reaction_rates) - - @property - def chain(self): - return self - - def form_matrix(self, rates): - """Forms the f(y) matrix in y' = f(y)y. - - Nominally a depletion matrix, this is abstracted on the off chance - that the function f has nothing to do with depletion at all. - - Parameters - ---------- - rates : numpy.ndarray - Slice of reaction rates for a single material - - Returns - ------- - scipy.sparse.csr_matrix - Sparse matrix representing f(y). - """ - - y_1 = rates[0, 0] - y_2 = rates[1, 0] - - mat = np.zeros((2, 2)) - a11 = np.sin(y_2) - a12 = np.cos(y_1) - a21 = -np.cos(y_2) - a22 = np.sin(y_1) - - return sp.csr_matrix(np.array([[a11, a12], [a21, a22]])) + return OperatorResult(ufloat(0.0, 0.0), reaction_rates) @property def volume(self): @@ -104,7 +186,8 @@ class DummyOperator(TransportOperator): def local_mats(self): """ local_mats : list of str - A list of all material IDs to be burned. Used for sorting the simulation. + A list of all material IDs to be burned. Used for sorting the + simulation. """ return ["1"] @@ -114,6 +197,9 @@ class DummyOperator(TransportOperator): """Maps cell name to index in global geometry.""" return self.local_mats + @staticmethod + def write_bos_data(_step): + """Empty method but avoids calls to C API""" @property def reaction_rates(self): @@ -148,7 +234,8 @@ class DummyOperator(TransportOperator): nuc_list : list of str A list of all nuclide names. Used for sorting the simulation. burn_list : list of int - A list of all cell IDs to be burned. Used for sorting the simulation. + A list of all cell IDs to be burned. Used for sorting the + simulation. full_burn_list : OrderedDict of str to int Maps cell name to index in global geometry. diff --git a/tests/regression_tests/deplete/test.py b/tests/regression_tests/deplete/test.py index 059a44ea35..c7a9c162e0 100644 --- a/tests/regression_tests/deplete/test.py +++ b/tests/regression_tests/deplete/test.py @@ -39,7 +39,7 @@ def test_full(run_in_tmpdir): space = openmc.stats.Box(lower_left, upper_right) settings.source = openmc.Source(space=space) settings.seed = 1 - settings.verbosity = 3 + settings.verbosity = 1 # Create operator chain_file = Path(__file__).parents[2] / 'chain_simple.xml' @@ -54,7 +54,7 @@ def test_full(run_in_tmpdir): power = 2.337e15*4*JOULE_PER_EV*1e6 # MeV/second cm from CASMO # Perform simulation using the predictor algorithm - openmc.deplete.integrator.predictor(op, dt, power) + openmc.deplete.PredictorIntegrator(op, dt, power).integrate() # Get path to test and reference results path_test = op.output_dir / 'depletion_results.h5' @@ -66,8 +66,8 @@ def test_full(run_in_tmpdir): return # Load the reference/test results - res_test = openmc.deplete.ResultsList(path_test) - res_ref = openmc.deplete.ResultsList(path_reference) + res_test = openmc.deplete.ResultsList.from_hdf5(path_test) + res_ref = openmc.deplete.ResultsList.from_hdf5(path_reference) # Assert same mats for mat in res_ref[0].mat_to_ind: @@ -101,3 +101,18 @@ def test_full(run_in_tmpdir): assert correct, "Discrepancy in mat {} and nuc {}\n{}\n{}".format( mat, nuc, y_old, y_test) + + # Compare statepoint files with depletion results + + t_test, k_test = res_test.get_eigenvalue() + t_ref, k_ref = res_ref.get_eigenvalue() + k_state = np.empty_like(k_ref) + + # Get statepoint files for all BOS points and EOL + for n in range(N + 1): + statepoint = openmc.StatePoint("openmc_simulation_n{}.h5".format(n)) + k_n = statepoint.k_combined + k_state[n] = [k_n.nominal_value, k_n.std_dev] + # Look for exact match pulling from statepoint and depletion_results + assert np.all(k_state == k_test) + assert np.allclose(k_test, k_ref) diff --git a/tests/regression_tests/energy_laws/geometry.xml b/tests/regression_tests/energy_laws/geometry.xml deleted file mode 100644 index c42f45597d..0000000000 --- a/tests/regression_tests/energy_laws/geometry.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/tests/regression_tests/energy_laws/inputs_true.dat b/tests/regression_tests/energy_laws/inputs_true.dat new file mode 100644 index 0000000000..2320b21d5b --- /dev/null +++ b/tests/regression_tests/energy_laws/inputs_true.dat @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + + eigenvalue + 1000 + 10 + 5 + diff --git a/tests/regression_tests/energy_laws/materials.xml b/tests/regression_tests/energy_laws/materials.xml deleted file mode 100644 index e63f4018c3..0000000000 --- a/tests/regression_tests/energy_laws/materials.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/tests/regression_tests/energy_laws/results_true.dat b/tests/regression_tests/energy_laws/results_true.dat index 02465fa798..ce93d6c822 100644 --- a/tests/regression_tests/energy_laws/results_true.dat +++ b/tests/regression_tests/energy_laws/results_true.dat @@ -1,2 +1,2 @@ k-combined: -2.122164E+00 1.946222E-02 +2.466441E+00 1.500183E-02 diff --git a/tests/regression_tests/energy_laws/settings.xml b/tests/regression_tests/energy_laws/settings.xml deleted file mode 100644 index 946345eeb5..0000000000 --- a/tests/regression_tests/energy_laws/settings.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - eigenvalue - 10 - 5 - 1000 - - - - diff --git a/tests/regression_tests/energy_laws/test.py b/tests/regression_tests/energy_laws/test.py index 8f9bbde356..1746b2d31b 100644 --- a/tests/regression_tests/energy_laws/test.py +++ b/tests/regression_tests/energy_laws/test.py @@ -2,23 +2,51 @@ are not covered in other tests. It has a single material with the following nuclides: -U-233: Only nuclide that has a Watt fission spectrum +U233: Only nuclide that has a Watt fission spectrum -H-2: Only nuclide that has an N-body phase space distribution, in this case for +Am244: One of a few nuclides that has a Maxwell fission spectrum + +H2: Only nuclide that has an N-body phase space distribution, in this case for (n,2n) -Na-23: Has an evaporation spectrum and also has reactions that have multiple +Na23: Has an evaporation spectrum and also has reactions that have multiple angle-energy distributions, so it provides coverage for both of those situations. -Ta-181: One of a few nuclides that has reactions with Kalbach-Mann distributions +Ta181: One of a few nuclides that has reactions with Kalbach-Mann distributions that use linear-linear interpolation. """ -from tests.testing_harness import TestHarness +import openmc +import pytest + +from tests.testing_harness import PyAPITestHarness -def test_energy_laws(): - harness = TestHarness('statepoint.10.h5') +@pytest.fixture +def model(): + model = openmc.model.Model() + + m = openmc.Material() + m.set_density('g/cm3', 20.0) + m.add_nuclide('U233', 1.0) + m.add_nuclide('Am244', 1.0) + m.add_nuclide('H2', 1.0) + m.add_nuclide('Na23', 1.0) + m.add_nuclide('Ta181', 1.0) + + s = openmc.Sphere(r=100.0, boundary_type='reflective') + c = openmc.Cell(fill=m, region=-s) + model.geometry = openmc.Geometry([c]) + + model.settings.batches = 10 + model.settings.inactive = 5 + model.settings.particles = 1000 + + return model + + +def test_energy_laws(model): + harness = PyAPITestHarness('statepoint.10.h5', model) harness.main() diff --git a/tests/regression_tests/filter_energyfun/inputs_true.dat b/tests/regression_tests/filter_energyfun/inputs_true.dat index 418354fc2b..0f506f3f5f 100644 --- a/tests/regression_tests/filter_energyfun/inputs_true.dat +++ b/tests/regression_tests/filter_energyfun/inputs_true.dat @@ -1,312 +1,21 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1.26 1.26 - 17 17 - -10.71 -10.71 - -1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 -1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 -1 1 1 1 1 2 1 1 2 1 1 2 1 1 1 1 1 -1 1 1 2 1 1 1 1 1 1 1 1 1 2 1 1 1 -1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 -1 1 2 1 1 2 1 1 2 1 1 2 1 1 2 1 1 -1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 -1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 -1 1 2 1 1 2 1 1 2 1 1 2 1 1 2 1 1 -1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 -1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 -1 1 2 1 1 2 1 1 2 1 1 2 1 1 2 1 1 -1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 -1 1 1 2 1 1 1 1 1 1 1 1 1 2 1 1 1 -1 1 1 1 1 2 1 1 2 1 1 2 1 1 1 1 1 -1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 -1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 - - - 1.26 1.26 - 17 17 - -10.71 -10.71 - -3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 -3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 -3 3 3 3 3 4 3 3 4 3 3 4 3 3 3 3 3 -3 3 3 4 3 3 3 3 3 3 3 3 3 4 3 3 3 -3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 -3 3 4 3 3 4 3 3 4 3 3 4 3 3 4 3 3 -3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 -3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 -3 3 4 3 3 4 3 3 4 3 3 4 3 3 4 3 3 -3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 -3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 -3 3 4 3 3 4 3 3 4 3 3 4 3 3 4 3 3 -3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 -3 3 3 4 3 3 3 3 3 3 3 3 3 4 3 3 3 -3 3 3 3 3 4 3 3 4 3 3 4 3 3 3 3 3 -3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 -3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 - - - 21.42 21.42 - 21 21 - -224.91 -224.91 - -5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 -5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 -5 5 5 5 5 5 5 6 6 6 6 6 6 6 5 5 5 5 5 5 5 -5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 -5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 -5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 -5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 -5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 -5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 -5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 -5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 -5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 -5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 -5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 -5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 -5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 -5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 -5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 -5 5 5 5 5 5 5 6 6 6 6 6 6 6 5 5 5 5 5 5 5 -5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 -5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 - - - 21.42 21.42 - 21 21 - -224.91 -224.91 - -7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 -7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 -7 7 7 7 7 7 7 8 8 8 8 8 8 8 7 7 7 7 7 7 7 -7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 -7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 -7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 -7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 -7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 -7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 -7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 -7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 -7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 -7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 -7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 -7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 -7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 -7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 -7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 -7 7 7 7 7 7 7 8 8 8 8 8 8 8 7 7 7 7 7 7 7 -7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 -7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 - - - - - - - - - - - - - - - - - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + eigenvalue - 100 - 10 - 5 - - - -160 -160 -183 160 160 183 - - + 1000 + 5 + 0 diff --git a/tests/regression_tests/filter_energyfun/results_true.dat b/tests/regression_tests/filter_energyfun/results_true.dat index 8e5577c432..a88cff4960 100644 --- a/tests/regression_tests/filter_energyfun/results_true.dat +++ b/tests/regression_tests/filter_energyfun/results_true.dat @@ -1,2 +1,2 @@ energyfunction nuclide score mean std. dev. -0 d2effa26cb3cf2 Am241 ((n,gamma) / (n,gamma)) 1.00e-01 9.97e-03 +0 d2effa26cb3cf2 Am241 ((n,gamma) / (n,gamma)) 1.74e-01 3.55e-03 diff --git a/tests/regression_tests/filter_energyfun/test.py b/tests/regression_tests/filter_energyfun/test.py index 7c5645b651..f61f05d785 100644 --- a/tests/regression_tests/filter_energyfun/test.py +++ b/tests/regression_tests/filter_energyfun/test.py @@ -1,36 +1,51 @@ import openmc +import pytest from tests.testing_harness import PyAPITestHarness +@pytest.fixture +def model(): + model = openmc.model.Model() + + m = openmc.Material() + m.set_density('g/cm3', 10.0) + m.add_nuclide('Am241', 1.0) + model.materials.append(m) + + s = openmc.Sphere(r=100.0, boundary_type='vacuum') + c = openmc.Cell(fill=m, region=-s) + model.geometry = openmc.Geometry([c]) + + model.settings.batches = 5 + model.settings.inactive = 0 + model.settings.particles = 1000 + + # Define Am242m / Am242 branching ratio from ENDF/B-VII.1 data. + x = [1e-5, 3.69e-1, 1e3, 1e5, 6e5, 1e6, 2e6, 4e6, 3e7] + y = [0.1, 0.1, 0.1333, 0.158, 0.18467, 0.25618, 0.4297, 0.48, 0.48] + + # Make an EnergyFunctionFilter directly from the x and y lists. + filt1 = openmc.EnergyFunctionFilter(x, y) + + # Also make a filter with the .from_tabulated1d constructor. Make sure + # the filters are identical. + tab1d = openmc.data.Tabulated1D(x, y) + filt2 = openmc.EnergyFunctionFilter.from_tabulated1d(tab1d) + assert filt1 == filt2, 'Error with the .from_tabulated1d constructor' + + # Make tallies + tallies = [openmc.Tally(), openmc.Tally()] + for t in tallies: + t.scores = ['(n,gamma)'] + t.nuclides = ['Am241'] + tallies[1].filters = [filt1] + model.tallies.extend(tallies) + + return model + + class FilterEnergyFunHarness(PyAPITestHarness): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - # Add Am241 to the fuel. - self._model.materials[1].add_nuclide('Am241', 1e-7) - - # Define Am242m / Am242 branching ratio from ENDF/B-VII.1 data. - x = [1e-5, 3.69e-1, 1e3, 1e5, 6e5, 1e6, 2e6, 4e6, 3e7] - y = [0.1, 0.1, 0.1333, 0.158, 0.18467, 0.25618, 0.4297, 0.48, 0.48] - - # Make an EnergyFunctionFilter directly from the x and y lists. - filt1 = openmc.EnergyFunctionFilter(x, y) - - # Also make a filter with the .from_tabulated1d constructor. Make sure - # the filters are identical. - tab1d = openmc.data.Tabulated1D(x, y) - filt2 = openmc.EnergyFunctionFilter.from_tabulated1d(tab1d) - assert filt1 == filt2, 'Error with the .from_tabulated1d constructor' - - # Make tallies. - tallies = [openmc.Tally(1), openmc.Tally(2)] - for t in tallies: - t.scores = ['(n,gamma)'] - t.nuclides = ['Am241'] - tallies[1].filters = [filt1] - self._model.tallies = tallies - def _get_results(self): # Read the statepoint file. sp = openmc.StatePoint(self._sp_name) @@ -42,6 +57,6 @@ class FilterEnergyFunHarness(PyAPITestHarness): return br_tally.get_pandas_dataframe().to_string() + '\n' -def test_filter_energyfun(): - harness = FilterEnergyFunHarness('statepoint.10.h5') +def test_filter_energyfun(model): + harness = FilterEnergyFunHarness('statepoint.5.h5', model) harness.main() diff --git a/tests/regression_tests/filter_mesh/inputs_true.dat b/tests/regression_tests/filter_mesh/inputs_true.dat index 6755a64c82..37716c02e4 100644 --- a/tests/regression_tests/filter_mesh/inputs_true.dat +++ b/tests/regression_tests/filter_mesh/inputs_true.dat @@ -1,333 +1,57 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1.26 1.26 - 17 17 - -10.71 -10.71 - -1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 -1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 -1 1 1 1 1 2 1 1 2 1 1 2 1 1 1 1 1 -1 1 1 2 1 1 1 1 1 1 1 1 1 2 1 1 1 -1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 -1 1 2 1 1 2 1 1 2 1 1 2 1 1 2 1 1 -1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 -1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 -1 1 2 1 1 2 1 1 2 1 1 2 1 1 2 1 1 -1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 -1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 -1 1 2 1 1 2 1 1 2 1 1 2 1 1 2 1 1 -1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 -1 1 1 2 1 1 1 1 1 1 1 1 1 2 1 1 1 -1 1 1 1 1 2 1 1 2 1 1 2 1 1 1 1 1 -1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 -1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 - - - 1.26 1.26 - 17 17 - -10.71 -10.71 - -3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 -3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 -3 3 3 3 3 4 3 3 4 3 3 4 3 3 3 3 3 -3 3 3 4 3 3 3 3 3 3 3 3 3 4 3 3 3 -3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 -3 3 4 3 3 4 3 3 4 3 3 4 3 3 4 3 3 -3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 -3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 -3 3 4 3 3 4 3 3 4 3 3 4 3 3 4 3 3 -3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 -3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 -3 3 4 3 3 4 3 3 4 3 3 4 3 3 4 3 3 -3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 -3 3 3 4 3 3 3 3 3 3 3 3 3 4 3 3 3 -3 3 3 3 3 4 3 3 4 3 3 4 3 3 3 3 3 -3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 -3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 - - - 21.42 21.42 - 21 21 - -224.91 -224.91 - -5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 -5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 -5 5 5 5 5 5 5 6 6 6 6 6 6 6 5 5 5 5 5 5 5 -5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 -5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 -5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 -5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 -5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 -5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 -5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 -5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 -5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 -5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 -5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 -5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 -5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 -5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 -5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 -5 5 5 5 5 5 5 6 6 6 6 6 6 6 5 5 5 5 5 5 5 -5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 -5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 - - - 21.42 21.42 - 21 21 - -224.91 -224.91 - -7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 -7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 -7 7 7 7 7 7 7 8 8 8 8 8 8 8 7 7 7 7 7 7 7 -7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 -7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 -7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 -7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 -7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 -7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 -7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 -7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 -7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 -7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 -7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 -7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 -7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 -7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 -7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 -7 7 7 7 7 7 7 8 8 8 8 8 8 8 7 7 7 7 7 7 7 -7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 -7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + - - - - - - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + eigenvalue - 100 - 10 - 5 - - - -160 -160 -183 160 160 183 - - + 1000 + 5 + 0 - 17 - -182.07 - 182.07 + 5 + -7.5 + 7.5 - 17 17 - -182.07 -182.07 - 182.07 182.07 + 5 5 + -7.5 -7.5 + 7.5 7.5 - 17 17 17 - -182.07 -182.07 -183.0 - 182.07 182.07 183.0 + 5 5 5 + -7.5 -7.5 -7.5 + 7.5 7.5 7.5 - -182.07 -160.65 -139.23 -117.81 -96.39 -74.97 -53.55000000000001 -32.129999999999995 -10.710000000000008 10.70999999999998 32.129999999999995 53.54999999999998 74.96999999999997 96.38999999999999 117.81 139.22999999999996 160.64999999999998 182.07 - -182.07 -160.65 -139.23 -117.81 -96.39 -74.97 -53.55000000000001 -32.129999999999995 -10.710000000000008 10.70999999999998 32.129999999999995 53.54999999999998 74.96999999999997 96.38999999999999 117.81 139.22999999999996 160.64999999999998 182.07 - 1.0 1.683624003879018 2.8345897864376153 4.772383405596668 8.034899257376447 13.52774925846868 22.77564337001445 38.34561988154435 64.55960607618856 108.69410247084474 182.99999999999991 + -7.5 -6.617647058823529 -5.735294117647059 -4.852941176470589 -3.9705882352941178 -3.0882352941176467 -2.2058823529411766 -1.3235294117647065 -0.4411764705882355 0.4411764705882355 1.3235294117647065 2.2058823529411757 3.0882352941176467 3.9705882352941178 4.852941176470587 5.735294117647058 6.617647058823529 7.5 + -7.5 -6.617647058823529 -5.735294117647059 -4.852941176470589 -3.9705882352941178 -3.0882352941176467 -2.2058823529411766 -1.3235294117647065 -0.4411764705882355 0.4411764705882355 1.3235294117647065 2.2058823529411757 3.0882352941176467 3.9705882352941178 4.852941176470587 5.735294117647058 6.617647058823529 7.5 + 1.0 1.223224374241637 1.4962778697388448 1.8302835609029084 2.2388474634702153 2.7386127875258306 3.3499379133114306 4.09772570775871 5.012437964687018 6.131336292779302 7.500000000000001 1 @@ -353,35 +77,35 @@ 4 - + 1 total - + 5 current - + 2 total - + 6 current - + 3 total - + 7 current - + 4 total - + 8 current diff --git a/tests/regression_tests/filter_mesh/results_true.dat b/tests/regression_tests/filter_mesh/results_true.dat index 05efffe2fb..10ea99f646 100644 --- a/tests/regression_tests/filter_mesh/results_true.dat +++ b/tests/regression_tests/filter_mesh/results_true.dat @@ -1 +1 @@ -35f04a6f062ef64116ef4eb0e9b803cd44cff7e185e2b53c9174afad8a26ca1a436ca9b800d6a228e006a9129f4536d7dce289d7a11cd56c6949d71d6a201b31 \ No newline at end of file +c3560155c2f713e5e2ad84451ddcd40484942faf94e2829db77df9b648ea880b3fba35c2a80dd1502e1ba62843e19e746638b2fe4961bde4ded3ce98624a2447 \ No newline at end of file diff --git a/tests/regression_tests/filter_mesh/test.py b/tests/regression_tests/filter_mesh/test.py index ed2e9b35b4..c8aa871a83 100644 --- a/tests/regression_tests/filter_mesh/test.py +++ b/tests/regression_tests/filter_mesh/test.py @@ -1,87 +1,84 @@ import numpy as np import openmc +import pytest from tests.testing_harness import HashedPyAPITestHarness -class FilterMeshTestHarness(HashedPyAPITestHarness): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) +@pytest.fixture +def model(): + model = openmc.model.Model() - # Initialize Meshes - mesh_1d = openmc.RegularMesh(mesh_id=1) - mesh_1d.dimension = [17] - mesh_1d.lower_left = [-182.07] - mesh_1d.upper_right = [182.07] + fuel = openmc.Material() + fuel.set_density('g/cm3', 10.0) + fuel.add_nuclide('U235', 1.0) + zr = openmc.Material() + zr.set_density('g/cm3', 1.0) + zr.add_nuclide('Zr90', 1.0) + model.materials.extend([fuel, zr]) - mesh_2d = openmc.RegularMesh(mesh_id=2) - mesh_2d.dimension = [17, 17] - mesh_2d.lower_left = [-182.07, -182.07] - mesh_2d.upper_right = [182.07, 182.07] + box1 = openmc.model.rectangular_prism(10.0, 10.0) + box2 = openmc.model.rectangular_prism(20.0, 20.0, boundary_type='reflective') + top = openmc.ZPlane(z0=10.0, boundary_type='vacuum') + bottom = openmc.ZPlane(z0=-10.0, boundary_type='vacuum') + cell1 = openmc.Cell(fill=fuel, region=box1 & +bottom & -top) + cell2 = openmc.Cell(fill=zr, region=~box1 & box2 & +bottom & -top) + model.geometry = openmc.Geometry([cell1, cell2]) - mesh_3d = openmc.RegularMesh(mesh_id=3) - mesh_3d.dimension = [17, 17, 17] - mesh_3d.lower_left = [-182.07, -182.07, -183.00] - mesh_3d.upper_right = [182.07, 182.07, 183.00] + model.settings.batches = 5 + model.settings.inactive = 0 + model.settings.particles = 1000 - recti_mesh = openmc.RectilinearMesh(mesh_id=4) - recti_mesh.x_grid = np.linspace(-182.07, 182.07, 18) - recti_mesh.y_grid = np.linspace(-182.07, 182.07, 18) - recti_mesh.z_grid = np.logspace(0, np.log10(183), 11) + # Create meshes + mesh_1d = openmc.RegularMesh() + mesh_1d.dimension = [5] + mesh_1d.lower_left = [-7.5] + mesh_1d.upper_right = [7.5] - # Initialize the filters - mesh_1d_filter = openmc.MeshFilter(mesh_1d) - mesh_2d_filter = openmc.MeshFilter(mesh_2d) - mesh_3d_filter = openmc.MeshFilter(mesh_3d) - recti_mesh_filter = openmc.MeshFilter(recti_mesh) - meshsurf_1d_filter = openmc.MeshSurfaceFilter(mesh_1d) - meshsurf_2d_filter = openmc.MeshSurfaceFilter(mesh_2d) - meshsurf_3d_filter = openmc.MeshSurfaceFilter(mesh_3d) - recti_meshsurf_filter = openmc.MeshSurfaceFilter(recti_mesh) + mesh_2d = openmc.RegularMesh() + mesh_2d.dimension = [5, 5] + mesh_2d.lower_left = [-7.5, -7.5] + mesh_2d.upper_right = [7.5, 7.5] - # Initialized the tallies - tally = openmc.Tally(name='tally 1') - tally.filters = [mesh_1d_filter] + mesh_3d = openmc.RegularMesh() + mesh_3d.dimension = [5, 5, 5] + mesh_3d.lower_left = [-7.5, -7.5, -7.5] + mesh_3d.upper_right = [7.5, 7.5, 7.5] + + recti_mesh = openmc.RectilinearMesh() + recti_mesh.x_grid = np.linspace(-7.5, 7.5, 18) + recti_mesh.y_grid = np.linspace(-7.5, 7.5, 18) + recti_mesh.z_grid = np.logspace(0, np.log10(7.5), 11) + + # Create filters + reg_filters = [ + openmc.MeshFilter(mesh_1d), + openmc.MeshFilter(mesh_2d), + openmc.MeshFilter(mesh_3d), + openmc.MeshFilter(recti_mesh) + ] + surf_filters = [ + openmc.MeshSurfaceFilter(mesh_1d), + openmc.MeshSurfaceFilter(mesh_2d), + openmc.MeshSurfaceFilter(mesh_3d), + openmc.MeshSurfaceFilter(recti_mesh) + ] + + # Create tallies + for f1, f2 in zip(reg_filters, surf_filters): + tally = openmc.Tally() + tally.filters = [f1] tally.scores = ['total'] - self._model.tallies.append(tally) - - tally = openmc.Tally(name='tally 2') - tally.filters = [meshsurf_1d_filter] + model.tallies.append(tally) + tally = openmc.Tally() + tally.filters = [f2] tally.scores = ['current'] - self._model.tallies.append(tally) + model.tallies.append(tally) - tally = openmc.Tally(name='tally 3') - tally.filters = [mesh_2d_filter] - tally.scores = ['total'] - self._model.tallies.append(tally) - - tally = openmc.Tally(name='tally 4') - tally.filters = [meshsurf_2d_filter] - tally.scores = ['current'] - self._model.tallies.append(tally) - - tally = openmc.Tally(name='tally 5') - tally.filters = [mesh_3d_filter] - tally.scores = ['total'] - self._model.tallies.append(tally) - - tally = openmc.Tally(name='tally 6') - tally.filters = [meshsurf_3d_filter] - tally.scores = ['current'] - self._model.tallies.append(tally) - - tally = openmc.Tally(name='tally 7') - tally.filters = [recti_mesh_filter] - tally.scores = ['total'] - self._model.tallies.append(tally) - - tally = openmc.Tally(name='tally 8') - tally.filters = [recti_meshsurf_filter] - tally.scores = ['current'] - self._model.tallies.append(tally) + return model -def test_filter_mesh(): - harness = FilterMeshTestHarness('statepoint.10.h5') +def test_filter_mesh(model): + harness = HashedPyAPITestHarness('statepoint.5.h5', model) harness.main() diff --git a/tests/regression_tests/lattice_multiple/geometry.xml b/tests/regression_tests/lattice_multiple/geometry.xml deleted file mode 100644 index f6f067aadd..0000000000 --- a/tests/regression_tests/lattice_multiple/geometry.xml +++ /dev/null @@ -1,181 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 17 17 - -10.71 -10.71 - 1.26 1.26 - - 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 - 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 - 1 1 1 1 1 2 1 1 2 1 1 2 1 1 1 1 1 - 1 1 1 2 1 1 1 1 1 1 1 1 1 2 1 1 1 - 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 - 1 1 2 1 1 2 1 1 2 1 1 2 1 1 2 1 1 - 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 - 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 - 1 1 2 1 1 2 1 1 2 1 1 2 1 1 2 1 1 - 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 - 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 - 1 1 2 1 1 2 1 1 2 1 1 2 1 1 2 1 1 - 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 - 1 1 1 2 1 1 1 1 1 1 1 1 1 2 1 1 1 - 1 1 1 1 1 2 1 1 2 1 1 2 1 1 1 1 1 - 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 - 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 - - - - - - 17 17 - -10.71 -10.71 - 1.26 1.26 - - 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 - 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 - 3 3 3 3 3 4 3 3 4 3 3 4 3 3 3 3 3 - 3 3 3 4 3 3 3 3 3 3 3 3 3 4 3 3 3 - 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 - 3 3 4 3 3 4 3 3 4 3 3 4 3 3 4 3 3 - 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 - 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 - 3 3 4 3 3 4 3 3 4 3 3 4 3 3 4 3 3 - 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 - 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 - 3 3 4 3 3 4 3 3 4 3 3 4 3 3 4 3 3 - 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 - 3 3 3 4 3 3 3 3 3 3 3 3 3 4 3 3 3 - 3 3 3 3 3 4 3 3 4 3 3 4 3 3 3 3 3 - 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 - 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 - - - - - - 21 21 - -224.91 -224.91 - 21.42 21.42 - - 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 - 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 - 5 5 5 5 5 5 5 6 6 6 6 6 6 6 5 5 5 5 5 5 5 - 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 - 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 - 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 - 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 - 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 - 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 - 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 - 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 - 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 - 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 - 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 - 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 - 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 - 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 - 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 - 5 5 5 5 5 5 5 6 6 6 6 6 6 6 5 5 5 5 5 5 5 - 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 - 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 - - - - - - 21 21 - -224.91 -224.91 - 21.42 21.42 - - 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 - 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 - 7 7 7 7 7 7 7 8 8 8 8 8 8 8 7 7 7 7 7 7 7 - 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 - 7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 - 7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 - 7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 - 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 - 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 - 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 - 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 - 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 - 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 - 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 - 7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 - 7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 - 7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 - 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 - 7 7 7 7 7 7 7 8 8 8 8 8 8 8 7 7 7 7 7 7 7 - 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 - 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 - - - - diff --git a/tests/regression_tests/lattice_multiple/inputs_true.dat b/tests/regression_tests/lattice_multiple/inputs_true.dat new file mode 100644 index 0000000000..c92e841bc9 --- /dev/null +++ b/tests/regression_tests/lattice_multiple/inputs_true.dat @@ -0,0 +1,53 @@ + + + + + + + + + + 1.2 1.2 + 1 + 2 2 + -1.2 -1.2 + +2 1 +1 1 + + + 2.4 2.4 + 2 2 + -2.4 -2.4 + +4 4 +4 4 + + + + + + + + + + + + + + + + + + + + + + + + + eigenvalue + 1000 + 10 + 5 + diff --git a/tests/regression_tests/lattice_multiple/materials.xml b/tests/regression_tests/lattice_multiple/materials.xml deleted file mode 100644 index 8021f5f99e..0000000000 --- a/tests/regression_tests/lattice_multiple/materials.xml +++ /dev/null @@ -1,270 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tests/regression_tests/lattice_multiple/results_true.dat b/tests/regression_tests/lattice_multiple/results_true.dat index d5d4bd4cc8..c1fa5321bf 100644 --- a/tests/regression_tests/lattice_multiple/results_true.dat +++ b/tests/regression_tests/lattice_multiple/results_true.dat @@ -1,2 +1,2 @@ k-combined: -9.581522E-01 4.261828E-02 +1.831313E+00 6.958576E-04 diff --git a/tests/regression_tests/lattice_multiple/settings.xml b/tests/regression_tests/lattice_multiple/settings.xml deleted file mode 100644 index 569c809821..0000000000 --- a/tests/regression_tests/lattice_multiple/settings.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - eigenvalue - 10 - 5 - 100 - - - - - -160 -160 -183 - 160 160 183 - - - - - diff --git a/tests/regression_tests/lattice_multiple/test.py b/tests/regression_tests/lattice_multiple/test.py index f0672d9a4e..c287c01027 100644 --- a/tests/regression_tests/lattice_multiple/test.py +++ b/tests/regression_tests/lattice_multiple/test.py @@ -1,6 +1,58 @@ -from tests.testing_harness import TestHarness +import numpy as np +import openmc +import pytest + +from tests.testing_harness import PyAPITestHarness -def test_lattice_multiple(): - harness = TestHarness('statepoint.10.h5') +@pytest.fixture +def model(): + model = openmc.model.Model() + + uo2 = openmc.Material(name='UO2') + uo2.set_density('g/cm3', 10.0) + uo2.add_nuclide('U235', 1.0) + uo2.add_nuclide('O16', 2.0) + water = openmc.Material(name='light water') + water.add_nuclide('H1', 2.0) + water.add_nuclide('O16', 1.0) + water.set_density('g/cm3', 1.0) + water.add_s_alpha_beta('c_H_in_H2O') + model.materials.extend([uo2, water]) + + cyl = openmc.ZCylinder(r=0.4) + big_cyl = openmc.ZCylinder(r=0.5) + pin = openmc.model.pin([cyl], [uo2, water]) + big_pin = openmc.model.pin([big_cyl], [uo2, water]) + + d = 1.2 + inner_lattice = openmc.RectLattice() + inner_lattice.lower_left = (-d, -d) + inner_lattice.pitch = (d, d) + inner_lattice.outer = pin + inner_lattice.universes = [ + [big_pin, pin], + [pin, pin], + ] + inner_cell = openmc.Cell(fill=inner_lattice) + inner_univ = openmc.Universe(cells=[inner_cell]) + + lattice = openmc.RectLattice() + lattice.lower_left = (-2*d, -2*d) + lattice.pitch = (2*d, 2*d) + lattice.universes = np.full((2, 2), inner_univ) + + box = openmc.model.rectangular_prism(4*d, 4*d, boundary_type='reflective') + main_cell = openmc.Cell(fill=lattice, region=box) + model.geometry = openmc.Geometry([main_cell]) + + model.settings.batches = 10 + model.settings.inactive = 5 + model.settings.particles = 1000 + + return model + + +def test_lattice_multiple(model): + harness = PyAPITestHarness('statepoint.10.h5', model) harness.main() diff --git a/tests/regression_tests/mgxs_library_mesh/inputs_true.dat b/tests/regression_tests/mgxs_library_mesh/inputs_true.dat index 4f138b24f6..f0f93d43c4 100644 --- a/tests/regression_tests/mgxs_library_mesh/inputs_true.dat +++ b/tests/regression_tests/mgxs_library_mesh/inputs_true.dat @@ -1,311 +1,35 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1.26 1.26 - 17 17 - -10.71 -10.71 - -1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 -1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 -1 1 1 1 1 2 1 1 2 1 1 2 1 1 1 1 1 -1 1 1 2 1 1 1 1 1 1 1 1 1 2 1 1 1 -1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 -1 1 2 1 1 2 1 1 2 1 1 2 1 1 2 1 1 -1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 -1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 -1 1 2 1 1 2 1 1 2 1 1 2 1 1 2 1 1 -1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 -1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 -1 1 2 1 1 2 1 1 2 1 1 2 1 1 2 1 1 -1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 -1 1 1 2 1 1 1 1 1 1 1 1 1 2 1 1 1 -1 1 1 1 1 2 1 1 2 1 1 2 1 1 1 1 1 -1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 -1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 - - - 1.26 1.26 - 17 17 - -10.71 -10.71 - -3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 -3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 -3 3 3 3 3 4 3 3 4 3 3 4 3 3 3 3 3 -3 3 3 4 3 3 3 3 3 3 3 3 3 4 3 3 3 -3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 -3 3 4 3 3 4 3 3 4 3 3 4 3 3 4 3 3 -3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 -3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 -3 3 4 3 3 4 3 3 4 3 3 4 3 3 4 3 3 -3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 -3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 -3 3 4 3 3 4 3 3 4 3 3 4 3 3 4 3 3 -3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 -3 3 3 4 3 3 3 3 3 3 3 3 3 4 3 3 3 -3 3 3 3 3 4 3 3 4 3 3 4 3 3 3 3 3 -3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 -3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 - - - 21.42 21.42 - 21 21 - -224.91 -224.91 - -5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 -5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 -5 5 5 5 5 5 5 6 6 6 6 6 6 6 5 5 5 5 5 5 5 -5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 -5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 -5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 -5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 -5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 -5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 -5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 -5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 -5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 -5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 -5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 -5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 -5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 -5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 -5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 -5 5 5 5 5 5 5 6 6 6 6 6 6 6 5 5 5 5 5 5 5 -5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 -5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 - - - 21.42 21.42 - 21 21 - -224.91 -224.91 - -7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 -7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 -7 7 7 7 7 7 7 8 8 8 8 8 8 8 7 7 7 7 7 7 7 -7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 -7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 -7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 -7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 -7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 -7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 -7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 -7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 -7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 -7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 -7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 -7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 -7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 -7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 -7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 -7 7 7 7 7 7 7 8 8 8 8 8 8 8 7 7 7 7 7 7 7 -7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 -7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + - - - - - - - + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + eigenvalue - 100 - 10 - 5 - - - -160 -160 -183 160 160 183 - - + 1000 + 5 + 0 diff --git a/tests/regression_tests/mgxs_library_mesh/results_true.dat b/tests/regression_tests/mgxs_library_mesh/results_true.dat index 829edde9f4..f1ff29d6ce 100644 --- a/tests/regression_tests/mgxs_library_mesh/results_true.dat +++ b/tests/regression_tests/mgxs_library_mesh/results_true.dat @@ -1,310 +1,310 @@ mesh 1 group in nuclide mean std. dev. x y z -0 1 1 1 1 total 0.762544 0.085298 -2 1 2 1 1 total 0.644837 0.088457 -1 2 1 1 1 total 0.653375 0.153317 -3 2 2 1 1 total 0.676480 0.094215 +0 1 1 1 1 total 0.105390 0.006421 +2 1 2 1 1 total 0.105466 0.003175 +1 2 1 1 1 total 0.106221 0.004040 +3 2 2 1 1 total 0.102641 0.002129 mesh 1 group in nuclide mean std. dev. x y z -0 1 1 1 1 total 0.473988 0.088732 -2 1 2 1 1 total 0.399254 0.091318 -1 2 1 1 1 total 0.379821 0.167092 -3 2 2 1 1 total 0.424265 0.099551 +0 1 1 1 1 total 0.078603 0.006888 +2 1 2 1 1 total 0.075950 0.003755 +1 2 1 1 1 total 0.074519 0.004589 +3 2 2 1 1 total 0.072616 0.002838 mesh 1 group in nuclide mean std. dev. x y z -0 1 1 1 1 total 0.473988 0.088732 -2 1 2 1 1 total 0.399254 0.091318 -1 2 1 1 1 total 0.379821 0.167092 -3 2 2 1 1 total 0.424265 0.099551 +0 1 1 1 1 total 0.078605 0.006892 +2 1 2 1 1 total 0.075989 0.003746 +1 2 1 1 1 total 0.074571 0.004600 +3 2 2 1 1 total 0.072586 0.002824 mesh 1 group in nuclide mean std. dev. x y z -0 1 1 1 1 total 0.027288 0.005813 -2 1 2 1 1 total 0.020262 0.003701 -1 2 1 1 1 total 0.019449 0.004420 -3 2 2 1 1 total 0.021266 0.002869 +0 1 1 1 1 total 0.013600 0.000926 +2 1 2 1 1 total 0.013584 0.000551 +1 2 1 1 1 total 0.013692 0.000712 +3 2 2 1 1 total 0.013022 0.000430 mesh 1 group in nuclide mean std. dev. x y z -0 1 1 1 1 total 0.016037 0.006339 -2 1 2 1 1 total 0.013018 0.003521 -1 2 1 1 1 total 0.012153 0.003804 -3 2 2 1 1 total 0.012965 0.002454 +0 1 1 1 1 total 0.001333 0.001105 +2 1 2 1 1 total 0.001339 0.000693 +1 2 1 1 1 total 0.001330 0.000885 +3 2 2 1 1 total 0.001260 0.000534 mesh 1 group in nuclide mean std. dev. x y z -0 1 1 1 1 total 0.011251 0.003050 -2 1 2 1 1 total 0.007243 0.001219 -1 2 1 1 1 total 0.007296 0.001795 -3 2 2 1 1 total 0.008301 0.001066 +0 1 1 1 1 total 0.012266 0.000830 +2 1 2 1 1 total 0.012244 0.000486 +1 2 1 1 1 total 0.012361 0.000650 +3 2 2 1 1 total 0.011762 0.000376 mesh 1 group in nuclide mean std. dev. x y z -0 1 1 1 1 total 0.027498 0.007445 -2 1 2 1 1 total 0.017954 0.003077 -1 2 1 1 1 total 0.017912 0.004426 -3 2 2 1 1 total 0.020469 0.002617 +0 1 1 1 1 total 0.032001 0.002161 +2 1 2 1 1 total 0.031882 0.001271 +1 2 1 1 1 total 0.032193 0.001701 +3 2 2 1 1 total 0.030726 0.001000 mesh 1 group in nuclide mean std. dev. x y z -0 1 1 1 1 total 2.177345e+06 589804.301157 -2 1 2 1 1 total 1.404096e+06 236476.852674 -1 2 1 1 1 total 1.413154e+06 347806.623478 -3 2 2 1 1 total 1.608259e+06 206502.707123 +0 1 1 1 1 total 2.372379e+06 160440.303797 +2 1 2 1 1 total 2.368109e+06 93914.371991 +1 2 1 1 1 total 2.390701e+06 125743.883417 +3 2 2 1 1 total 2.274785e+06 72785.094827 mesh 1 group in nuclide mean std. dev. x y z -0 1 1 1 1 total 0.735256 0.080216 -2 1 2 1 1 total 0.624575 0.084974 -1 2 1 1 1 total 0.633925 0.149098 -3 2 2 1 1 total 0.655214 0.091422 +0 1 1 1 1 total 0.091790 0.005503 +2 1 2 1 1 total 0.091883 0.002653 +1 2 1 1 1 total 0.092530 0.003354 +3 2 2 1 1 total 0.089619 0.001721 mesh 1 group in nuclide mean std. dev. x y z -0 1 1 1 1 total 0.763779 0.070696 -2 1 2 1 1 total 0.628158 0.064356 -1 2 1 1 1 total 0.640809 0.158369 -3 2 2 1 1 total 0.645171 0.080467 +0 1 1 1 1 total 0.087817 0.005624 +2 1 2 1 1 total 0.090790 0.005246 +1 2 1 1 1 total 0.093736 0.005609 +3 2 2 1 1 total 0.092035 0.003633 mesh 1 group in group out legendre nuclide mean std. dev. x y z -0 1 1 1 1 1 P0 total 0.763779 0.070696 -1 1 1 1 1 1 P1 total 0.288556 0.024446 -2 1 1 1 1 1 P2 total 0.082441 0.011443 -3 1 1 1 1 1 P3 total -0.005627 0.012638 -8 1 2 1 1 1 P0 total 0.628158 0.064356 -9 1 2 1 1 1 P1 total 0.245583 0.022676 -10 1 2 1 1 1 P2 total 0.086370 0.007833 -11 1 2 1 1 1 P3 total 0.019590 0.005345 -4 2 1 1 1 1 P0 total 0.640809 0.158369 -5 2 1 1 1 1 P1 total 0.273553 0.066437 -6 2 1 1 1 1 P2 total 0.108446 0.024435 -7 2 1 1 1 1 P3 total 0.012229 0.003785 -12 2 2 1 1 1 P0 total 0.645171 0.080467 -13 2 2 1 1 1 P1 total 0.252215 0.032154 -14 2 2 1 1 1 P2 total 0.089251 0.009734 -15 2 2 1 1 1 P3 total 0.004748 0.002987 +0 1 1 1 1 1 P0 total 0.087684 0.005584 +1 1 1 1 1 1 P1 total 0.026787 0.002493 +2 1 1 1 1 1 P2 total 0.014937 0.001035 +3 1 1 1 1 1 P3 total 0.007893 0.001109 +8 1 2 1 1 1 P0 total 0.090687 0.005242 +9 1 2 1 1 1 P1 total 0.029516 0.002004 +10 1 2 1 1 1 P2 total 0.016952 0.001093 +11 1 2 1 1 1 P3 total 0.008019 0.001095 +4 2 1 1 1 1 P0 total 0.093670 0.005616 +5 2 1 1 1 1 P1 total 0.031703 0.002177 +6 2 1 1 1 1 P2 total 0.017922 0.001352 +7 2 1 1 1 1 P3 total 0.011171 0.001055 +12 2 2 1 1 1 P0 total 0.091808 0.003617 +13 2 2 1 1 1 P1 total 0.030025 0.001876 +14 2 2 1 1 1 P2 total 0.015181 0.002277 +15 2 2 1 1 1 P3 total 0.009550 0.001713 mesh 1 group in group out legendre nuclide mean std. dev. x y z -0 1 1 1 1 1 P0 total 0.763779 0.070696 -1 1 1 1 1 1 P1 total 0.288556 0.024446 -2 1 1 1 1 1 P2 total 0.082441 0.011443 -3 1 1 1 1 1 P3 total -0.005627 0.012638 -8 1 2 1 1 1 P0 total 0.628158 0.064356 -9 1 2 1 1 1 P1 total 0.245583 0.022676 -10 1 2 1 1 1 P2 total 0.086370 0.007833 -11 1 2 1 1 1 P3 total 0.019590 0.005345 -4 2 1 1 1 1 P0 total 0.640809 0.158369 -5 2 1 1 1 1 P1 total 0.273553 0.066437 -6 2 1 1 1 1 P2 total 0.108446 0.024435 -7 2 1 1 1 1 P3 total 0.012229 0.003785 -12 2 2 1 1 1 P0 total 0.645171 0.080467 -13 2 2 1 1 1 P1 total 0.252215 0.032154 -14 2 2 1 1 1 P2 total 0.089251 0.009734 -15 2 2 1 1 1 P3 total 0.004748 0.002987 - mesh 1 group in group out nuclide mean std. dev. - x y z -0 1 1 1 1 1 total 1.0 0.108337 -2 1 2 1 1 1 total 1.0 0.113128 -1 2 1 1 1 1 total 1.0 0.238517 -3 2 2 1 1 1 total 1.0 0.132597 +0 1 1 1 1 1 P0 total 0.087817 0.005624 +1 1 1 1 1 1 P1 total 0.026785 0.002504 +2 1 1 1 1 1 P2 total 0.014973 0.001041 +3 1 1 1 1 1 P3 total 0.007913 0.001144 +8 1 2 1 1 1 P0 total 0.090790 0.005246 +9 1 2 1 1 1 P1 total 0.029477 0.001987 +10 1 2 1 1 1 P2 total 0.016940 0.001094 +11 1 2 1 1 1 P3 total 0.008033 0.001104 +4 2 1 1 1 1 P0 total 0.093736 0.005609 +5 2 1 1 1 1 P1 total 0.031651 0.002201 +6 2 1 1 1 1 P2 total 0.017953 0.001364 +7 2 1 1 1 1 P3 total 0.011158 0.001044 +12 2 2 1 1 1 P0 total 0.092035 0.003633 +13 2 2 1 1 1 P1 total 0.030055 0.001856 +14 2 2 1 1 1 P2 total 0.015245 0.002274 +15 2 2 1 1 1 P3 total 0.009534 0.001700 mesh 1 group in group out nuclide mean std. dev. x y z -0 1 1 1 1 1 total 0.015584 0.003404 -2 1 2 1 1 1 total 0.017684 0.002499 -1 2 1 1 1 1 total 0.014200 0.003676 -3 2 2 1 1 1 total 0.022409 0.002481 +0 1 1 1 1 1 total 1.001515 0.075311 +2 1 2 1 1 1 total 1.001135 0.061671 +1 2 1 1 1 1 total 1.000704 0.055977 +3 2 2 1 1 1 total 1.002471 0.042246 + mesh 1 group in group out nuclide mean std. dev. + x y z +0 1 1 1 1 1 total 0.031246 0.001839 +2 1 2 1 1 1 total 0.032452 0.002365 +1 2 1 1 1 1 total 0.032568 0.002068 +3 2 2 1 1 1 total 0.031529 0.001639 mesh 1 group in group out nuclide mean std. dev. x y z -0 1 1 1 1 1 total 1.0 0.108337 -2 1 2 1 1 1 total 1.0 0.113128 -1 2 1 1 1 1 total 1.0 0.238517 -3 2 2 1 1 1 total 1.0 0.132597 +0 1 1 1 1 1 total 1.0 0.074891 +2 1 2 1 1 1 total 1.0 0.061618 +1 2 1 1 1 1 total 1.0 0.056068 +3 2 2 1 1 1 total 1.0 0.042067 mesh 1 group in group out legendre nuclide mean std. dev. x y z -0 1 1 1 1 1 P0 total 0.735256 0.113047 -1 1 1 1 1 1 P1 total 0.277780 0.041434 -2 1 1 1 1 1 P2 total 0.079362 0.014706 -3 1 1 1 1 1 P3 total -0.005417 0.012184 -8 1 2 1 1 1 P0 total 0.624575 0.110512 -9 1 2 1 1 1 P1 total 0.244182 0.041824 -10 1 2 1 1 1 P2 total 0.085877 0.014634 -11 1 2 1 1 1 P3 total 0.019478 0.006012 -4 2 1 1 1 1 P0 total 0.633925 0.212349 -5 2 1 1 1 1 P1 total 0.270615 0.089799 -6 2 1 1 1 1 P2 total 0.107281 0.034246 -7 2 1 1 1 1 P3 total 0.012098 0.004637 -12 2 2 1 1 1 P0 total 0.655214 0.126119 -13 2 2 1 1 1 P1 total 0.256141 0.049765 -14 2 2 1 1 1 P2 total 0.090641 0.016563 -15 2 2 1 1 1 P3 total 0.004822 0.003115 +0 1 1 1 1 1 P0 total 0.091790 0.008806 +1 1 1 1 1 1 P1 total 0.028042 0.003295 +2 1 1 1 1 1 P2 total 0.015636 0.001560 +3 1 1 1 1 1 P3 total 0.008263 0.001304 +8 1 2 1 1 1 P0 total 0.091883 0.006252 +9 1 2 1 1 1 P1 total 0.029905 0.002297 +10 1 2 1 1 1 P2 total 0.017175 0.001268 +11 1 2 1 1 1 P3 total 0.008124 0.001147 +4 2 1 1 1 1 P0 total 0.092530 0.006177 +5 2 1 1 1 1 P1 total 0.031317 0.002339 +6 2 1 1 1 1 P2 total 0.017704 0.001433 +7 2 1 1 1 1 P3 total 0.011035 0.001092 +12 2 2 1 1 1 P0 total 0.089619 0.004144 +13 2 2 1 1 1 P1 total 0.029309 0.001964 +14 2 2 1 1 1 P2 total 0.014820 0.002251 +15 2 2 1 1 1 P3 total 0.009322 0.001687 mesh 1 group in group out legendre nuclide mean std. dev. x y z -0 1 1 1 1 1 P0 total 0.735256 0.138292 -1 1 1 1 1 1 P1 total 0.277780 0.051210 -2 1 1 1 1 1 P2 total 0.079362 0.017035 -3 1 1 1 1 1 P3 total -0.005417 0.012198 -8 1 2 1 1 1 P0 total 0.624575 0.131169 -9 1 2 1 1 1 P1 total 0.244182 0.050123 -10 1 2 1 1 1 P2 total 0.085877 0.017565 -11 1 2 1 1 1 P3 total 0.019478 0.006403 -4 2 1 1 1 1 P0 total 0.633925 0.260681 -5 2 1 1 1 1 P1 total 0.270615 0.110590 -6 2 1 1 1 1 P2 total 0.107281 0.042750 -7 2 1 1 1 1 P3 total 0.012098 0.005462 -12 2 2 1 1 1 P0 total 0.655214 0.153147 -13 2 2 1 1 1 P1 total 0.256141 0.060250 -14 2 2 1 1 1 P2 total 0.090641 0.020464 -15 2 2 1 1 1 P3 total 0.004822 0.003180 +0 1 1 1 1 1 P0 total 0.091929 0.011205 +1 1 1 1 1 1 P1 total 0.028084 0.003918 +2 1 1 1 1 1 P2 total 0.015660 0.001956 +3 1 1 1 1 1 P3 total 0.008276 0.001447 +8 1 2 1 1 1 P0 total 0.091987 0.008443 +9 1 2 1 1 1 P1 total 0.029939 0.002948 +10 1 2 1 1 1 P2 total 0.017195 0.001653 +11 1 2 1 1 1 P3 total 0.008134 0.001253 +4 2 1 1 1 1 P0 total 0.092595 0.008065 +5 2 1 1 1 1 P1 total 0.031339 0.002924 +6 2 1 1 1 1 P2 total 0.017716 0.001743 +7 2 1 1 1 1 P3 total 0.011042 0.001255 +12 2 2 1 1 1 P0 total 0.089840 0.005621 +13 2 2 1 1 1 P1 total 0.029381 0.002326 +14 2 2 1 1 1 P2 total 0.014856 0.002342 +15 2 2 1 1 1 P3 total 0.009345 0.001737 mesh 1 group out nuclide mean std. dev. x y z -0 1 1 1 1 total 1.0 0.300047 -2 1 2 1 1 total 1.0 0.178169 -1 2 1 1 1 total 1.0 0.262180 -3 2 2 1 1 total 1.0 0.104797 +0 1 1 1 1 total 1.0 0.066520 +2 1 2 1 1 total 1.0 0.087934 +1 2 1 1 1 total 1.0 0.063390 +3 2 2 1 1 total 1.0 0.063791 mesh 1 group out nuclide mean std. dev. x y z -0 1 1 1 1 total 1.0 0.300047 -2 1 2 1 1 total 1.0 0.178169 -1 2 1 1 1 total 1.0 0.262180 -3 2 2 1 1 total 1.0 0.108931 +0 1 1 1 1 total 1.0 0.068463 +2 1 2 1 1 total 1.0 0.091776 +1 2 1 1 1 total 1.0 0.064705 +3 2 2 1 1 total 1.0 0.063003 mesh 1 group in nuclide mean std. dev. x y z -0 1 1 1 1 total 7.097008e-07 1.458546e-07 -2 1 2 1 1 total 4.407745e-07 7.903907e-08 -1 2 1 1 1 total 3.984535e-07 1.157576e-07 -3 2 2 1 1 total 4.750476e-07 6.207437e-08 +0 1 1 1 1 total 8.735713e-10 4.530341e-11 +2 1 2 1 1 total 8.821319e-10 3.206094e-11 +1 2 1 1 1 total 8.699208e-10 2.515246e-11 +3 2 2 1 1 total 8.738762e-10 1.734562e-11 mesh 1 group in nuclide mean std. dev. x y z -0 1 1 1 1 total 0.027311 0.007397 -2 1 2 1 1 total 0.017820 0.003054 -1 2 1 1 1 total 0.017783 0.004394 -3 2 2 1 1 total 0.020320 0.002598 +0 1 1 1 1 total 0.031799 0.002147 +2 1 2 1 1 total 0.031680 0.001263 +1 2 1 1 1 total 0.031989 0.001691 +3 2 2 1 1 total 0.030533 0.000994 mesh 1 group in group out nuclide mean std. dev. x y z -0 1 1 1 1 1 total 0.015584 0.003404 -2 1 2 1 1 1 total 0.017684 0.002499 -1 2 1 1 1 1 total 0.014200 0.003676 -3 2 2 1 1 1 total 0.022259 0.002508 +0 1 1 1 1 1 total 0.031056 0.001862 +2 1 2 1 1 1 total 0.032188 0.002420 +1 2 1 1 1 1 total 0.032304 0.002073 +3 2 2 1 1 1 total 0.031336 0.001614 mesh 1 delayedgroup group in nuclide mean std. dev. x y z -0 1 1 1 1 1 total 0.000006 1.689606e-06 -1 1 1 1 2 1 total 0.000033 8.718916e-06 -2 1 1 1 3 1 total 0.000032 8.323051e-06 -3 1 1 1 4 1 total 0.000072 1.866015e-05 -4 1 1 1 5 1 total 0.000031 7.654909e-06 -5 1 1 1 6 1 total 0.000013 3.206343e-06 -12 1 2 1 1 1 total 0.000004 6.723192e-07 -13 1 2 1 2 1 total 0.000022 3.706235e-06 -14 1 2 1 3 1 total 0.000022 3.674263e-06 -15 1 2 1 4 1 total 0.000052 8.774048e-06 -16 1 2 1 5 1 total 0.000024 4.168024e-06 -17 1 2 1 6 1 total 0.000010 1.726268e-06 -6 2 1 1 1 1 total 0.000004 1.003100e-06 -7 2 1 1 2 1 total 0.000022 5.425275e-06 -8 2 1 1 3 1 total 0.000021 5.324236e-06 -9 2 1 1 4 1 total 0.000050 1.251572e-05 -10 2 1 1 5 1 total 0.000022 5.762184e-06 -11 2 1 1 6 1 total 0.000009 2.391676e-06 -18 2 2 1 1 1 total 0.000005 5.962367e-07 -19 2 2 1 2 1 total 0.000025 3.200900e-06 -20 2 2 1 3 1 total 0.000025 3.127442e-06 -21 2 2 1 4 1 total 0.000058 7.296157e-06 -22 2 2 1 5 1 total 0.000026 3.298196e-06 -23 2 2 1 6 1 total 0.000011 1.370918e-06 +0 1 1 1 1 1 total 0.000007 4.734745e-07 +1 1 1 1 2 1 total 0.000036 2.443930e-06 +2 1 1 1 3 1 total 0.000035 2.333188e-06 +3 1 1 1 4 1 total 0.000078 5.231199e-06 +4 1 1 1 5 1 total 0.000032 2.144718e-06 +5 1 1 1 6 1 total 0.000013 8.984148e-07 +12 1 2 1 1 1 total 0.000007 2.770884e-07 +13 1 2 1 2 1 total 0.000036 1.430245e-06 +14 1 2 1 3 1 total 0.000035 1.365436e-06 +15 1 2 1 4 1 total 0.000078 3.061421e-06 +16 1 2 1 5 1 total 0.000032 1.255139e-06 +17 1 2 1 6 1 total 0.000013 5.257735e-07 +6 2 1 1 1 1 total 0.000007 3.731284e-07 +7 2 1 1 2 1 total 0.000037 1.925974e-06 +8 2 1 1 3 1 total 0.000035 1.838702e-06 +9 2 1 1 4 1 total 0.000079 4.122522e-06 +10 2 1 1 5 1 total 0.000032 1.690176e-06 +11 2 1 1 6 1 total 0.000014 7.080087e-07 +18 2 2 1 1 1 total 0.000007 2.050310e-07 +19 2 2 1 2 1 total 0.000035 1.058307e-06 +20 2 2 1 3 1 total 0.000033 1.010352e-06 +21 2 2 1 4 1 total 0.000075 2.265292e-06 +22 2 2 1 5 1 total 0.000031 9.287379e-07 +23 2 2 1 6 1 total 0.000013 3.890451e-07 mesh 1 delayedgroup group out nuclide mean std. dev. x y z 0 1 1 1 1 1 total 0.0 0.000000 -1 1 1 1 2 1 total 0.0 0.000000 -2 1 1 1 3 1 total 0.0 0.000000 -3 1 1 1 4 1 total 0.0 0.000000 -4 1 1 1 5 1 total 0.0 0.000000 +1 1 1 1 2 1 total 1.0 1.414214 +2 1 1 1 3 1 total 1.0 0.868831 +3 1 1 1 4 1 total 1.0 1.414214 +4 1 1 1 5 1 total 1.0 1.414214 5 1 1 1 6 1 total 0.0 0.000000 12 1 2 1 1 1 total 0.0 0.000000 -13 1 2 1 2 1 total 0.0 0.000000 +13 1 2 1 2 1 total 1.0 0.866827 14 1 2 1 3 1 total 0.0 0.000000 -15 1 2 1 4 1 total 0.0 0.000000 -16 1 2 1 5 1 total 0.0 0.000000 +15 1 2 1 4 1 total 1.0 0.455171 +16 1 2 1 5 1 total 1.0 0.868553 17 1 2 1 6 1 total 0.0 0.000000 6 2 1 1 1 1 total 0.0 0.000000 -7 2 1 1 2 1 total 0.0 0.000000 -8 2 1 1 3 1 total 0.0 0.000000 -9 2 1 1 4 1 total 0.0 0.000000 -10 2 1 1 5 1 total 0.0 0.000000 -11 2 1 1 6 1 total 0.0 0.000000 -18 2 2 1 1 1 total 0.0 0.000000 -19 2 2 1 2 1 total 0.0 0.000000 +7 2 1 1 2 1 total 1.0 1.414214 +8 2 1 1 3 1 total 1.0 1.414214 +9 2 1 1 4 1 total 1.0 0.674843 +10 2 1 1 5 1 total 1.0 1.414214 +11 2 1 1 6 1 total 1.0 0.866033 +18 2 2 1 1 1 total 1.0 1.414214 +19 2 2 1 2 1 total 1.0 1.414214 20 2 2 1 3 1 total 1.0 1.414214 -21 2 2 1 4 1 total 0.0 0.000000 +21 2 2 1 4 1 total 1.0 0.579059 22 2 2 1 5 1 total 0.0 0.000000 -23 2 2 1 6 1 total 0.0 0.000000 +23 2 2 1 6 1 total 1.0 1.414214 mesh 1 delayedgroup group in nuclide mean std. dev. x y z -0 1 1 1 1 1 total 0.000228 0.000084 -1 1 1 1 2 1 total 0.001195 0.000438 -2 1 1 1 3 1 total 0.001153 0.000420 -3 1 1 1 4 1 total 0.002629 0.000950 -4 1 1 1 5 1 total 0.001125 0.000398 -5 1 1 1 6 1 total 0.000470 0.000166 -12 1 2 1 1 1 total 0.000225 0.000044 -13 1 2 1 2 1 total 0.001232 0.000242 -14 1 2 1 3 1 total 0.001216 0.000239 -15 1 2 1 4 1 total 0.002882 0.000570 -16 1 2 1 5 1 total 0.001345 0.000270 -17 1 2 1 6 1 total 0.000558 0.000112 -6 2 1 1 1 1 total 0.000228 0.000057 -7 2 1 1 2 1 total 0.001222 0.000309 -8 2 1 1 3 1 total 0.001193 0.000304 -9 2 1 1 4 1 total 0.002780 0.000713 -10 2 1 1 5 1 total 0.001250 0.000328 -11 2 1 1 6 1 total 0.000520 0.000136 -18 2 2 1 1 1 total 0.000227 0.000027 -19 2 2 1 2 1 total 0.001225 0.000143 -20 2 2 1 3 1 total 0.001201 0.000140 -21 2 2 1 4 1 total 0.002815 0.000326 -22 2 2 1 5 1 total 0.001284 0.000147 -23 2 2 1 6 1 total 0.000533 0.000061 +0 1 1 1 1 1 total 0.000221 0.000019 +1 1 1 1 2 1 total 0.001139 0.000096 +2 1 1 1 3 1 total 0.001087 0.000092 +3 1 1 1 4 1 total 0.002437 0.000206 +4 1 1 1 5 1 total 0.000999 0.000084 +5 1 1 1 6 1 total 0.000419 0.000035 +12 1 2 1 1 1 total 0.000222 0.000012 +13 1 2 1 2 1 total 0.001144 0.000060 +14 1 2 1 3 1 total 0.001092 0.000057 +15 1 2 1 4 1 total 0.002448 0.000129 +16 1 2 1 5 1 total 0.001004 0.000053 +17 1 2 1 6 1 total 0.000420 0.000022 +6 2 1 1 1 1 total 0.000221 0.000015 +7 2 1 1 2 1 total 0.001143 0.000078 +8 2 1 1 3 1 total 0.001091 0.000075 +9 2 1 1 4 1 total 0.002446 0.000167 +10 2 1 1 5 1 total 0.001003 0.000069 +11 2 1 1 6 1 total 0.000420 0.000029 +18 2 2 1 1 1 total 0.000220 0.000009 +19 2 2 1 2 1 total 0.001136 0.000047 +20 2 2 1 3 1 total 0.001084 0.000045 +21 2 2 1 4 1 total 0.002431 0.000100 +22 2 2 1 5 1 total 0.000997 0.000041 +23 2 2 1 6 1 total 0.000417 0.000017 mesh 1 delayedgroup group in nuclide mean std. dev. x y z -0 1 1 1 1 1 total 0.013345 0.004923 -1 1 1 1 2 1 total 0.032674 0.011850 -2 1 1 1 3 1 total 0.120923 0.043307 -3 1 1 1 4 1 total 0.304289 0.106753 -4 1 1 1 5 1 total 0.855760 0.286466 -5 1 1 1 6 1 total 2.874120 0.965609 -12 1 2 1 1 1 total 0.013367 0.002548 -13 1 2 1 2 1 total 0.032520 0.006266 -14 1 2 1 3 1 total 0.121250 0.023544 -15 1 2 1 4 1 total 0.307552 0.060464 -16 1 2 1 5 1 total 0.867665 0.175131 -17 1 2 1 6 1 total 2.914635 0.587161 -6 2 1 1 1 1 total 0.013357 0.003345 -7 2 1 1 2 1 total 0.032590 0.008273 -8 2 1 1 3 1 total 0.121103 0.031074 -9 2 1 1 4 1 total 0.306111 0.080011 -10 2 1 1 5 1 total 0.862660 0.235694 -11 2 1 1 6 1 total 2.897534 0.788926 -18 2 2 1 1 1 total 0.013360 0.001587 -19 2 2 1 2 1 total 0.032564 0.003810 -20 2 2 1 3 1 total 0.121158 0.014038 -21 2 2 1 4 1 total 0.306653 0.035052 -22 2 2 1 5 1 total 0.864587 0.096680 -23 2 2 1 6 1 total 2.904111 0.325170 - mesh 1 delayedgroup group in group out nuclide mean std. dev. - x y z -0 1 1 1 1 1 1 total 0.00000 0.000000 -1 1 1 1 2 1 1 total 0.00000 0.000000 -2 1 1 1 3 1 1 total 0.00000 0.000000 -3 1 1 1 4 1 1 total 0.00000 0.000000 -4 1 1 1 5 1 1 total 0.00000 0.000000 -5 1 1 1 6 1 1 total 0.00000 0.000000 -12 1 2 1 1 1 1 total 0.00000 0.000000 -13 1 2 1 2 1 1 total 0.00000 0.000000 -14 1 2 1 3 1 1 total 0.00000 0.000000 -15 1 2 1 4 1 1 total 0.00000 0.000000 -16 1 2 1 5 1 1 total 0.00000 0.000000 -17 1 2 1 6 1 1 total 0.00000 0.000000 -6 2 1 1 1 1 1 total 0.00000 0.000000 -7 2 1 1 2 1 1 total 0.00000 0.000000 -8 2 1 1 3 1 1 total 0.00000 0.000000 -9 2 1 1 4 1 1 total 0.00000 0.000000 -10 2 1 1 5 1 1 total 0.00000 0.000000 -11 2 1 1 6 1 1 total 0.00000 0.000000 -18 2 2 1 1 1 1 total 0.00000 0.000000 -19 2 2 1 2 1 1 total 0.00000 0.000000 -20 2 2 1 3 1 1 total 0.00015 0.000151 -21 2 2 1 4 1 1 total 0.00000 0.000000 -22 2 2 1 5 1 1 total 0.00000 0.000000 -23 2 2 1 6 1 1 total 0.00000 0.000000 +0 1 1 1 1 1 total 0.013336 0.001120 +1 1 1 1 2 1 total 0.032739 0.002751 +2 1 1 1 3 1 total 0.120780 0.010147 +3 1 1 1 4 1 total 0.302780 0.025438 +4 1 1 1 5 1 total 0.849490 0.071370 +5 1 1 1 6 1 total 2.853000 0.239696 +12 1 2 1 1 1 total 0.013336 0.000695 +13 1 2 1 2 1 total 0.032739 0.001707 +14 1 2 1 3 1 total 0.120780 0.006296 +15 1 2 1 4 1 total 0.302780 0.015784 +16 1 2 1 5 1 total 0.849490 0.044285 +17 1 2 1 6 1 total 2.853000 0.148730 +6 2 1 1 1 1 total 0.013336 0.000906 +7 2 1 1 2 1 total 0.032739 0.002224 +8 2 1 1 3 1 total 0.120780 0.008205 +9 2 1 1 4 1 total 0.302780 0.020570 +10 2 1 1 5 1 total 0.849490 0.057711 +11 2 1 1 6 1 total 2.853000 0.193821 +18 2 2 1 1 1 total 0.013336 0.000528 +19 2 2 1 2 1 total 0.032739 0.001296 +20 2 2 1 3 1 total 0.120780 0.004781 +21 2 2 1 4 1 total 0.302780 0.011986 +22 2 2 1 5 1 total 0.849490 0.033628 +23 2 2 1 6 1 total 2.853000 0.112940 + mesh 1 delayedgroup group in group out nuclide mean std. dev. + x y z +0 1 1 1 1 1 1 total 0.000000 0.000000 +1 1 1 1 2 1 1 total 0.000055 0.000055 +2 1 1 1 3 1 1 total 0.000055 0.000034 +3 1 1 1 4 1 1 total 0.000052 0.000052 +4 1 1 1 5 1 1 total 0.000029 0.000029 +5 1 1 1 6 1 1 total 0.000000 0.000000 +12 1 2 1 1 1 1 total 0.000000 0.000000 +13 1 2 1 2 1 1 total 0.000058 0.000036 +14 1 2 1 3 1 1 total 0.000000 0.000000 +15 1 2 1 4 1 1 total 0.000149 0.000048 +16 1 2 1 5 1 1 total 0.000057 0.000035 +17 1 2 1 6 1 1 total 0.000000 0.000000 +6 2 1 1 1 1 1 total 0.000000 0.000000 +7 2 1 1 2 1 1 total 0.000027 0.000027 +8 2 1 1 3 1 1 total 0.000026 0.000026 +9 2 1 1 4 1 1 total 0.000105 0.000051 +10 2 1 1 5 1 1 total 0.000054 0.000054 +11 2 1 1 6 1 1 total 0.000051 0.000031 +18 2 2 1 1 1 1 total 0.000028 0.000028 +19 2 2 1 2 1 1 total 0.000025 0.000025 +20 2 2 1 3 1 1 total 0.000032 0.000032 +21 2 2 1 4 1 1 total 0.000080 0.000033 +22 2 2 1 5 1 1 total 0.000000 0.000000 +23 2 2 1 6 1 1 total 0.000027 0.000027 diff --git a/tests/regression_tests/mgxs_library_mesh/test.py b/tests/regression_tests/mgxs_library_mesh/test.py index b72fdf2f1e..3660d3eb74 100644 --- a/tests/regression_tests/mgxs_library_mesh/test.py +++ b/tests/regression_tests/mgxs_library_mesh/test.py @@ -2,42 +2,67 @@ import hashlib import openmc import openmc.mgxs +import pytest from tests.testing_harness import PyAPITestHarness +@pytest.fixture +def model(): + model = openmc.model.Model() + + fuel = openmc.Material() + fuel.set_density('g/cm3', 10.0) + fuel.add_nuclide('U235', 1.0) + zr = openmc.Material() + zr.set_density('g/cm3', 1.0) + zr.add_nuclide('Zr90', 1.0) + model.materials.extend([fuel, zr]) + + box1 = openmc.model.rectangular_prism(10.0, 10.0) + box2 = openmc.model.rectangular_prism(20.0, 20.0, boundary_type='reflective') + top = openmc.ZPlane(z0=10.0, boundary_type='vacuum') + bottom = openmc.ZPlane(z0=-10.0, boundary_type='vacuum') + cell1 = openmc.Cell(fill=fuel, region=box1 & +bottom & -top) + cell2 = openmc.Cell(fill=zr, region=~box1 & box2 & +bottom & -top) + model.geometry = openmc.Geometry([cell1, cell2]) + + model.settings.batches = 5 + model.settings.inactive = 0 + model.settings.particles = 1000 + + # Initialize a one-group structure + energy_groups = openmc.mgxs.EnergyGroups([0, 20.e6]) + + # Initialize MGXS Library for a few cross section types + # for one material-filled cell in the geometry + model.mgxs_lib = openmc.mgxs.Library(model.geometry) + model.mgxs_lib.by_nuclide = False + + # Test all MGXS types + model.mgxs_lib.mgxs_types = openmc.mgxs.MGXS_TYPES + openmc.mgxs.MDGXS_TYPES + model.mgxs_lib.energy_groups = energy_groups + model.mgxs_lib.num_delayed_groups = 6 + model.mgxs_lib.correction = None # Avoid warning about P0 correction + model.mgxs_lib.legendre_order = 3 + model.mgxs_lib.domain_type = 'mesh' + + # Instantiate a tally mesh + mesh = openmc.RegularMesh(mesh_id=1) + mesh.dimension = [2, 2] + mesh.lower_left = [-100., -100.] + mesh.width = [100., 100.] + + model.mgxs_lib.domains = [mesh] + model.mgxs_lib.build_library() + + # Add tallies + model.mgxs_lib.add_to_tallies_file(model.tallies, merge=False) + + return model + + class MGXSTestHarness(PyAPITestHarness): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - # Initialize a one-group structure - energy_groups = openmc.mgxs.EnergyGroups(group_edges=[0, 20.e6]) - - # Initialize MGXS Library for a few cross section types - # for one material-filled cell in the geometry - self.mgxs_lib = openmc.mgxs.Library(self._model.geometry) - self.mgxs_lib.by_nuclide = False - - # Test all MGXS types - self.mgxs_lib.mgxs_types = openmc.mgxs.MGXS_TYPES + \ - openmc.mgxs.MDGXS_TYPES - self.mgxs_lib.energy_groups = energy_groups - self.mgxs_lib.num_delayed_groups = 6 - self.mgxs_lib.legendre_order = 3 - self.mgxs_lib.domain_type = 'mesh' - - # Instantiate a tally mesh - mesh = openmc.RegularMesh(mesh_id=1) - mesh.dimension = [2, 2] - mesh.lower_left = [-100., -100.] - mesh.width = [100., 100.] - - self.mgxs_lib.domains = [mesh] - self.mgxs_lib.build_library() - - # Add tallies - self.mgxs_lib.add_to_tallies_file(self._model.tallies, merge=False) - def _get_results(self, hash_output=False): """Digest info in the statepoint and return as a string.""" @@ -45,13 +70,14 @@ class MGXSTestHarness(PyAPITestHarness): sp = openmc.StatePoint(self._sp_name) # Load the MGXS library from the statepoint - self.mgxs_lib.load_from_statepoint(sp) + mgxs_lib = self._model.mgxs_lib + mgxs_lib.load_from_statepoint(sp) # Build a string from Pandas Dataframe for each 1-group MGXS outstr = '' - for domain in self.mgxs_lib.domains: - for mgxs_type in self.mgxs_lib.mgxs_types: - mgxs = self.mgxs_lib.get_mgxs(domain, mgxs_type) + for domain in mgxs_lib.domains: + for mgxs_type in mgxs_lib.mgxs_types: + mgxs = mgxs_lib.get_mgxs(domain, mgxs_type) df = mgxs.get_pandas_dataframe() outstr += df.to_string() + '\n' @@ -64,6 +90,6 @@ class MGXSTestHarness(PyAPITestHarness): return outstr -def test_mgxs_library_mesh(): - harness = MGXSTestHarness('statepoint.10.h5') +def test_mgxs_library_mesh(model): + harness = MGXSTestHarness('statepoint.5.h5', model) harness.main() diff --git a/tests/regression_tests/score_current/geometry.xml b/tests/regression_tests/score_current/geometry.xml deleted file mode 100644 index f6f067aadd..0000000000 --- a/tests/regression_tests/score_current/geometry.xml +++ /dev/null @@ -1,181 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 17 17 - -10.71 -10.71 - 1.26 1.26 - - 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 - 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 - 1 1 1 1 1 2 1 1 2 1 1 2 1 1 1 1 1 - 1 1 1 2 1 1 1 1 1 1 1 1 1 2 1 1 1 - 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 - 1 1 2 1 1 2 1 1 2 1 1 2 1 1 2 1 1 - 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 - 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 - 1 1 2 1 1 2 1 1 2 1 1 2 1 1 2 1 1 - 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 - 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 - 1 1 2 1 1 2 1 1 2 1 1 2 1 1 2 1 1 - 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 - 1 1 1 2 1 1 1 1 1 1 1 1 1 2 1 1 1 - 1 1 1 1 1 2 1 1 2 1 1 2 1 1 1 1 1 - 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 - 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 - - - - - - 17 17 - -10.71 -10.71 - 1.26 1.26 - - 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 - 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 - 3 3 3 3 3 4 3 3 4 3 3 4 3 3 3 3 3 - 3 3 3 4 3 3 3 3 3 3 3 3 3 4 3 3 3 - 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 - 3 3 4 3 3 4 3 3 4 3 3 4 3 3 4 3 3 - 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 - 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 - 3 3 4 3 3 4 3 3 4 3 3 4 3 3 4 3 3 - 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 - 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 - 3 3 4 3 3 4 3 3 4 3 3 4 3 3 4 3 3 - 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 - 3 3 3 4 3 3 3 3 3 3 3 3 3 4 3 3 3 - 3 3 3 3 3 4 3 3 4 3 3 4 3 3 3 3 3 - 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 - 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 - - - - - - 21 21 - -224.91 -224.91 - 21.42 21.42 - - 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 - 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 - 5 5 5 5 5 5 5 6 6 6 6 6 6 6 5 5 5 5 5 5 5 - 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 - 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 - 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 - 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 - 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 - 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 - 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 - 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 - 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 - 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 - 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 - 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 - 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 - 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 - 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 - 5 5 5 5 5 5 5 6 6 6 6 6 6 6 5 5 5 5 5 5 5 - 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 - 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 - - - - - - 21 21 - -224.91 -224.91 - 21.42 21.42 - - 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 - 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 - 7 7 7 7 7 7 7 8 8 8 8 8 8 8 7 7 7 7 7 7 7 - 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 - 7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 - 7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 - 7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 - 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 - 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 - 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 - 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 - 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 - 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 - 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 - 7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 - 7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 - 7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 - 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 - 7 7 7 7 7 7 7 8 8 8 8 8 8 8 7 7 7 7 7 7 7 - 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 - 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 - - - - diff --git a/tests/regression_tests/score_current/inputs_true.dat b/tests/regression_tests/score_current/inputs_true.dat new file mode 100644 index 0000000000..2b81f508f1 --- /dev/null +++ b/tests/regression_tests/score_current/inputs_true.dat @@ -0,0 +1,55 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + eigenvalue + 1000 + 5 + 0 + + + + + 3 3 3 + -10.0 -10.0 -10.0 + 10.0 10.0 10.0 + + + 1 + + + 0.0 0.253 20000000.0 + + + 1 + current + + + 1 2 + current + + diff --git a/tests/regression_tests/score_current/materials.xml b/tests/regression_tests/score_current/materials.xml deleted file mode 100644 index 8021f5f99e..0000000000 --- a/tests/regression_tests/score_current/materials.xml +++ /dev/null @@ -1,270 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tests/regression_tests/score_current/results_true.dat b/tests/regression_tests/score_current/results_true.dat index dedc473de9..ddd73d6cca 100644 --- a/tests/regression_tests/score_current/results_true.dat +++ b/tests/regression_tests/score_current/results_true.dat @@ -1 +1,1948 @@ -138b312cdaa822c9b62f757b3259522004b679c4ed289a92798b29a6442c26d12c53256635be273f13e3703816ff50a1b9f52d79770eade01e482384ac0d389f \ No newline at end of file +k-combined: +7.656044E-01 5.168921E-02 +tally 1: +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +1.080000E-01 +2.386000E-03 +1.410000E-01 +4.007000E-03 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +1.160000E-01 +2.830000E-03 +1.530000E-01 +4.895000E-03 +1.450000E-01 +4.395000E-03 +0.000000E+00 +0.000000E+00 +6.500000E-02 +9.150000E-04 +1.340000E-01 +3.660000E-03 +1.410000E-01 +4.007000E-03 +1.080000E-01 +2.386000E-03 +1.640000E-01 +5.438000E-03 +1.230000E-01 +3.203000E-03 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +1.640000E-01 +5.776000E-03 +2.610000E-01 +1.373100E-02 +1.910000E-01 +7.339000E-03 +0.000000E+00 +0.000000E+00 +1.000000E-01 +2.116000E-03 +1.990000E-01 +8.309000E-03 +1.230000E-01 +3.203000E-03 +1.640000E-01 +5.438000E-03 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +1.100000E-01 +2.476000E-03 +1.540000E-01 +5.106000E-03 +1.660000E-01 +5.664000E-03 +0.000000E+00 +0.000000E+00 +7.000000E-02 +1.080000E-03 +1.370000E-01 +3.867000E-03 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +1.620000E-01 +5.422000E-03 +2.650000E-01 +1.456300E-02 +1.530000E-01 +4.895000E-03 +1.160000E-01 +2.830000E-03 +1.600000E-01 +5.184000E-03 +1.060000E-01 +2.286000E-03 +1.870000E-01 +7.195000E-03 +0.000000E+00 +0.000000E+00 +1.090000E-01 +2.653000E-03 +2.320000E-01 +1.087200E-02 +2.650000E-01 +1.456300E-02 +1.620000E-01 +5.422000E-03 +2.500000E-01 +1.285000E-02 +1.530000E-01 +4.823000E-03 +2.610000E-01 +1.373100E-02 +1.640000E-01 +5.776000E-03 +2.530000E-01 +1.347100E-02 +1.700000E-01 +5.906000E-03 +2.300000E-01 +1.089400E-02 +0.000000E+00 +0.000000E+00 +2.380000E-01 +1.226000E-02 +5.120000E-01 +5.796600E-02 +1.530000E-01 +4.823000E-03 +2.500000E-01 +1.285000E-02 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +1.540000E-01 +5.106000E-03 +1.100000E-01 +2.476000E-03 +1.600000E-01 +5.166000E-03 +1.350000E-01 +3.771000E-03 +1.830000E-01 +6.757000E-03 +0.000000E+00 +0.000000E+00 +9.100000E-02 +1.903000E-03 +1.940000E-01 +7.626000E-03 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +1.360000E-01 +3.862000E-03 +1.560000E-01 +4.970000E-03 +1.060000E-01 +2.286000E-03 +1.600000E-01 +5.184000E-03 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +1.560000E-01 +4.922000E-03 +0.000000E+00 +0.000000E+00 +5.900000E-02 +7.270000E-04 +1.310000E-01 +3.483000E-03 +1.560000E-01 +4.970000E-03 +1.360000E-01 +3.862000E-03 +1.940000E-01 +8.162000E-03 +1.390000E-01 +3.905000E-03 +1.700000E-01 +5.906000E-03 +2.530000E-01 +1.347100E-02 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +2.020000E-01 +8.326000E-03 +0.000000E+00 +0.000000E+00 +1.100000E-01 +2.510000E-03 +2.230000E-01 +1.009700E-02 +1.390000E-01 +3.905000E-03 +1.940000E-01 +8.162000E-03 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +1.350000E-01 +3.771000E-03 +1.600000E-01 +5.166000E-03 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +1.630000E-01 +5.451000E-03 +0.000000E+00 +0.000000E+00 +8.100000E-02 +1.385000E-03 +1.530000E-01 +4.723000E-03 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +1.380000E-01 +3.886000E-03 +2.270000E-01 +1.064700E-02 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +1.670000E-01 +5.625000E-03 +2.150000E-01 +9.531000E-03 +1.340000E-01 +3.660000E-03 +6.500000E-02 +9.150000E-04 +1.510000E-01 +4.573000E-03 +6.900000E-02 +9.890000E-04 +2.270000E-01 +1.064700E-02 +1.380000E-01 +3.886000E-03 +2.040000E-01 +8.552000E-03 +1.310000E-01 +3.655000E-03 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +2.390000E-01 +1.148900E-02 +5.090000E-01 +6.440300E-02 +1.990000E-01 +8.309000E-03 +1.000000E-01 +2.116000E-03 +2.040000E-01 +8.722000E-03 +8.700000E-02 +1.655000E-03 +1.310000E-01 +3.655000E-03 +2.040000E-01 +8.552000E-03 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +1.520000E-01 +4.840000E-03 +2.210000E-01 +1.015500E-02 +1.370000E-01 +3.867000E-03 +7.000000E-02 +1.080000E-03 +1.510000E-01 +4.589000E-03 +5.600000E-02 +7.760000E-04 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +2.440000E-01 +1.198000E-02 +5.330000E-01 +6.307900E-02 +2.150000E-01 +9.531000E-03 +1.670000E-01 +5.625000E-03 +2.090000E-01 +8.891000E-03 +1.580000E-01 +5.042000E-03 +2.320000E-01 +1.087200E-02 +1.090000E-01 +2.653000E-03 +2.150000E-01 +9.421000E-03 +7.800000E-02 +1.474000E-03 +5.330000E-01 +6.307900E-02 +2.440000E-01 +1.198000E-02 +5.000000E-01 +5.426200E-02 +2.160000E-01 +9.816000E-03 +5.090000E-01 +6.440300E-02 +2.390000E-01 +1.148900E-02 +4.860000E-01 +5.139400E-02 +2.330000E-01 +1.110300E-02 +5.120000E-01 +5.796600E-02 +2.380000E-01 +1.226000E-02 +5.280000E-01 +6.237800E-02 +2.150000E-01 +1.004500E-02 +2.160000E-01 +9.816000E-03 +5.000000E-01 +5.426200E-02 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +2.210000E-01 +1.015500E-02 +1.520000E-01 +4.840000E-03 +2.300000E-01 +1.067400E-02 +1.610000E-01 +5.391000E-03 +1.940000E-01 +7.626000E-03 +9.100000E-02 +1.903000E-03 +2.160000E-01 +9.358000E-03 +8.000000E-02 +1.440000E-03 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +1.420000E-01 +4.126000E-03 +2.210000E-01 +9.953000E-03 +1.580000E-01 +5.042000E-03 +2.090000E-01 +8.891000E-03 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +1.310000E-01 +3.483000E-03 +5.900000E-02 +7.270000E-04 +1.310000E-01 +3.567000E-03 +6.000000E-02 +7.940000E-04 +2.210000E-01 +9.953000E-03 +1.420000E-01 +4.126000E-03 +2.360000E-01 +1.141600E-02 +1.770000E-01 +6.341000E-03 +2.330000E-01 +1.110300E-02 +4.860000E-01 +5.139400E-02 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +2.230000E-01 +1.009700E-02 +1.100000E-01 +2.510000E-03 +2.040000E-01 +8.736000E-03 +1.100000E-01 +2.790000E-03 +1.770000E-01 +6.341000E-03 +2.360000E-01 +1.141600E-02 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +1.610000E-01 +5.391000E-03 +2.300000E-01 +1.067400E-02 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +1.530000E-01 +4.723000E-03 +8.100000E-02 +1.385000E-03 +1.460000E-01 +4.382000E-03 +6.100000E-02 +8.190000E-04 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +1.140000E-01 +2.660000E-03 +1.620000E-01 +5.420000E-03 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +1.300000E-01 +3.474000E-03 +1.440000E-01 +4.322000E-03 +6.900000E-02 +9.890000E-04 +1.510000E-01 +4.573000E-03 +1.560000E-01 +4.912000E-03 +0.000000E+00 +0.000000E+00 +1.620000E-01 +5.420000E-03 +1.140000E-01 +2.660000E-03 +1.580000E-01 +5.102000E-03 +1.550000E-01 +4.831000E-03 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +1.340000E-01 +3.708000E-03 +2.300000E-01 +1.092600E-02 +8.700000E-02 +1.655000E-03 +2.040000E-01 +8.722000E-03 +2.060000E-01 +8.520000E-03 +0.000000E+00 +0.000000E+00 +1.550000E-01 +4.831000E-03 +1.580000E-01 +5.102000E-03 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +1.230000E-01 +3.223000E-03 +1.680000E-01 +5.674000E-03 +5.600000E-02 +7.760000E-04 +1.510000E-01 +4.589000E-03 +1.660000E-01 +6.068000E-03 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +1.450000E-01 +4.371000E-03 +2.450000E-01 +1.248900E-02 +1.440000E-01 +4.322000E-03 +1.300000E-01 +3.474000E-03 +1.680000E-01 +5.918000E-03 +1.120000E-01 +2.590000E-03 +7.800000E-02 +1.474000E-03 +2.150000E-01 +9.421000E-03 +2.110000E-01 +9.163000E-03 +0.000000E+00 +0.000000E+00 +2.450000E-01 +1.248900E-02 +1.450000E-01 +4.371000E-03 +2.490000E-01 +1.251100E-02 +1.460000E-01 +4.574000E-03 +2.300000E-01 +1.092600E-02 +1.340000E-01 +3.708000E-03 +2.680000E-01 +1.517800E-02 +1.560000E-01 +5.476000E-03 +2.150000E-01 +1.004500E-02 +5.280000E-01 +6.237800E-02 +2.380000E-01 +1.203000E-02 +0.000000E+00 +0.000000E+00 +1.460000E-01 +4.574000E-03 +2.490000E-01 +1.251100E-02 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +1.680000E-01 +5.674000E-03 +1.230000E-01 +3.223000E-03 +1.460000E-01 +4.356000E-03 +1.260000E-01 +3.252000E-03 +8.000000E-02 +1.440000E-03 +2.160000E-01 +9.358000E-03 +2.100000E-01 +9.014000E-03 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +1.190000E-01 +2.863000E-03 +1.520000E-01 +4.782000E-03 +1.120000E-01 +2.590000E-03 +1.680000E-01 +5.918000E-03 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +6.000000E-02 +7.940000E-04 +1.310000E-01 +3.567000E-03 +1.730000E-01 +6.389000E-03 +0.000000E+00 +0.000000E+00 +1.520000E-01 +4.782000E-03 +1.190000E-01 +2.863000E-03 +1.640000E-01 +5.422000E-03 +1.290000E-01 +3.499000E-03 +1.560000E-01 +5.476000E-03 +2.680000E-01 +1.517800E-02 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +1.100000E-01 +2.790000E-03 +2.040000E-01 +8.736000E-03 +1.930000E-01 +7.503000E-03 +0.000000E+00 +0.000000E+00 +1.290000E-01 +3.499000E-03 +1.640000E-01 +5.422000E-03 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +1.260000E-01 +3.252000E-03 +1.460000E-01 +4.356000E-03 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +6.100000E-02 +8.190000E-04 +1.460000E-01 +4.382000E-03 +1.560000E-01 +4.892000E-03 +0.000000E+00 +0.000000E+00 +tally 2: +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +1.080000E-01 +2.386000E-03 +0.000000E+00 +0.000000E+00 +1.410000E-01 +4.007000E-03 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +1.160000E-01 +2.830000E-03 +0.000000E+00 +0.000000E+00 +1.530000E-01 +4.895000E-03 +0.000000E+00 +0.000000E+00 +1.450000E-01 +4.395000E-03 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +6.500000E-02 +9.150000E-04 +0.000000E+00 +0.000000E+00 +1.340000E-01 +3.660000E-03 +0.000000E+00 +0.000000E+00 +1.410000E-01 +4.007000E-03 +0.000000E+00 +0.000000E+00 +1.080000E-01 +2.386000E-03 +0.000000E+00 +0.000000E+00 +1.640000E-01 +5.438000E-03 +0.000000E+00 +0.000000E+00 +1.230000E-01 +3.203000E-03 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +1.640000E-01 +5.776000E-03 +0.000000E+00 +0.000000E+00 +2.610000E-01 +1.373100E-02 +0.000000E+00 +0.000000E+00 +1.910000E-01 +7.339000E-03 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +1.000000E-01 +2.116000E-03 +0.000000E+00 +0.000000E+00 +1.990000E-01 +8.309000E-03 +0.000000E+00 +0.000000E+00 +1.230000E-01 +3.203000E-03 +0.000000E+00 +0.000000E+00 +1.640000E-01 +5.438000E-03 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +1.100000E-01 +2.476000E-03 +0.000000E+00 +0.000000E+00 +1.540000E-01 +5.106000E-03 +0.000000E+00 +0.000000E+00 +1.660000E-01 +5.664000E-03 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +7.000000E-02 +1.080000E-03 +0.000000E+00 +0.000000E+00 +1.370000E-01 +3.867000E-03 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +1.620000E-01 +5.422000E-03 +0.000000E+00 +0.000000E+00 +2.650000E-01 +1.456300E-02 +0.000000E+00 +0.000000E+00 +1.530000E-01 +4.895000E-03 +0.000000E+00 +0.000000E+00 +1.160000E-01 +2.830000E-03 +0.000000E+00 +0.000000E+00 +1.600000E-01 +5.184000E-03 +0.000000E+00 +0.000000E+00 +1.060000E-01 +2.286000E-03 +0.000000E+00 +0.000000E+00 +1.870000E-01 +7.195000E-03 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +1.090000E-01 +2.653000E-03 +0.000000E+00 +0.000000E+00 +2.320000E-01 +1.087200E-02 +0.000000E+00 +0.000000E+00 +2.650000E-01 +1.456300E-02 +0.000000E+00 +0.000000E+00 +1.620000E-01 +5.422000E-03 +0.000000E+00 +0.000000E+00 +2.500000E-01 +1.285000E-02 +0.000000E+00 +0.000000E+00 +1.530000E-01 +4.823000E-03 +0.000000E+00 +0.000000E+00 +2.610000E-01 +1.373100E-02 +0.000000E+00 +0.000000E+00 +1.640000E-01 +5.776000E-03 +0.000000E+00 +0.000000E+00 +2.530000E-01 +1.347100E-02 +0.000000E+00 +0.000000E+00 +1.700000E-01 +5.906000E-03 +0.000000E+00 +0.000000E+00 +2.300000E-01 +1.089400E-02 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +2.380000E-01 +1.226000E-02 +0.000000E+00 +0.000000E+00 +5.120000E-01 +5.796600E-02 +0.000000E+00 +0.000000E+00 +1.530000E-01 +4.823000E-03 +0.000000E+00 +0.000000E+00 +2.500000E-01 +1.285000E-02 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +1.540000E-01 +5.106000E-03 +0.000000E+00 +0.000000E+00 +1.100000E-01 +2.476000E-03 +0.000000E+00 +0.000000E+00 +1.600000E-01 +5.166000E-03 +0.000000E+00 +0.000000E+00 +1.350000E-01 +3.771000E-03 +0.000000E+00 +0.000000E+00 +1.830000E-01 +6.757000E-03 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +9.100000E-02 +1.903000E-03 +0.000000E+00 +0.000000E+00 +1.940000E-01 +7.626000E-03 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +1.360000E-01 +3.862000E-03 +0.000000E+00 +0.000000E+00 +1.560000E-01 +4.970000E-03 +0.000000E+00 +0.000000E+00 +1.060000E-01 +2.286000E-03 +0.000000E+00 +0.000000E+00 +1.600000E-01 +5.184000E-03 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +1.560000E-01 +4.922000E-03 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +5.900000E-02 +7.270000E-04 +0.000000E+00 +0.000000E+00 +1.310000E-01 +3.483000E-03 +0.000000E+00 +0.000000E+00 +1.560000E-01 +4.970000E-03 +0.000000E+00 +0.000000E+00 +1.360000E-01 +3.862000E-03 +0.000000E+00 +0.000000E+00 +1.940000E-01 +8.162000E-03 +0.000000E+00 +0.000000E+00 +1.390000E-01 +3.905000E-03 +0.000000E+00 +0.000000E+00 +1.700000E-01 +5.906000E-03 +0.000000E+00 +0.000000E+00 +2.530000E-01 +1.347100E-02 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +2.020000E-01 +8.326000E-03 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +1.100000E-01 +2.510000E-03 +0.000000E+00 +0.000000E+00 +2.230000E-01 +1.009700E-02 +0.000000E+00 +0.000000E+00 +1.390000E-01 +3.905000E-03 +0.000000E+00 +0.000000E+00 +1.940000E-01 +8.162000E-03 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +1.350000E-01 +3.771000E-03 +0.000000E+00 +0.000000E+00 +1.600000E-01 +5.166000E-03 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +1.630000E-01 +5.451000E-03 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +8.100000E-02 +1.385000E-03 +0.000000E+00 +0.000000E+00 +1.530000E-01 +4.723000E-03 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +1.380000E-01 +3.886000E-03 +0.000000E+00 +0.000000E+00 +2.270000E-01 +1.064700E-02 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +1.670000E-01 +5.625000E-03 +0.000000E+00 +0.000000E+00 +2.150000E-01 +9.531000E-03 +0.000000E+00 +0.000000E+00 +1.340000E-01 +3.660000E-03 +0.000000E+00 +0.000000E+00 +6.500000E-02 +9.150000E-04 +0.000000E+00 +0.000000E+00 +1.510000E-01 +4.573000E-03 +0.000000E+00 +0.000000E+00 +6.900000E-02 +9.890000E-04 +0.000000E+00 +0.000000E+00 +2.270000E-01 +1.064700E-02 +0.000000E+00 +0.000000E+00 +1.380000E-01 +3.886000E-03 +0.000000E+00 +0.000000E+00 +2.040000E-01 +8.552000E-03 +0.000000E+00 +0.000000E+00 +1.310000E-01 +3.655000E-03 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +2.390000E-01 +1.148900E-02 +0.000000E+00 +0.000000E+00 +5.090000E-01 +6.440300E-02 +0.000000E+00 +0.000000E+00 +1.990000E-01 +8.309000E-03 +0.000000E+00 +0.000000E+00 +1.000000E-01 +2.116000E-03 +0.000000E+00 +0.000000E+00 +2.040000E-01 +8.722000E-03 +0.000000E+00 +0.000000E+00 +8.700000E-02 +1.655000E-03 +0.000000E+00 +0.000000E+00 +1.310000E-01 +3.655000E-03 +0.000000E+00 +0.000000E+00 +2.040000E-01 +8.552000E-03 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +1.520000E-01 +4.840000E-03 +0.000000E+00 +0.000000E+00 +2.210000E-01 +1.015500E-02 +0.000000E+00 +0.000000E+00 +1.370000E-01 +3.867000E-03 +0.000000E+00 +0.000000E+00 +7.000000E-02 +1.080000E-03 +0.000000E+00 +0.000000E+00 +1.510000E-01 +4.589000E-03 +0.000000E+00 +0.000000E+00 +5.600000E-02 +7.760000E-04 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +2.440000E-01 +1.198000E-02 +0.000000E+00 +0.000000E+00 +5.330000E-01 +6.307900E-02 +0.000000E+00 +0.000000E+00 +2.150000E-01 +9.531000E-03 +0.000000E+00 +0.000000E+00 +1.670000E-01 +5.625000E-03 +0.000000E+00 +0.000000E+00 +2.090000E-01 +8.891000E-03 +0.000000E+00 +0.000000E+00 +1.580000E-01 +5.042000E-03 +0.000000E+00 +0.000000E+00 +2.320000E-01 +1.087200E-02 +0.000000E+00 +0.000000E+00 +1.090000E-01 +2.653000E-03 +0.000000E+00 +0.000000E+00 +2.150000E-01 +9.421000E-03 +0.000000E+00 +0.000000E+00 +7.800000E-02 +1.474000E-03 +0.000000E+00 +0.000000E+00 +5.330000E-01 +6.307900E-02 +0.000000E+00 +0.000000E+00 +2.440000E-01 +1.198000E-02 +0.000000E+00 +0.000000E+00 +5.000000E-01 +5.426200E-02 +0.000000E+00 +0.000000E+00 +2.160000E-01 +9.816000E-03 +0.000000E+00 +0.000000E+00 +5.090000E-01 +6.440300E-02 +0.000000E+00 +0.000000E+00 +2.390000E-01 +1.148900E-02 +0.000000E+00 +0.000000E+00 +4.860000E-01 +5.139400E-02 +0.000000E+00 +0.000000E+00 +2.330000E-01 +1.110300E-02 +0.000000E+00 +0.000000E+00 +5.120000E-01 +5.796600E-02 +0.000000E+00 +0.000000E+00 +2.380000E-01 +1.226000E-02 +0.000000E+00 +0.000000E+00 +5.280000E-01 +6.237800E-02 +0.000000E+00 +0.000000E+00 +2.150000E-01 +1.004500E-02 +0.000000E+00 +0.000000E+00 +2.160000E-01 +9.816000E-03 +0.000000E+00 +0.000000E+00 +5.000000E-01 +5.426200E-02 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +2.210000E-01 +1.015500E-02 +0.000000E+00 +0.000000E+00 +1.520000E-01 +4.840000E-03 +0.000000E+00 +0.000000E+00 +2.300000E-01 +1.067400E-02 +0.000000E+00 +0.000000E+00 +1.610000E-01 +5.391000E-03 +0.000000E+00 +0.000000E+00 +1.940000E-01 +7.626000E-03 +0.000000E+00 +0.000000E+00 +9.100000E-02 +1.903000E-03 +0.000000E+00 +0.000000E+00 +2.160000E-01 +9.358000E-03 +0.000000E+00 +0.000000E+00 +8.000000E-02 +1.440000E-03 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +1.420000E-01 +4.126000E-03 +0.000000E+00 +0.000000E+00 +2.210000E-01 +9.953000E-03 +0.000000E+00 +0.000000E+00 +1.580000E-01 +5.042000E-03 +0.000000E+00 +0.000000E+00 +2.090000E-01 +8.891000E-03 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +1.310000E-01 +3.483000E-03 +0.000000E+00 +0.000000E+00 +5.900000E-02 +7.270000E-04 +0.000000E+00 +0.000000E+00 +1.310000E-01 +3.567000E-03 +0.000000E+00 +0.000000E+00 +6.000000E-02 +7.940000E-04 +0.000000E+00 +0.000000E+00 +2.210000E-01 +9.953000E-03 +0.000000E+00 +0.000000E+00 +1.420000E-01 +4.126000E-03 +0.000000E+00 +0.000000E+00 +2.360000E-01 +1.141600E-02 +0.000000E+00 +0.000000E+00 +1.770000E-01 +6.341000E-03 +0.000000E+00 +0.000000E+00 +2.330000E-01 +1.110300E-02 +0.000000E+00 +0.000000E+00 +4.860000E-01 +5.139400E-02 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +2.230000E-01 +1.009700E-02 +0.000000E+00 +0.000000E+00 +1.100000E-01 +2.510000E-03 +0.000000E+00 +0.000000E+00 +2.040000E-01 +8.736000E-03 +0.000000E+00 +0.000000E+00 +1.100000E-01 +2.790000E-03 +0.000000E+00 +0.000000E+00 +1.770000E-01 +6.341000E-03 +0.000000E+00 +0.000000E+00 +2.360000E-01 +1.141600E-02 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +1.610000E-01 +5.391000E-03 +0.000000E+00 +0.000000E+00 +2.300000E-01 +1.067400E-02 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +1.530000E-01 +4.723000E-03 +0.000000E+00 +0.000000E+00 +8.100000E-02 +1.385000E-03 +0.000000E+00 +0.000000E+00 +1.460000E-01 +4.382000E-03 +0.000000E+00 +0.000000E+00 +6.100000E-02 +8.190000E-04 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +1.140000E-01 +2.660000E-03 +0.000000E+00 +0.000000E+00 +1.620000E-01 +5.420000E-03 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +1.300000E-01 +3.474000E-03 +0.000000E+00 +0.000000E+00 +1.440000E-01 +4.322000E-03 +0.000000E+00 +0.000000E+00 +6.900000E-02 +9.890000E-04 +0.000000E+00 +0.000000E+00 +1.510000E-01 +4.573000E-03 +0.000000E+00 +0.000000E+00 +1.560000E-01 +4.912000E-03 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +1.620000E-01 +5.420000E-03 +0.000000E+00 +0.000000E+00 +1.140000E-01 +2.660000E-03 +0.000000E+00 +0.000000E+00 +1.580000E-01 +5.102000E-03 +0.000000E+00 +0.000000E+00 +1.550000E-01 +4.831000E-03 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +1.340000E-01 +3.708000E-03 +0.000000E+00 +0.000000E+00 +2.300000E-01 +1.092600E-02 +0.000000E+00 +0.000000E+00 +8.700000E-02 +1.655000E-03 +0.000000E+00 +0.000000E+00 +2.040000E-01 +8.722000E-03 +0.000000E+00 +0.000000E+00 +2.060000E-01 +8.520000E-03 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +1.550000E-01 +4.831000E-03 +0.000000E+00 +0.000000E+00 +1.580000E-01 +5.102000E-03 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +1.230000E-01 +3.223000E-03 +0.000000E+00 +0.000000E+00 +1.680000E-01 +5.674000E-03 +0.000000E+00 +0.000000E+00 +5.600000E-02 +7.760000E-04 +0.000000E+00 +0.000000E+00 +1.510000E-01 +4.589000E-03 +0.000000E+00 +0.000000E+00 +1.660000E-01 +6.068000E-03 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +1.450000E-01 +4.371000E-03 +0.000000E+00 +0.000000E+00 +2.450000E-01 +1.248900E-02 +0.000000E+00 +0.000000E+00 +1.440000E-01 +4.322000E-03 +0.000000E+00 +0.000000E+00 +1.300000E-01 +3.474000E-03 +0.000000E+00 +0.000000E+00 +1.680000E-01 +5.918000E-03 +0.000000E+00 +0.000000E+00 +1.120000E-01 +2.590000E-03 +0.000000E+00 +0.000000E+00 +7.800000E-02 +1.474000E-03 +0.000000E+00 +0.000000E+00 +2.150000E-01 +9.421000E-03 +0.000000E+00 +0.000000E+00 +2.110000E-01 +9.163000E-03 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +2.450000E-01 +1.248900E-02 +0.000000E+00 +0.000000E+00 +1.450000E-01 +4.371000E-03 +0.000000E+00 +0.000000E+00 +2.490000E-01 +1.251100E-02 +0.000000E+00 +0.000000E+00 +1.460000E-01 +4.574000E-03 +0.000000E+00 +0.000000E+00 +2.300000E-01 +1.092600E-02 +0.000000E+00 +0.000000E+00 +1.340000E-01 +3.708000E-03 +0.000000E+00 +0.000000E+00 +2.680000E-01 +1.517800E-02 +0.000000E+00 +0.000000E+00 +1.560000E-01 +5.476000E-03 +0.000000E+00 +0.000000E+00 +2.150000E-01 +1.004500E-02 +0.000000E+00 +0.000000E+00 +5.280000E-01 +6.237800E-02 +0.000000E+00 +0.000000E+00 +2.380000E-01 +1.203000E-02 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +1.460000E-01 +4.574000E-03 +0.000000E+00 +0.000000E+00 +2.490000E-01 +1.251100E-02 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +1.680000E-01 +5.674000E-03 +0.000000E+00 +0.000000E+00 +1.230000E-01 +3.223000E-03 +0.000000E+00 +0.000000E+00 +1.460000E-01 +4.356000E-03 +0.000000E+00 +0.000000E+00 +1.260000E-01 +3.252000E-03 +0.000000E+00 +0.000000E+00 +8.000000E-02 +1.440000E-03 +0.000000E+00 +0.000000E+00 +2.160000E-01 +9.358000E-03 +0.000000E+00 +0.000000E+00 +2.100000E-01 +9.014000E-03 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +1.190000E-01 +2.863000E-03 +0.000000E+00 +0.000000E+00 +1.520000E-01 +4.782000E-03 +0.000000E+00 +0.000000E+00 +1.120000E-01 +2.590000E-03 +0.000000E+00 +0.000000E+00 +1.680000E-01 +5.918000E-03 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +6.000000E-02 +7.940000E-04 +0.000000E+00 +0.000000E+00 +1.310000E-01 +3.567000E-03 +0.000000E+00 +0.000000E+00 +1.730000E-01 +6.389000E-03 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +1.520000E-01 +4.782000E-03 +0.000000E+00 +0.000000E+00 +1.190000E-01 +2.863000E-03 +0.000000E+00 +0.000000E+00 +1.640000E-01 +5.422000E-03 +0.000000E+00 +0.000000E+00 +1.290000E-01 +3.499000E-03 +0.000000E+00 +0.000000E+00 +1.560000E-01 +5.476000E-03 +0.000000E+00 +0.000000E+00 +2.680000E-01 +1.517800E-02 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +1.100000E-01 +2.790000E-03 +0.000000E+00 +0.000000E+00 +2.040000E-01 +8.736000E-03 +0.000000E+00 +0.000000E+00 +1.930000E-01 +7.503000E-03 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +1.290000E-01 +3.499000E-03 +0.000000E+00 +0.000000E+00 +1.640000E-01 +5.422000E-03 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +1.260000E-01 +3.252000E-03 +0.000000E+00 +0.000000E+00 +1.460000E-01 +4.356000E-03 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 +6.100000E-02 +8.190000E-04 +0.000000E+00 +0.000000E+00 +1.460000E-01 +4.382000E-03 +0.000000E+00 +0.000000E+00 +1.560000E-01 +4.892000E-03 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 diff --git a/tests/regression_tests/score_current/settings.xml b/tests/regression_tests/score_current/settings.xml deleted file mode 100644 index 569c809821..0000000000 --- a/tests/regression_tests/score_current/settings.xml +++ /dev/null @@ -1,18 +0,0 @@ - - - - eigenvalue - 10 - 5 - 100 - - - - - -160 -160 -183 - 160 160 183 - - - - - diff --git a/tests/regression_tests/score_current/tallies.xml b/tests/regression_tests/score_current/tallies.xml deleted file mode 100644 index a381e4c2e2..0000000000 --- a/tests/regression_tests/score_current/tallies.xml +++ /dev/null @@ -1,31 +0,0 @@ - - - - - regular - -182.07 -182.07 -183.00 - 182.07 182.07 183.00 - 17 17 17 - - - - meshsurface - 1 - - - - energy - 0. 0.253 20.0e6 - - - - 1 - current - - - - 1 2 - current - - - diff --git a/tests/regression_tests/score_current/test.py b/tests/regression_tests/score_current/test.py index 21c5d08812..1309584d2c 100644 --- a/tests/regression_tests/score_current/test.py +++ b/tests/regression_tests/score_current/test.py @@ -1,6 +1,53 @@ -from tests.testing_harness import HashedTestHarness +import openmc +import pytest + +from tests.testing_harness import PyAPITestHarness -def test_score_current(): - harness = HashedTestHarness('statepoint.10.h5') +@pytest.fixture +def model(): + model = openmc.model.Model() + + fuel = openmc.Material() + fuel.set_density('g/cm3', 10.0) + fuel.add_nuclide('U235', 1.0) + zr = openmc.Material() + zr.set_density('g/cm3', 1.0) + zr.add_nuclide('Zr90', 1.0) + model.materials.extend([fuel, zr]) + + box1 = openmc.model.rectangular_prism(10.0, 10.0) + box2 = openmc.model.rectangular_prism(20.0, 20.0, boundary_type='reflective') + top = openmc.ZPlane(z0=10.0, boundary_type='vacuum') + bottom = openmc.ZPlane(z0=-10.0, boundary_type='vacuum') + cell1 = openmc.Cell(fill=fuel, region=box1 & +bottom & -top) + cell2 = openmc.Cell(fill=zr, region=~box1 & box2 & +bottom & -top) + model.geometry = openmc.Geometry([cell1, cell2]) + + model.settings.batches = 5 + model.settings.inactive = 0 + model.settings.particles = 1000 + + + mesh = openmc.Mesh() + mesh.lower_left = (-10.0, -10.0, -10.0) + mesh.upper_right = (10.0, 10.0, 10.0) + mesh.dimension = (3, 3, 3) + + mesh_surface_filter = openmc.MeshSurfaceFilter(mesh) + energy_filter = openmc.EnergyFilter([0.0, 0.253, 20.0e6]) + + tally1 = openmc.Tally() + tally1.filters = [mesh_surface_filter] + tally1.scores = ['current'] + tally2 = openmc.Tally() + tally2.filters = [mesh_surface_filter, energy_filter] + tally2.scores = ['current'] + model.tallies.extend([tally1, tally2]) + + return model + + +def test_score_current(model): + harness = PyAPITestHarness('statepoint.5.h5', model) harness.main() diff --git a/tests/regression_tests/tally_aggregation/inputs_true.dat b/tests/regression_tests/tally_aggregation/inputs_true.dat index 86806572a0..80356f7119 100644 --- a/tests/regression_tests/tally_aggregation/inputs_true.dat +++ b/tests/regression_tests/tally_aggregation/inputs_true.dat @@ -1,311 +1,45 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1.26 1.26 - 17 17 - -10.71 -10.71 + + + + + 1.2 1.2 + 1 + 2 2 + -1.2 -1.2 -1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 -1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 -1 1 1 1 1 2 1 1 2 1 1 2 1 1 1 1 1 -1 1 1 2 1 1 1 1 1 1 1 1 1 2 1 1 1 -1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 -1 1 2 1 1 2 1 1 2 1 1 2 1 1 2 1 1 -1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 -1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 -1 1 2 1 1 2 1 1 2 1 1 2 1 1 2 1 1 -1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 -1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 -1 1 2 1 1 2 1 1 2 1 1 2 1 1 2 1 1 -1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 -1 1 1 2 1 1 1 1 1 1 1 1 1 2 1 1 1 -1 1 1 1 1 2 1 1 2 1 1 2 1 1 1 1 1 -1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 -1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 +1 1 +1 1 - - 1.26 1.26 - 17 17 - -10.71 -10.71 - -3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 -3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 -3 3 3 3 3 4 3 3 4 3 3 4 3 3 3 3 3 -3 3 3 4 3 3 3 3 3 3 3 3 3 4 3 3 3 -3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 -3 3 4 3 3 4 3 3 4 3 3 4 3 3 4 3 3 -3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 -3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 -3 3 4 3 3 4 3 3 4 3 3 4 3 3 4 3 3 -3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 -3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 -3 3 4 3 3 4 3 3 4 3 3 4 3 3 4 3 3 -3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 -3 3 3 4 3 3 3 3 3 3 3 3 3 4 3 3 3 -3 3 3 3 3 4 3 3 4 3 3 4 3 3 3 3 3 -3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 -3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 - - - 21.42 21.42 - 21 21 - -224.91 -224.91 - -5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 -5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 -5 5 5 5 5 5 5 6 6 6 6 6 6 6 5 5 5 5 5 5 5 -5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 -5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 -5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 -5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 -5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 -5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 -5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 -5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 -5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 -5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 -5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 -5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 -5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 -5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 -5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 -5 5 5 5 5 5 5 6 6 6 6 6 6 6 5 5 5 5 5 5 5 -5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 -5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 - - - 21.42 21.42 - 21 21 - -224.91 -224.91 - -7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 -7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 -7 7 7 7 7 7 7 8 8 8 8 8 8 8 7 7 7 7 7 7 7 -7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 -7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 -7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 -7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 -7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 -7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 -7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 -7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 -7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 -7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 -7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 -7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 -7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 -7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 -7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 -7 7 7 7 7 7 7 8 8 8 8 8 8 8 7 7 7 7 7 7 7 -7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 -7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 - - - - - - - - - - - - - - - - - - + + + + + - - - - - - - + + + + + + - - - - - - - - - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - eigenvalue - 100 + 1000 10 5 - - - -160 -160 -183 160 160 183 - - @@ -313,7 +47,7 @@ 0.0 0.253 1000.0 1000000.0 20000000.0 - 60 + 1 1 2 diff --git a/tests/regression_tests/tally_aggregation/results_true.dat b/tests/regression_tests/tally_aggregation/results_true.dat index f1738ddfd9..e48f933701 100644 --- a/tests/regression_tests/tally_aggregation/results_true.dat +++ b/tests/regression_tests/tally_aggregation/results_true.dat @@ -1 +1,97 @@ -eac8fb56a8146b9e186ac9fb003753f4a0a18d4159d10e6fa51da4856baef66a10a0f5fb10c5727b51c6e44d81c147a8a7348ad9c9f7119a6ec33e0361082376 \ No newline at end of file +[[1.63731762e-05 5.08325999e-04] + [3.27547470e-01 1.77506218e-01] + [1.89164083e-02 7.22719366e-01]], [[1.64200315e-05 5.45231503e-04] + [3.24031537e-01 1.75537230e-01] + [1.87708717e-02 7.26768216e-01]], [[1.67487818e-05 5.03394672e-04] + [3.07780931e-01 1.67166322e-01] + [1.97449893e-02 7.06598738e-01]], [[1.63603106e-05 6.10423337e-04] + [3.32313666e-01 1.79982864e-01] + [1.90686364e-02 7.22662206e-01]][[4.65536263e-07 4.34762669e-05] + [9.14451087e-03 4.56766386e-03] + [7.87091351e-04 1.04610084e-02]], [[1.70217471e-07 3.74604055e-05] + [9.18903111e-03 4.56228307e-03] + [1.74179620e-04 9.17281345e-03]], [[2.49291594e-07 1.81470751e-05] + [8.34746285e-03 4.15219947e-03] + [3.57935129e-04 1.00438242e-02]], [[4.12850822e-07 3.98122204e-05] + [1.38191650e-02 6.82224745e-03] + [6.79096490e-04 9.85922061e-03]][[1.03849782e-06 8.14983256e-04] + [1.13114721e+00 5.60394847e-01] + [1.40550899e-06 5.11848857e-01]], [[1.99773314e-06 1.03955500e-03] + [1.42269343e-01 9.92372469e-02] + [3.30482612e-05 8.08235307e-01]], [[1.45630159e-05 2.22446467e-04] + [1.30357824e-02 2.93669452e-02] + [3.05346682e-04 1.10154874e+00]], [[4.83030533e-05 9.03907895e-05] + [5.22127114e-03 1.11935954e-02] + [7.61611053e-02 4.57115627e-01]][[1.87165262e-08 1.43906416e-05] + [2.05916324e-02 1.01675733e-02] + [2.50902573e-08 8.54377830e-03]], [[1.12098060e-07 7.06870145e-05] + [2.16279434e-03 1.42142705e-03] + [1.22851164e-05 1.41445724e-02]], [[2.08629342e-07 1.63602148e-06] + [1.08988258e-04 2.01647731e-04] + [7.14052741e-06 9.13467415e-03]], [[6.49497971e-07 1.17295883e-06] + [7.03193479e-05 1.45388818e-04] + [1.11307638e-03 5.92861613e-03]][[0.28708077 0.27231775]], [[0.283269 0.26846215]], [[0.26933717 0.25561967]], [[0.29146272 0.27665912]], [[0.03591854 0.2267151 ]], [[0.03618374 0.23663228]], [[0.03393119 0.22268261]], [[0.03627092 0.22248212]], [[0.00333501 0.28502144]], [[0.00339378 0.2823686 ]], [[0.0032646 0.27622413]], [[0.0033623 0.28752397]], [[0.02014593 0.11667962]], [[0.01997231 0.11538764]], [[0.02100971 0.11974205]], [[0.02030272 0.11659029]][[0.00908247 0.00614665]], [[0.00911888 0.00556208]], [[0.00831969 0.00596124]], [[0.01375328 0.00849244]], [[0.00106067 0.00719168]], [[0.00113262 0.00790992]], [[0.00067545 0.00732313]], [[0.00134675 0.00584628]], [[5.86657354e-05 4.85962742e-03]], [[3.78231391e-05 3.26052035e-03]], [[7.89746053e-05 5.03120906e-03]], [[2.86395600e-05 4.89110417e-03]], [[0.00078861 0.00414495]], [[0.00017413 0.00090648]], [[0.00035855 0.00190835]], [[0.00068051 0.0036777 ]][[2.07154706e-04] + [4.29264961e-01] + [1.29926403e-01]], [[2.04124023e-04] + [4.23635331e-01] + [1.27891698e-01]], [[1.94482255e-04] + [4.02735295e-01] + [1.22027057e-01]], [[2.10260769e-04] + [4.35906468e-01] + [1.32005105e-01]], [[0.00022426] + [0.06105851] + [0.20135086]], [[0.00026389] + [0.0612101 ] + [0.21134203]], [[0.00023084] + [0.05761498] + [0.19876799]], [[0.00032256] + [0.061623 ] + [0.19680747]], [[5.87610156e-05] + [1.06427230e-02] + [2.77654967e-01]], [[5.94899925e-05] + [1.06822132e-02] + [2.75020675e-01]], [[5.93176564e-05] + [1.03968293e-02] + [2.69032575e-01]], [[5.94408186e-05] + [1.06809621e-02] + [2.80145865e-01]], [[3.45195544e-05] + [4.08749343e-03] + [1.32703540e-01]], [[3.41476180e-05] + [4.04112643e-03] + [1.31284684e-01]], [[3.55075881e-05] + [4.20014745e-03] + [1.36516110e-01]], [[3.45190823e-05] + [4.08609920e-03] + [1.32772398e-01]][[6.53616088e-06] + [1.01396032e-02] + [4.17862796e-03]], [[6.14101518e-06] + [1.01647874e-02] + [3.28144648e-03]], [[6.21879601e-06] + [9.29002877e-03] + [4.29521996e-03]], [[9.37998012e-06] + [1.53282674e-02] + [5.13014723e-03]], [[4.29725320e-05] + [1.28419933e-03] + [7.15501363e-03]], [[3.69392134e-05] + [1.38678579e-03] + [7.86925329e-03]], [[1.70223774e-05] + [7.65771551e-04] + [7.31421975e-03]], [[3.86750728e-05] + [1.59354492e-03] + [5.78376198e-03]], [[4.00936601e-07] + [1.10265266e-04] + [4.85873047e-03]], [[1.02736776e-06] + [7.78918832e-05] + [3.25980910e-03]], [[8.71027724e-07] + [1.64820060e-04] + [5.02912867e-03]], [[8.63231446e-07] + [8.45518855e-05] + [4.89045708e-03]], [[9.39068799e-07] + [1.12928697e-04] + [4.21779516e-03]], [[1.94712273e-07] + [2.39886479e-05] + [9.22741200e-04]], [[4.30155635e-07] + [5.18679547e-05] + [1.94104652e-03]], [[8.32395987e-07] + [1.00319931e-04] + [3.73878581e-03]] \ No newline at end of file diff --git a/tests/regression_tests/tally_aggregation/test.py b/tests/regression_tests/tally_aggregation/test.py index f7df543ded..70a948be67 100644 --- a/tests/regression_tests/tally_aggregation/test.py +++ b/tests/regression_tests/tally_aggregation/test.py @@ -1,26 +1,61 @@ import hashlib import openmc +import pytest from tests.testing_harness import PyAPITestHarness +@pytest.fixture +def model(): + model = openmc.model.Model() + + fuel = openmc.Material(name='UO2') + fuel.set_density('g/cm3', 10.29769) + fuel.add_nuclide("U234", 4.4843e-6) + fuel.add_nuclide("U235", 5.5815e-4) + fuel.add_nuclide("U238", 2.2408e-2) + fuel.add_nuclide("O16", 4.5829e-2) + water = openmc.Material(name='light water') + water.add_nuclide('H1', 2.0) + water.add_nuclide('O16', 1.0) + water.set_density('g/cm3', 1.0) + water.add_s_alpha_beta('c_H_in_H2O') + model.materials.extend([fuel, water]) + + cyl = openmc.ZCylinder(r=0.4) + pin = openmc.model.pin([cyl], [fuel, water]) + d = 1.2 + lattice = openmc.RectLattice() + lattice.lower_left = (-d, -d) + lattice.pitch = (d, d) + lattice.outer = pin + lattice.universes = [ + [pin, pin], + [pin, pin], + ] + box = openmc.model.rectangular_prism(2*d, 2*d, boundary_type='reflective') + main_cell = openmc.Cell(fill=lattice, region=box) + model.geometry = openmc.Geometry([main_cell]) + + model.settings.batches = 10 + model.settings.inactive = 5 + model.settings.particles = 1000 + + energy_filter = openmc.EnergyFilter([0.0, 0.253, 1.0e3, 1.0e6, 20.0e6]) + distrib_filter = openmc.DistribcellFilter(pin.cells[1]) + tally = openmc.Tally(name='distribcell tally') + tally.filters = [energy_filter, distrib_filter] + tally.scores = ['nu-fission', 'total'] + tally.nuclides = ['U234', 'U235', 'U238'] + model.tallies.append(tally) + + return model + + + class TallyAggregationTestHarness(PyAPITestHarness): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - # Initialize the filters - energy_filter = openmc.EnergyFilter([0.0, 0.253, 1.0e3, 1.0e6, 20.0e6]) - distrib_filter = openmc.DistribcellFilter(60) - - # Initialized the tallies - tally = openmc.Tally(name='distribcell tally') - tally.filters = [energy_filter, distrib_filter] - tally.scores = ['nu-fission', 'total'] - tally.nuclides = ['U234', 'U235', 'U238'] - self._model.tallies.append(tally) - - def _get_results(self, hash_output=True): + def _get_results(self, hash_output=False): """Digest info in the statepoint and return as a string.""" # Read the statepoint file. @@ -52,15 +87,9 @@ class TallyAggregationTestHarness(PyAPITestHarness): outstr += ', '.join(map(str, tally_sum.mean)) outstr += ', '.join(map(str, tally_sum.std_dev)) - # Hash the results if necessary - if hash_output: - sha512 = hashlib.sha512() - sha512.update(outstr.encode('utf-8')) - outstr = sha512.hexdigest() - return outstr -def test_tally_aggregation(): - harness = TallyAggregationTestHarness('statepoint.10.h5') +def test_tally_aggregation(model): + harness = TallyAggregationTestHarness('statepoint.10.h5', model) harness.main() diff --git a/tests/regression_tests/tally_arithmetic/inputs_true.dat b/tests/regression_tests/tally_arithmetic/inputs_true.dat index 842b0df623..fbbf94fa90 100644 --- a/tests/regression_tests/tally_arithmetic/inputs_true.dat +++ b/tests/regression_tests/tally_arithmetic/inputs_true.dat @@ -1,338 +1,55 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 1.26 1.26 - 17 17 - -10.71 -10.71 - -1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 -1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 -1 1 1 1 1 2 1 1 2 1 1 2 1 1 1 1 1 -1 1 1 2 1 1 1 1 1 1 1 1 1 2 1 1 1 -1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 -1 1 2 1 1 2 1 1 2 1 1 2 1 1 2 1 1 -1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 -1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 -1 1 2 1 1 2 1 1 2 1 1 2 1 1 2 1 1 -1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 -1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 -1 1 2 1 1 2 1 1 2 1 1 2 1 1 2 1 1 -1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 -1 1 1 2 1 1 1 1 1 1 1 1 1 2 1 1 1 -1 1 1 1 1 2 1 1 2 1 1 2 1 1 1 1 1 -1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 -1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 - - - 1.26 1.26 - 17 17 - -10.71 -10.71 - -3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 -3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 -3 3 3 3 3 4 3 3 4 3 3 4 3 3 3 3 3 -3 3 3 4 3 3 3 3 3 3 3 3 3 4 3 3 3 -3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 -3 3 4 3 3 4 3 3 4 3 3 4 3 3 4 3 3 -3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 -3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 -3 3 4 3 3 4 3 3 4 3 3 4 3 3 4 3 3 -3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 -3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 -3 3 4 3 3 4 3 3 4 3 3 4 3 3 4 3 3 -3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 -3 3 3 4 3 3 3 3 3 3 3 3 3 4 3 3 3 -3 3 3 3 3 4 3 3 4 3 3 4 3 3 3 3 3 -3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 -3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 - - - 21.42 21.42 - 21 21 - -224.91 -224.91 - -5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 -5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 -5 5 5 5 5 5 5 6 6 6 6 6 6 6 5 5 5 5 5 5 5 -5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 -5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 -5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 -5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 -5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 -5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 -5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 -5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 -5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 -5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 -5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 -5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 -5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 -5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 -5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 -5 5 5 5 5 5 5 6 6 6 6 6 6 6 5 5 5 5 5 5 5 -5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 -5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 - - - 21.42 21.42 - 21 21 - -224.91 -224.91 - -7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 -7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 -7 7 7 7 7 7 7 8 8 8 8 8 8 8 7 7 7 7 7 7 7 -7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 -7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 -7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 -7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 -7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 -7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 -7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 -7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 -7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 -7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 -7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 -7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 -7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 -7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 -7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 -7 7 7 7 7 7 7 8 8 8 8 8 8 8 7 7 7 7 7 7 7 -7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 -7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 - - - - - - - - - - - - - - - - - - + + + + - - - - - - - + + + + + - - - - - - - - - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - eigenvalue - 100 - 10 - 5 - - - -160 -160 -183 160 160 183 - - + 1000 + 5 + 0 - 2 2 2 - -160.0 -160.0 -183.0 - 160.0 160.0 183.0 + 2 2 + -10.0 -10.0 + 10.0 10.0 - 1 3 + 1 2 - 0.0 2.53e-07 0.001 1.0 20.0 + 0.0 10.0 20000000.0 - - 60 - - + 1 - 2 1 3 + 2 1 U234 U235 nu-fission total - 1 4 + 1 3 U238 U235 total fission diff --git a/tests/regression_tests/tally_arithmetic/results_true.dat b/tests/regression_tests/tally_arithmetic/results_true.dat index 6302095d08..c10e0fb386 100644 --- a/tests/regression_tests/tally_arithmetic/results_true.dat +++ b/tests/regression_tests/tally_arithmetic/results_true.dat @@ -1,139 +1,49 @@ -[[[0. 0. 0. 0.] - [0. 0. 0. 0.] - [0. 0. 0. 0.] - [0. 0. 0. 0.]] - - [[0. 0. 0. 0.] - [0. 0. 0. 0.] - [0. 0. 0. 0.] - [0. 0. 0. 0.]] - - [[0. 0. 0. 0.] - [0. 0. 0. 0.] - [0. 0. 0. 0.] - [0. 0. 0. 0.]] - - ... - - [[0. 0. 0. 0.] - [0. 0. 0. 0.] - [0. 0. 0. 0.] - [0. 0. 0. 0.]] - - [[0. 0. 0. 0.] - [0. 0. 0. 0.] - [0. 0. 0. 0.] - [0. 0. 0. 0.]] - - [[0. 0. 0. 0.] - [0. 0. 0. 0.] - [0. 0. 0. 0.] - [0. 0. 0. 0.]]][[[0. 0. 0. 0.] - [0. 0. 0. 0.] - [0. 0. 0. 0.] - [0. 0. 0. 0.]] - - [[0. 0. 0. 0.] - [0. 0. 0. 0.] - [0. 0. 0. 0.] - [0. 0. 0. 0.]] - - [[0. 0. 0. 0.] - [0. 0. 0. 0.] - [0. 0. 0. 0.] - [0. 0. 0. 0.]] - - ... - - [[0. 0. 0. 0.] - [0. 0. 0. 0.] - [0. 0. 0. 0.] - [0. 0. 0. 0.]] - - [[0. 0. 0. 0.] - [0. 0. 0. 0.] - [0. 0. 0. 0.] - [0. 0. 0. 0.]] - - [[0. 0. 0. 0.] - [0. 0. 0. 0.] - [0. 0. 0. 0.] - [0. 0. 0. 0.]]][[[0. 0. 0. 0.] - [0. 0. 0. 0.] - [0. 0. 0. 0.]] - - [[0. 0. 0. 0.] - [0. 0. 0. 0.] - [0. 0. 0. 0.]] - - [[0. 0. 0. 0.] - [0. 0. 0. 0.] - [0. 0. 0. 0.]] - - ... - - [[0. 0. 0. 0.] - [0. 0. 0. 0.] - [0. 0. 0. 0.]] - - [[0. 0. 0. 0.] - [0. 0. 0. 0.] - [0. 0. 0. 0.]] - - [[0. 0. 0. 0.] - [0. 0. 0. 0.] - [0. 0. 0. 0.]]][[[0. 0. 0.] - [0. 0. 0.] - [0. 0. 0.] - [0. 0. 0.]] - - [[0. 0. 0.] - [0. 0. 0.] - [0. 0. 0.] - [0. 0. 0.]] - - [[0. 0. 0.] - [0. 0. 0.] - [0. 0. 0.] - [0. 0. 0.]] - - ... - - [[0. 0. 0.] - [0. 0. 0.] - [0. 0. 0.] - [0. 0. 0.]] - - [[0. 0. 0.] - [0. 0. 0.] - [0. 0. 0.] - [0. 0. 0.]] - - [[0. 0. 0.] - [0. 0. 0.] - [0. 0. 0.] - [0. 0. 0.]]][[[0. 0. 0.] - [0. 0. 0.] - [0. 0. 0.]] - - [[0. 0. 0.] - [0. 0. 0.] - [0. 0. 0.]] - - [[0. 0. 0.] - [0. 0. 0.] - [0. 0. 0.]] - - ... - - [[0. 0. 0.] - [0. 0. 0.] - [0. 0. 0.]] - - [[0. 0. 0.] - [0. 0. 0.] - [0. 0. 0.]] - - [[0. 0. 0.] - [0. 0. 0.] - [0. 0. 0.]]] \ No newline at end of file +[2.87610e-07 1.60278e-13 2.54988e-04 1.42099e-10 2.33002e-07 1.83973e-07 + 2.06574e-04 1.63105e-04 7.77662e-03 4.33372e-09 4.02417e-03 2.24257e-09 + 6.30008e-03 4.97439e-03 3.26010e-03 2.57410e-03 2.52614e-07 1.57719e-13 + 2.23961e-04 1.39830e-10 2.43493e-07 1.93615e-07 2.15874e-04 1.71654e-04 + 6.83035e-03 4.26453e-09 3.53450e-03 2.20677e-09 6.58373e-03 5.23510e-03 + 3.40688e-03 2.70901e-03 2.72019e-07 1.63161e-13 2.41165e-04 1.44654e-10 + 2.44531e-07 1.94194e-07 2.16795e-04 1.72167e-04 7.35506e-03 4.41166e-09 + 3.80602e-03 2.28290e-09 6.61180e-03 5.25075e-03 3.42141e-03 2.71710e-03 + 2.41472e-07 1.50936e-13 2.14083e-04 1.33816e-10 2.24252e-07 1.77892e-07 + 1.98816e-04 1.57715e-04 6.52910e-03 4.08113e-09 3.37861e-03 2.11186e-09 + 6.06350e-03 4.80998e-03 3.13768e-03 2.48902e-03 2.48914e-03 4.48016e-05 + 1.13508e-02 2.04302e-04 1.19934e-04 2.60981e-05 5.46915e-04 1.19011e-04 + 2.77675e-02 4.99783e-04 4.93334e-02 8.87945e-04 1.33792e-03 2.91137e-04 + 2.37703e-03 5.17251e-04 2.49083e-03 4.43597e-05 1.13585e-02 2.02286e-04 + 1.21668e-04 2.66359e-05 5.54821e-04 1.21463e-04 2.77864e-02 4.94854e-04 + 4.93670e-02 8.79187e-04 1.35726e-03 2.97136e-04 2.41139e-03 5.27909e-04 + 2.28387e-03 4.25108e-05 1.04148e-02 1.93855e-04 1.16292e-04 2.67442e-05 + 5.30307e-04 1.21957e-04 2.54776e-02 4.74228e-04 4.52651e-02 8.42543e-04 + 1.29729e-03 2.98344e-04 2.30485e-03 5.30056e-04 2.25849e-03 4.37806e-05 + 1.02990e-02 1.99646e-04 1.16539e-04 2.71633e-05 5.31435e-04 1.23868e-04 + 2.51945e-02 4.88393e-04 4.47620e-02 8.67709e-04 1.30005e-03 3.03020e-04 + 2.30975e-03 5.38363e-04][2.87610e-07 1.60278e-13 2.54988e-04 1.42099e-10 2.33002e-07 1.83973e-07 + 2.06574e-04 1.63105e-04 7.77662e-03 4.33372e-09 4.02417e-03 2.24257e-09 + 6.30008e-03 4.97439e-03 3.26010e-03 2.57410e-03 2.52614e-07 1.57719e-13 + 2.23961e-04 1.39830e-10 2.43493e-07 1.93615e-07 2.15874e-04 1.71654e-04 + 6.83035e-03 4.26453e-09 3.53450e-03 2.20677e-09 6.58373e-03 5.23510e-03 + 3.40688e-03 2.70901e-03 2.72019e-07 1.63161e-13 2.41165e-04 1.44654e-10 + 2.44531e-07 1.94194e-07 2.16795e-04 1.72167e-04 7.35506e-03 4.41166e-09 + 3.80602e-03 2.28290e-09 6.61180e-03 5.25075e-03 3.42141e-03 2.71710e-03 + 2.41472e-07 1.50936e-13 2.14083e-04 1.33816e-10 2.24252e-07 1.77892e-07 + 1.98816e-04 1.57715e-04 6.52910e-03 4.08113e-09 3.37861e-03 2.11186e-09 + 6.06350e-03 4.80998e-03 3.13768e-03 2.48902e-03 2.48914e-03 4.48016e-05 + 1.13508e-02 2.04302e-04 1.19934e-04 2.60981e-05 5.46915e-04 1.19011e-04 + 2.77675e-02 4.99783e-04 4.93334e-02 8.87945e-04 1.33792e-03 2.91137e-04 + 2.37703e-03 5.17251e-04 2.49083e-03 4.43597e-05 1.13585e-02 2.02286e-04 + 1.21668e-04 2.66359e-05 5.54821e-04 1.21463e-04 2.77864e-02 4.94854e-04 + 4.93670e-02 8.79187e-04 1.35726e-03 2.97136e-04 2.41139e-03 5.27909e-04 + 2.28387e-03 4.25108e-05 1.04148e-02 1.93855e-04 1.16292e-04 2.67442e-05 + 5.30307e-04 1.21957e-04 2.54776e-02 4.74228e-04 4.52651e-02 8.42543e-04 + 1.29729e-03 2.98344e-04 2.30485e-03 5.30056e-04 2.25849e-03 4.37806e-05 + 1.02990e-02 1.99646e-04 1.16539e-04 2.71633e-05 5.31435e-04 1.23868e-04 + 2.51945e-02 4.88393e-04 4.47620e-02 8.67709e-04 1.30005e-03 3.03020e-04 + 2.30975e-03 5.38363e-04][0.0063 0.00497 0.00326 0.00257 0.00658 0.00524 0.00341 0.00271 0.00661 + 0.00525 0.00342 0.00272 0.00606 0.00481 0.00314 0.00249 0.00134 0.00029 + 0.00238 0.00052 0.00136 0.0003 0.00241 0.00053 0.0013 0.0003 0.0023 + 0.00053 0.0013 0.0003 0.00231 0.00054][0.00025 0.00021 0.00402 0.00326 0.00022 0.00022 0.00353 0.00341 0.00024 + 0.00022 0.00381 0.00342 0.00021 0.0002 0.00338 0.00314 0.01135 0.00055 + 0.04933 0.00238 0.01136 0.00055 0.04937 0.00241 0.01041 0.00053 0.04527 + 0.0023 0.0103 0.00053 0.04476 0.00231][0.00326 0.00341 0.00342 0.00314 0.00238 0.00241 0.0023 0.00231] \ No newline at end of file diff --git a/tests/regression_tests/tally_arithmetic/test.py b/tests/regression_tests/tally_arithmetic/test.py index 236f5cc384..532160a17e 100644 --- a/tests/regression_tests/tally_arithmetic/test.py +++ b/tests/regression_tests/tally_arithmetic/test.py @@ -1,39 +1,61 @@ import hashlib +import numpy as np import openmc +import pytest from tests.testing_harness import PyAPITestHarness +@pytest.fixture +def model(): + model = openmc.model.Model() + + fuel = openmc.Material() + fuel.set_density('g/cm3', 10.0) + fuel.add_nuclide('U234', 1.0) + fuel.add_nuclide('U235', 4.0) + fuel.add_nuclide('U238', 95.0) + water = openmc.Material(name='light water') + water.add_nuclide('H1', 2.0) + water.add_nuclide('O16', 1.0) + water.set_density('g/cm3', 1.0) + water.add_s_alpha_beta('c_H_in_H2O') + model.materials.extend([fuel, water]) + + cyl1 = openmc.ZCylinder(r=5.0) + cyl2 = openmc.ZCylinder(r=10.0, boundary_type='vacuum') + cell1 = openmc.Cell(fill=fuel, region=-cyl1) + cell2 = openmc.Cell(fill=water, region=+cyl1 & -cyl2) + model.geometry = openmc.Geometry([cell1, cell2]) + + model.settings.batches = 5 + model.settings.inactive = 0 + model.settings.particles = 1000 + + mesh = openmc.RegularMesh() + mesh.dimension = (2, 2) + mesh.lower_left = (-10.0, -10.0) + mesh.upper_right = (10.0, 10.0) + energy_filter = openmc.EnergyFilter((0.0, 10.0, 20.0e6)) + material_filter = openmc.MaterialFilter((fuel, water)) + mesh_filter = openmc.MeshFilter(mesh) + + tally = openmc.Tally(name='tally 1') + tally.filters = [material_filter, energy_filter] + tally.scores = ['nu-fission', 'total'] + tally.nuclides = ['U234', 'U235'] + model.tallies.append(tally) + tally = openmc.Tally(name='tally 2') + tally.filters = [energy_filter, mesh_filter] + tally.scores = ['total', 'fission'] + tally.nuclides = ['U238', 'U235'] + model.tallies.append(tally) + + return model + + class TallyArithmeticTestHarness(PyAPITestHarness): - def __init__(self, *args, **kwargs): - super().__init__(*args, **kwargs) - - # Initialize Mesh - mesh = openmc.RegularMesh(mesh_id=1) - mesh.dimension = [2, 2, 2] - mesh.lower_left = [-160.0, -160.0, -183.0] - mesh.upper_right = [160.0, 160.0, 183.0] - - # Initialize the filters - energy_filter = openmc.EnergyFilter((0.0, 0.253e-6, 1.0e-3, 1.0, 20.0)) - material_filter = openmc.MaterialFilter((1, 3)) - distrib_filter = openmc.DistribcellFilter(60) - mesh_filter = openmc.MeshFilter(mesh) - - # Initialized the tallies - tally = openmc.Tally(name='tally 1') - tally.filters = [material_filter, energy_filter, distrib_filter] - tally.scores = ['nu-fission', 'total'] - tally.nuclides = ['U234', 'U235'] - self._model.tallies.append(tally) - - tally = openmc.Tally(name='tally 2') - tally.filters = [energy_filter, mesh_filter] - tally.scores = ['total', 'fission'] - tally.nuclides = ['U238', 'U235'] - self._model.tallies.append(tally) - def _get_results(self, hash_output=False): """Digest info in the statepoint and return as a string.""" @@ -45,27 +67,29 @@ class TallyArithmeticTestHarness(PyAPITestHarness): tally_2 = sp.get_tally(name='tally 2') # Perform all the tally arithmetic operations and output results - outstr = '' - tally_3 = tally_1 * tally_2 - outstr += str(tally_3.mean) + output = [] + with np.printoptions(precision=5, threshold=np.inf): + mean = (tally_1 * tally_2).mean + output.append(str(mean[np.nonzero(mean)])) - tally_3 = tally_1.hybrid_product(tally_2, '*', 'entrywise', 'tensor', - 'tensor') - outstr += str(tally_3.mean) + mean = tally_1.hybrid_product( + tally_2, '*', 'entrywise', 'tensor', 'tensor').mean + output.append(str(mean[np.nonzero(mean)])) - tally_3 = tally_1.hybrid_product(tally_2, '*', 'entrywise', 'entrywise', - 'tensor') - outstr += str(tally_3.mean) + mean = tally_1.hybrid_product( + tally_2, '*', 'entrywise', 'entrywise', 'tensor').mean + output.append(str(mean[np.nonzero(mean)])) - tally_3 = tally_1.hybrid_product(tally_2, '*', 'entrywise', 'tensor', - 'entrywise') - outstr += str(tally_3.mean) + mean = tally_1.hybrid_product( + tally_2, '*', 'entrywise', 'tensor', 'entrywise').mean + output.append(str(mean[np.nonzero(mean)])) - tally_3 = tally_1.hybrid_product(tally_2, '*', 'entrywise', 'entrywise', - 'entrywise') - outstr += str(tally_3.mean) + mean = tally_1.hybrid_product( + tally_2, '*', 'entrywise', 'entrywise', 'entrywise').mean + output.append(str(mean[np.nonzero(mean)])) # Hash the results if necessary + outstr = ''.join(output) if hash_output: sha512 = hashlib.sha512() sha512.update(outstr.encode('utf-8')) @@ -74,6 +98,6 @@ class TallyArithmeticTestHarness(PyAPITestHarness): return outstr -def test_tally_arithmetic(): - harness = TallyArithmeticTestHarness('statepoint.10.h5') +def test_tally_arithmetic(model): + harness = TallyArithmeticTestHarness('statepoint.5.h5', model) harness.main() diff --git a/tests/regression_tests/tally_assumesep/geometry.xml b/tests/regression_tests/tally_assumesep/geometry.xml index f6f067aadd..a028ad05de 100644 --- a/tests/regression_tests/tally_assumesep/geometry.xml +++ b/tests/regression_tests/tally_assumesep/geometry.xml @@ -1,181 +1,9 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 17 17 - -10.71 -10.71 - 1.26 1.26 - - 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 - 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 - 1 1 1 1 1 2 1 1 2 1 1 2 1 1 1 1 1 - 1 1 1 2 1 1 1 1 1 1 1 1 1 2 1 1 1 - 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 - 1 1 2 1 1 2 1 1 2 1 1 2 1 1 2 1 1 - 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 - 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 - 1 1 2 1 1 2 1 1 2 1 1 2 1 1 2 1 1 - 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 - 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 - 1 1 2 1 1 2 1 1 2 1 1 2 1 1 2 1 1 - 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 - 1 1 1 2 1 1 1 1 1 1 1 1 1 2 1 1 1 - 1 1 1 1 1 2 1 1 2 1 1 2 1 1 1 1 1 - 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 - 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 1 - - - - - - 17 17 - -10.71 -10.71 - 1.26 1.26 - - 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 - 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 - 3 3 3 3 3 4 3 3 4 3 3 4 3 3 3 3 3 - 3 3 3 4 3 3 3 3 3 3 3 3 3 4 3 3 3 - 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 - 3 3 4 3 3 4 3 3 4 3 3 4 3 3 4 3 3 - 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 - 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 - 3 3 4 3 3 4 3 3 4 3 3 4 3 3 4 3 3 - 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 - 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 - 3 3 4 3 3 4 3 3 4 3 3 4 3 3 4 3 3 - 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 - 3 3 3 4 3 3 3 3 3 3 3 3 3 4 3 3 3 - 3 3 3 3 3 4 3 3 4 3 3 4 3 3 3 3 3 - 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 - 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 3 - - - - - - 21 21 - -224.91 -224.91 - 21.42 21.42 - - 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 - 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 - 5 5 5 5 5 5 5 6 6 6 6 6 6 6 5 5 5 5 5 5 5 - 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 - 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 - 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 - 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 - 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 - 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 - 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 - 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 - 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 - 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 - 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 - 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 - 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 - 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 - 5 5 5 5 5 6 6 6 6 6 6 6 6 6 6 6 5 5 5 5 5 - 5 5 5 5 5 5 5 6 6 6 6 6 6 6 5 5 5 5 5 5 5 - 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 - 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 5 - - - - - - 21 21 - -224.91 -224.91 - 21.42 21.42 - - 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 - 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 - 7 7 7 7 7 7 7 8 8 8 8 8 8 8 7 7 7 7 7 7 7 - 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 - 7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 - 7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 - 7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 - 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 - 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 - 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 - 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 - 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 - 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 - 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 - 7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 - 7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 - 7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 - 7 7 7 7 7 8 8 8 8 8 8 8 8 8 8 8 7 7 7 7 7 - 7 7 7 7 7 7 7 8 8 8 8 8 8 8 7 7 7 7 7 7 7 - 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 - 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 7 - - - + + + + + + diff --git a/tests/regression_tests/tally_assumesep/materials.xml b/tests/regression_tests/tally_assumesep/materials.xml index 8021f5f99e..b27086e61f 100644 --- a/tests/regression_tests/tally_assumesep/materials.xml +++ b/tests/regression_tests/tally_assumesep/materials.xml @@ -1,270 +1,15 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - - - - - - - + + - - - - - - - - + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tests/regression_tests/tally_assumesep/results_true.dat b/tests/regression_tests/tally_assumesep/results_true.dat index 5f4692ad16..d9dc53f0ab 100644 --- a/tests/regression_tests/tally_assumesep/results_true.dat +++ b/tests/regression_tests/tally_assumesep/results_true.dat @@ -1,11 +1,11 @@ k-combined: -9.581522E-01 4.261828E-02 +6.161485E-01 2.229530E-02 tally 1: -1.529084E+01 -4.769011E+01 +7.433231E+00 +1.122269E+01 tally 2: -3.198905E+00 -2.114128E+00 +2.545046E-01 +1.340485E-02 tally 3: -4.510603E+01 -4.183089E+02 +1.136947E+01 +2.646408E+01 diff --git a/tests/regression_tests/tally_assumesep/settings.xml b/tests/regression_tests/tally_assumesep/settings.xml index 569c809821..64b69f394b 100644 --- a/tests/regression_tests/tally_assumesep/settings.xml +++ b/tests/regression_tests/tally_assumesep/settings.xml @@ -1,18 +1,12 @@ - eigenvalue 10 5 100 - - - - -160 -160 -183 - 160 160 183 - + + 0.0 0.0 0.0 - diff --git a/tests/regression_tests/tally_assumesep/tallies.xml b/tests/regression_tests/tally_assumesep/tallies.xml index c588eb9e40..f65e5dfec6 100644 --- a/tests/regression_tests/tally_assumesep/tallies.xml +++ b/tests/regression_tests/tally_assumesep/tallies.xml @@ -5,17 +5,17 @@ cell - 21 + 1 cell - 22 + 2 cell - 23 + 3 diff --git a/tests/regression_tests/void/geometry.xml b/tests/regression_tests/void/geometry.xml deleted file mode 100644 index 48a72c7c31..0000000000 --- a/tests/regression_tests/void/geometry.xml +++ /dev/null @@ -1,40 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tests/regression_tests/void/inputs_true.dat b/tests/regression_tests/void/inputs_true.dat new file mode 100644 index 0000000000..6449520975 --- /dev/null +++ b/tests/regression_tests/void/inputs_true.dat @@ -0,0 +1,132 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + fixed source + 1000 + 3 + + + 0.0 0.0 0.0 + + + + + + + 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 + + + 1 + total + + diff --git a/tests/regression_tests/void/materials.xml b/tests/regression_tests/void/materials.xml deleted file mode 100644 index f70c3a40f2..0000000000 --- a/tests/regression_tests/void/materials.xml +++ /dev/null @@ -1,61 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tests/regression_tests/void/results_true.dat b/tests/regression_tests/void/results_true.dat index c0e8184b6f..ec87df3bad 100644 --- a/tests/regression_tests/void/results_true.dat +++ b/tests/regression_tests/void/results_true.dat @@ -1,2 +1,103 @@ -k-combined: -9.612556E-01 1.990135E-02 +tally 1: +8.636087E-01 +2.486134E-01 +0.000000E+00 +0.000000E+00 +2.848447E+00 +2.704761E+00 +0.000000E+00 +0.000000E+00 +3.944691E+00 +5.195294E+00 +0.000000E+00 +0.000000E+00 +4.822264E+00 +7.764351E+00 +0.000000E+00 +0.000000E+00 +5.295627E+00 +9.359046E+00 +0.000000E+00 +0.000000E+00 +5.331702E+00 +9.480054E+00 +0.000000E+00 +0.000000E+00 +5.598029E+00 +1.044833E+01 +0.000000E+00 +0.000000E+00 +5.803138E+00 +1.124136E+01 +0.000000E+00 +0.000000E+00 +5.768440E+00 +1.110547E+01 +0.000000E+00 +0.000000E+00 +5.554687E+00 +1.029492E+01 +0.000000E+00 +0.000000E+00 +5.503880E+00 +1.011832E+01 +0.000000E+00 +0.000000E+00 +5.059080E+00 +8.564408E+00 +0.000000E+00 +0.000000E+00 +4.718153E+00 +7.431929E+00 +0.000000E+00 +0.000000E+00 +4.575826E+00 +6.980348E+00 +0.000000E+00 +0.000000E+00 +4.283929E+00 +6.120164E+00 +0.000000E+00 +0.000000E+00 +3.997971E+00 +5.330979E+00 +0.000000E+00 +0.000000E+00 +3.735927E+00 +4.653022E+00 +0.000000E+00 +0.000000E+00 +3.302545E+00 +3.645387E+00 +0.000000E+00 +0.000000E+00 +3.082630E+00 +3.203343E+00 +0.000000E+00 +0.000000E+00 +2.775434E+00 +2.569844E+00 +0.000000E+00 +0.000000E+00 +2.518170E+00 +2.134420E+00 +0.000000E+00 +0.000000E+00 +2.233921E+00 +1.666795E+00 +0.000000E+00 +0.000000E+00 +1.829991E+00 +1.118883E+00 +0.000000E+00 +0.000000E+00 +1.420528E+00 +6.755061E-01 +0.000000E+00 +0.000000E+00 +9.856494E-01 +3.253539E-01 +0.000000E+00 +0.000000E+00 +0.000000E+00 +0.000000E+00 diff --git a/tests/regression_tests/void/settings.xml b/tests/regression_tests/void/settings.xml deleted file mode 100644 index 32afc717a9..0000000000 --- a/tests/regression_tests/void/settings.xml +++ /dev/null @@ -1,16 +0,0 @@ - - - - eigenvalue - 10 - 5 - 100 - - - - point - 0.0 0.0 0.0 - - - - diff --git a/tests/regression_tests/void/test.py b/tests/regression_tests/void/test.py index af16f2ac80..af1e3887b8 100644 --- a/tests/regression_tests/void/test.py +++ b/tests/regression_tests/void/test.py @@ -1,6 +1,39 @@ -from tests.testing_harness import TestHarness +import numpy as np +import openmc +import pytest + +from tests.testing_harness import PyAPITestHarness + +@pytest.fixture +def model(): + model = openmc.model.Model() + + zn = openmc.Material() + zn.set_density('g/cm3', 7.14) + zn.add_nuclide('Zn64', 1.0) + model.materials.append(zn) + + radii = np.linspace(1.0, 100.0) + surfs = [openmc.Sphere(r=r) for r in radii] + surfs[-1].boundary_type = 'vacuum' + cells = [openmc.Cell(fill=(zn if i % 2 == 0 else None), region=region) + for i, region in enumerate(openmc.model.subdivide(surfs))] + model.geometry = openmc.Geometry(cells) + + model.settings.run_mode = 'fixed source' + model.settings.batches = 3 + model.settings.particles = 1000 + model.settings.source = openmc.Source(space=openmc.stats.Point()) + + cell_filter = openmc.CellFilter(cells) + tally = openmc.Tally() + tally.filters = [cell_filter] + tally.scores = ['total'] + model.tallies.append(tally) + + return model -def test_void(): - harness = TestHarness('statepoint.10.h5') +def test_void(model): + harness = PyAPITestHarness('statepoint.3.h5', model) harness.main() diff --git a/tests/testing_harness.py b/tests/testing_harness.py index e341a3cf17..0d3c4bec00 100644 --- a/tests/testing_harness.py +++ b/tests/testing_harness.py @@ -72,10 +72,12 @@ class TestHarness(object): # Read the statepoint file. statepoint = glob.glob(self._sp_name)[0] with openmc.StatePoint(statepoint) as sp: - # Write out k-combined. - outstr = 'k-combined:\n' - form = '{0:12.6E} {1:12.6E}\n' - outstr += form.format(sp.k_combined.n, sp.k_combined.s) + outstr = '' + if sp.run_mode == 'eigenvalue': + # Write out k-combined. + outstr += 'k-combined:\n' + form = '{0:12.6E} {1:12.6E}\n' + outstr += form.format(sp.k_combined.n, sp.k_combined.s) # Write out tally data. for i, tally_ind in enumerate(sp.tallies): diff --git a/tests/unit_tests/conftest.py b/tests/unit_tests/conftest.py index aa28be9c3b..b69ef0b138 100644 --- a/tests/unit_tests/conftest.py +++ b/tests/unit_tests/conftest.py @@ -1,6 +1,17 @@ import openmc import pytest +from tests.regression_tests import config + + +@pytest.fixture(scope='module') +def mpi_intracomm(): + if config['mpi']: + from mpi4py import MPI + return MPI.COMM_WORLD + else: + return None + @pytest.fixture(scope='module') def uo2(): diff --git a/tests/unit_tests/test_capi.py b/tests/unit_tests/test_capi.py index 6953186eb9..c8ad0907c5 100644 --- a/tests/unit_tests/test_capi.py +++ b/tests/unit_tests/test_capi.py @@ -35,6 +35,14 @@ def pincell_model(): zernike_tally.scores = ['fission'] pincell.tallies.append(zernike_tally) + # Add an energy function tally + energyfunc_tally = openmc.Tally() + energyfunc_filter = openmc.EnergyFunctionFilter( + [0.0, 20e6], [0.0, 20e6]) + energyfunc_tally.scores = ['fission'] + energyfunc_tally.filters = [energyfunc_filter] + pincell.tallies.append(energyfunc_tally) + # Write XML files in tmpdir with cdtemp(): pincell.export_to_xml() @@ -42,8 +50,8 @@ def pincell_model(): @pytest.fixture(scope='module') -def capi_init(pincell_model): - openmc.capi.init() +def capi_init(pincell_model, mpi_intracomm): + openmc.capi.init(intracomm=mpi_intracomm) yield openmc.capi.finalize() @@ -170,12 +178,21 @@ def test_settings(capi_init): def test_tally_mapping(capi_init): tallies = openmc.capi.tallies assert isinstance(tallies, Mapping) - assert len(tallies) == 2 + assert len(tallies) == 3 for tally_id, tally in tallies.items(): assert isinstance(tally, openmc.capi.Tally) assert tally_id == tally.id +def test_energy_function_filter(capi_init): + """Test special __new__ and __init__ for EnergyFunctionFilter""" + efunc = openmc.capi.EnergyFunctionFilter([0.0, 1.0], [0.0, 2.0]) + assert len(efunc.energy) == 2 + assert (efunc.energy == [0.0, 1.0]).all() + assert len(efunc.y) == 2 + assert (efunc.y == [0.0, 2.0]).all() + + def test_tally(capi_init): t = openmc.capi.tallies[1] assert t.type == 'volume' @@ -211,6 +228,16 @@ def test_tally(capi_init): assert len(t2.filters[1].bins) == 3 assert t2.filters[0].order == 5 + t3 = openmc.capi.tallies[3] + assert len(t3.filters) == 1 + t3_f = t3.filters[0] + assert isinstance(t3_f, openmc.capi.EnergyFunctionFilter) + assert len(t3_f.energy) == 2 + assert len(t3_f.y) == 2 + t3_f.set_data([0.0, 1.0, 2.0], [0.0, 1.0, 4.0]) + assert len(t3_f.energy) == 3 + assert len(t3_f.y) == 3 + def test_new_tally(capi_init): with pytest.raises(exc.AllocationError): @@ -219,7 +246,7 @@ def test_new_tally(capi_init): new_tally.scores = ['flux'] new_tally_with_id = openmc.capi.Tally(10) new_tally_with_id.scores = ['flux'] - assert len(openmc.capi.tallies) == 4 + assert len(openmc.capi.tallies) == 5 def test_tally_activate(capi_simulation_init): @@ -381,11 +408,11 @@ def test_mesh(capi_init): assert msf.mesh == mesh -def test_restart(capi_init): +def test_restart(capi_init, mpi_intracomm): # Finalize and re-init to make internal state consistent with XML. openmc.capi.hard_reset() openmc.capi.finalize() - openmc.capi.init() + openmc.capi.init(intracomm=mpi_intracomm) openmc.capi.simulation_init() # Run for 7 batches then write a statepoint. diff --git a/tests/unit_tests/test_complex_cell_capi.py b/tests/unit_tests/test_complex_cell_capi.py index 365ce48081..a76fa63076 100644 --- a/tests/unit_tests/test_complex_cell_capi.py +++ b/tests/unit_tests/test_complex_cell_capi.py @@ -3,7 +3,7 @@ import openmc.capi import pytest @pytest.fixture(autouse=True) -def complex_cell(run_in_tmpdir): +def complex_cell(run_in_tmpdir, mpi_intracomm): openmc.reset_auto_ids() @@ -74,7 +74,7 @@ def complex_cell(run_in_tmpdir): model.export_to_xml() openmc.capi.finalize() - openmc.capi.init() + openmc.capi.init(intracomm=mpi_intracomm) yield diff --git a/tests/unit_tests/test_data_neutron.py b/tests/unit_tests/test_data_neutron.py index 3cf036aa2c..40a286331e 100644 --- a/tests/unit_tests/test_data_neutron.py +++ b/tests/unit_tests/test_data_neutron.py @@ -205,6 +205,22 @@ def test_derived_products(am244): assert total_neutron.yield_(6e6) == pytest.approx(4.2558) +def test_heating(run_in_tmpdir, am244): + assert 318 in am244.reactions + assert 999 in am244.reactions + # compare values in 999 reaction + total_heating = am244.reactions[301].xs["294K"] + fission_heating = am244.reactions[318].xs["294K"] + expected_y = total_heating.y - fission_heating(total_heating.x) + assert am244.reactions[999].xs["294K"].y == pytest.approx(expected_y) + + am244.export_to_hdf5("am244.h5") + read_in = openmc.data.IncidentNeutron.from_hdf5("am244.h5") + assert 318 in read_in.reactions + assert 999 in read_in.reactions + assert read_in.reactions[999].xs["294K"].y == pytest.approx(expected_y) + + def test_urr(pu239): for T, ptable in pu239.urr.items(): assert T.endswith('K') @@ -465,7 +481,7 @@ def test_ace_convert(run_in_tmpdir): filename = os.path.join(_ENDF_DATA, 'neutrons', 'n-001_H_001.endf') ace_ascii = 'ace_ascii' ace_binary = 'ace_binary' - openmc.data.njoy.make_ace(filename, ace=ace_ascii) + openmc.data.njoy.make_ace(filename, acer=ace_ascii) # Convert to binary openmc.data.ace.ascii_to_binary(ace_ascii, ace_binary) diff --git a/tests/unit_tests/test_deplete_cecm.py b/tests/unit_tests/test_deplete_cecm.py deleted file mode 100644 index dd8b769fe6..0000000000 --- a/tests/unit_tests/test_deplete_cecm.py +++ /dev/null @@ -1,43 +0,0 @@ -"""Regression tests for openmc.deplete.integrator.cecm algorithm. - -These tests integrate a simple test problem described in dummy_geometry.py. -""" - -from pytest import approx -import openmc.deplete - -from tests import dummy_operator - - -def test_cecm(run_in_tmpdir): - """Integral regression test of integrator algorithm using CE/CM.""" - - op = dummy_operator.DummyOperator() - op.output_dir = "test_integrator_regression" - - # Perform simulation using the MCNPX/MCNP6 algorithm - dt = [0.75, 0.75] - power = 1.0 - openmc.deplete.cecm(op, dt, power, print_out=False) - - # Load the files - res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") - - _, y1 = res.get_atoms("1", "1") - _, y2 = res.get_atoms("1", "2") - - # Mathematica solution - s1 = [1.86872629872102, 1.395525772416039] - s2 = [2.18097439443550, 2.69429754646747] - - assert y1[1] == approx(s1[0]) - assert y2[1] == approx(s1[1]) - - assert y1[2] == approx(s2[0]) - assert y2[2] == approx(s2[1]) - - # Test structure of depletion time dataset - - dep_time = res.get_depletion_time() - assert dep_time.shape == (len(dt), ) - assert all(dep_time > 0) diff --git a/tests/unit_tests/test_deplete_celi.py b/tests/unit_tests/test_deplete_celi.py deleted file mode 100644 index 47d3319fdb..0000000000 --- a/tests/unit_tests/test_deplete_celi.py +++ /dev/null @@ -1,43 +0,0 @@ -"""Regression tests for openmc.deplete.integrator.celi algorithm. - -These tests integrate a simple test problem described in dummy_geometry.py. -""" - -from pytest import approx -import openmc.deplete - -from tests import dummy_operator - - -def test_celi(run_in_tmpdir): - """Integral regression test of integrator algorithm using celi""" - - op = dummy_operator.DummyOperator() - op.output_dir = "test_integrator_regression" - - # Perform simulation using the celi algorithm - dt = [0.75, 0.75] - power = 1.0 - openmc.deplete.celi(op, dt, power, print_out=False) - - # Load the files - res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") - - _, y1 = res.get_atoms("1", "1") - _, y2 = res.get_atoms("1", "2") - - # Reference solution - s1 = [1.82078767, 0.97122898] - s2 = [2.68441779, 0.05125966] - - assert y1[1] == approx(s1[0]) - assert y2[1] == approx(s1[1]) - - assert y1[2] == approx(s2[0]) - assert y2[2] == approx(s2[1]) - - # Test structure of depletion time dataset - - dep_time = res.get_depletion_time() - assert dep_time.shape == (len(dt), ) - assert all(dep_time > 0) diff --git a/tests/unit_tests/test_deplete_cf4.py b/tests/unit_tests/test_deplete_cf4.py deleted file mode 100644 index 0c6199c3e5..0000000000 --- a/tests/unit_tests/test_deplete_cf4.py +++ /dev/null @@ -1,43 +0,0 @@ -"""Regression tests for openmc.deplete.integrator.cf4 algorithm. - -These tests integrate a simple test problem described in dummy_geometry.py. -""" - -from pytest import approx -import openmc.deplete - -from tests import dummy_operator - - -def test_cf4(run_in_tmpdir): - """Integral regression test of integrator algorithm using CF4""" - - op = dummy_operator.DummyOperator() - op.output_dir = "test_integrator_regression" - - # Perform simulation using the cf4 algorithm - dt = [0.75, 0.75] - power = 1.0 - openmc.deplete.cf4(op, dt, power, print_out=False) - - # Load the files - res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") - - _, y1 = res.get_atoms("1", "1") - _, y2 = res.get_atoms("1", "2") - - # Reference solution - s1 = [2.06101629, 1.37783588] - s2 = [2.57241318, 2.63731630] - - assert y1[1] == approx(s1[0]) - assert y2[1] == approx(s1[1]) - - assert y1[2] == approx(s2[0]) - assert y2[2] == approx(s2[1]) - - # Test structure of depletion time dataset - - dep_time = res.get_depletion_time() - assert dep_time.shape == (len(dt), ) - assert all(dep_time > 0) diff --git a/tests/unit_tests/test_deplete_chain.py b/tests/unit_tests/test_deplete_chain.py index 33b13b596d..3dcd3ad750 100644 --- a/tests/unit_tests/test_deplete_chain.py +++ b/tests/unit_tests/test_deplete_chain.py @@ -3,10 +3,11 @@ from collections.abc import Mapping import os from pathlib import Path +from itertools import product import numpy as np from openmc.data import zam, ATOMIC_SYMBOL -from openmc.deplete import comm, Chain, reaction_rates, nuclide +from openmc.deplete import comm, Chain, reaction_rates, nuclide, cram import pytest from tests import cdtemp @@ -128,9 +129,10 @@ def test_from_xml(simple_chain): assert [r.branching_ratio for r in nuc.reactions] == [1.0, 0.7, 0.3] # Yield tests - assert nuc.yield_energies == [0.0253] + assert nuc.yield_energies == (0.0253,) assert list(nuc.yield_data) == [0.0253] - assert nuc.yield_data[0.0253] == [("A", 0.0292737), ("B", 0.002566345)] + assert nuc.yield_data[0.0253].products == ("A", "B") + assert (nuc.yield_data[0.0253].yields == [0.0292737, 0.002566345]).all() def test_export_to_xml(run_in_tmpdir): @@ -139,8 +141,7 @@ def test_export_to_xml(run_in_tmpdir): # Prevent different MPI ranks from conflicting filename = 'test{}.xml'.format(comm.rank) - A = nuclide.Nuclide() - A.name = "A" + A = nuclide.Nuclide("A") A.half_life = 2.36520e4 A.decay_modes = [ nuclide.DecayTuple("beta1", "B", 0.6), @@ -148,21 +149,19 @@ def test_export_to_xml(run_in_tmpdir): ] A.reactions = [nuclide.ReactionTuple("(n,gamma)", "C", 0.0, 1.0)] - B = nuclide.Nuclide() - B.name = "B" + B = nuclide.Nuclide("B") B.half_life = 3.29040e4 B.decay_modes = [nuclide.DecayTuple("beta", "A", 1.0)] B.reactions = [nuclide.ReactionTuple("(n,gamma)", "C", 0.0, 1.0)] - C = nuclide.Nuclide() - C.name = "C" + C = nuclide.Nuclide("C") C.reactions = [ nuclide.ReactionTuple("fission", None, 2.0e8, 1.0), nuclide.ReactionTuple("(n,gamma)", "A", 0.0, 0.7), nuclide.ReactionTuple("(n,gamma)", "B", 0.0, 0.3) ] - C.yield_energies = [0.0253] - C.yield_data = {0.0253: [("A", 0.0292737), ("B", 0.002566345)]} + C.yield_data = nuclide.FissionYieldDistribution({ + 0.0253: {"A": 0.0292737, "B": 0.002566345}}) chain = Chain() chain.nuclides = [A, B, C] @@ -220,6 +219,13 @@ def test_form_matrix(simple_chain): assert mat[1, 2] == mat12 assert mat[2, 2] == mat22 + # Pass equivalent fission yields directly + # Ensure identical matrix is formed + f_yields = {"C": {"A": 0.0292737, "B": 0.002566345}} + new_mat = chain.form_matrix(react[0], f_yields) + for r, c in product(range(3), range(3)): + assert new_mat[r, c] == mat[r, c] + def test_getitem(): """ Test nuc_by_ind converter function. """ @@ -248,29 +254,29 @@ def test_set_fiss_q(): def test_get_set_chain_br(simple_chain): """Test minor modifications to capture branch ratios""" expected = {"C": {"A": 0.7, "B": 0.3}} - assert simple_chain.get_capture_branches() == expected + assert simple_chain.get_branch_ratios() == expected # safely modify new_chain = Chain.from_xml("chain_test.xml") new_br = {"C": {"A": 0.5, "B": 0.5}, "A": {"C": 0.99, "B": 0.01}} - new_chain.set_capture_branches(new_br) - assert new_chain.get_capture_branches() == new_br + new_chain.set_branch_ratios(new_br) + assert new_chain.get_branch_ratios() == new_br # write, re-read new_chain.export_to_xml("chain_mod.xml") - assert Chain.from_xml("chain_mod.xml").get_capture_branches() == new_br + assert Chain.from_xml("chain_mod.xml").get_branch_ratios() == new_br # Test non-strict [warn, not error] setting bad_br = {"B": {"X": 0.6, "A": 0.4}, "X": {"A": 0.5, "C": 0.5}} bad_br.update(new_br) - new_chain.set_capture_branches(bad_br, strict=False) - assert new_chain.get_capture_branches() == new_br + new_chain.set_branch_ratios(bad_br, strict=False) + assert new_chain.get_branch_ratios() == new_br # Ensure capture reactions are removed rem_br = {"A": {"C": 1.0}} - new_chain.set_capture_branches(rem_br) + new_chain.set_branch_ratios(rem_br) # A is not in returned dict because there is no branch - assert "A" not in new_chain.get_capture_branches() + assert "A" not in new_chain.get_branch_ratios() def test_capture_branch_infer_ground(): @@ -283,15 +289,14 @@ def test_capture_branch_infer_ground(): chain = Chain.from_xml(chain_file) # Create nuclide to be added into the chain - xe136m = nuclide.Nuclide() - xe136m.name = "Xe136_m1" + xe136m = nuclide.Nuclide("Xe136_m1") chain.nuclides.append(xe136m) chain.nuclide_dict[xe136m.name] = len(chain.nuclides) - 1 - chain.set_capture_branches(infer_br) + chain.set_branch_ratios(infer_br, "(n,gamma)") - assert chain.get_capture_branches() == set_br + assert chain.get_branch_ratios("(n,gamma)") == set_br def test_capture_branch_no_rxn(): @@ -301,15 +306,13 @@ def test_capture_branch_no_rxn(): chain_file = Path(__file__).parents[1] / "chain_simple.xml" chain = Chain.from_xml(chain_file) - u5m = nuclide.Nuclide() - u5m.name = "U235_m1" + u5m = nuclide.Nuclide("U235_m1") chain.nuclides.append(u5m) chain.nuclide_dict[u5m.name] = len(chain.nuclides) - 1 - phrase = "U234 does not have capture reactions" - with pytest.raises(AttributeError, match=phrase): - chain.set_capture_branches(u4br) + with pytest.raises(AttributeError, match="U234"): + chain.set_branch_ratios(u4br) def test_capture_branch_failures(simple_chain): @@ -318,14 +321,140 @@ def test_capture_branch_failures(simple_chain): # Parent isotope not present br = {"X": {"A": 0.6, "B": 0.7}} with pytest.raises(KeyError, match="X"): - simple_chain.set_capture_branches(br) + simple_chain.set_branch_ratios(br) # Product isotope not present br = {"C": {"X": 0.4, "A": 0.2, "B": 0.4}} with pytest.raises(KeyError, match="X"): - simple_chain.set_capture_branches(br) + simple_chain.set_branch_ratios(br) # Sum of ratios > 1.0 br = {"C": {"A": 1.0, "B": 1.0}} - with pytest.raises(ValueError, match="C ratios"): - simple_chain.set_capture_branches(br) + with pytest.raises(ValueError, match=r"Sum of \(n,gamma\).*for C"): + simple_chain.set_branch_ratios(br, "(n,gamma)") + + +def test_set_alpha_branches(): + """Test setting of alpha reaction branching ratios""" + # Build a mock chain + chain = Chain() + + parent = nuclide.Nuclide() + parent.name = "A" + + he4 = nuclide.Nuclide() + he4.name = "He4" + + ground_tgt = nuclide.Nuclide() + ground_tgt.name = "B" + + meta_tgt = nuclide.Nuclide() + meta_tgt.name = "B_m1" + + for ix, nuc in enumerate((parent, ground_tgt, meta_tgt, he4)): + chain.nuclides.append(nuc) + chain.nuclide_dict[nuc.name] = ix + + # add reactions to parent + parent.reactions.append(nuclide.ReactionTuple( + "(n,a)", ground_tgt.name, 1.0, 0.6)) + parent.reactions.append(nuclide.ReactionTuple( + "(n,a)", meta_tgt.name, 1.0, 0.4)) + parent.reactions.append(nuclide.ReactionTuple( + "(n,a)", he4.name, 1.0, 1.0)) + + expected_ref = {"A": {"B": 0.6, "B_m1": 0.4}} + + assert chain.get_branch_ratios("(n,a)") == expected_ref + + # alter and check again + + altered = {"A": {"B": 0.5, "B_m1": 0.5}} + + chain.set_branch_ratios(altered, "(n,a)") + assert chain.get_branch_ratios("(n,a)") == altered + + # make sure that alpha particle still produced + for r in parent.reactions: + if r.target == he4.name: + break + else: + raise ValueError("Helium has been removed and should not have been") + + +def test_simple_fission_yields(simple_chain): + """Check the default fission yields that can be used to form the matrix + """ + fission_yields = simple_chain.get_thermal_fission_yields() + assert fission_yields == {"C": {"A": 0.0292737, "B": 0.002566345}} + + +def test_fission_yield_attribute(simple_chain): + """Test the fission_yields property""" + thermal_yields = simple_chain.get_thermal_fission_yields() + # generate default with property + assert simple_chain.fission_yields[0] == thermal_yields + empty_chain = Chain() + empty_chain.fission_yields = thermal_yields + assert empty_chain.fission_yields[0] == thermal_yields + empty_chain.fission_yields = [thermal_yields] * 2 + assert empty_chain.fission_yields[0] == thermal_yields + assert empty_chain.fission_yields[1] == thermal_yields + + # test failure with deplete function + # number fission yields != number of materials + dummy_conc = [[1, 2]] * (len(empty_chain.fission_yields) + 1) + with pytest.raises( + ValueError, match="fission yield.*not equal.*compositions"): + cram.deplete(empty_chain, dummy_conc, None, 0.5) + +def test_validate(simple_chain): + """Test the validate method""" + + # current chain is invalid + # fission yields do not sum to 2.0 + with pytest.raises(ValueError, match="Nuclide C.*fission yields"): + simple_chain.validate(strict=True, tolerance=0.0) + + with pytest.warns(UserWarning) as record: + assert not simple_chain.validate(strict=False, quiet=False, tolerance=0.0) + assert not simple_chain.validate(strict=False, quiet=True, tolerance=0.0) + assert len(record) == 1 + assert "Nuclide C" in record[0].message.args[0] + + # Fix fission yields but keep to restore later + old_yields = simple_chain["C"].yield_data + simple_chain["C"].yield_data = {0.0253: {"A": 1.4, "B": 0.6}} + + assert simple_chain.validate(strict=True, tolerance=0.0) + with pytest.warns(None) as record: + assert simple_chain.validate(strict=False, quiet=False, tolerance=0.0) + assert len(record) == 0 + + # Mess up "earlier" nuclide's reactions + decay_mode = simple_chain["A"].decay_modes.pop() + + with pytest.raises(ValueError, match="Nuclide A.*decay mode"): + simple_chain.validate(strict=True, tolerance=0.0) + + # restore old fission yields + simple_chain["C"].yield_data = old_yields + + with pytest.warns(UserWarning) as record: + assert not simple_chain.validate(strict=False, quiet=False, tolerance=0.0) + assert len(record) == 2 + assert "Nuclide A" in record[0].message.args[0] + assert "Nuclide C" in record[1].message.args[0] + + # restore decay modes + simple_chain["A"].decay_modes.append(decay_mode) + + +def test_validate_inputs(): + c = Chain() + + with pytest.raises(TypeError, match="tolerance"): + c.validate(tolerance=None) + + with pytest.raises(ValueError, match="tolerance"): + c.validate(tolerance=-1) diff --git a/tests/unit_tests/test_deplete_cram.py b/tests/unit_tests/test_deplete_cram.py index 21b93d17e3..8987fbd7aa 100644 --- a/tests/unit_tests/test_deplete_cram.py +++ b/tests/unit_tests/test_deplete_cram.py @@ -6,7 +6,7 @@ Compares a few Mathematica matrix exponentials to CRAM16/CRAM48. from pytest import approx import numpy as np import scipy.sparse as sp -from openmc.deplete.integrator import CRAM16, CRAM48 +from openmc.deplete.cram import CRAM16, CRAM48 def test_CRAM16(): diff --git a/tests/unit_tests/test_deplete_epc_rk4.py b/tests/unit_tests/test_deplete_epc_rk4.py deleted file mode 100644 index ea687bfd5d..0000000000 --- a/tests/unit_tests/test_deplete_epc_rk4.py +++ /dev/null @@ -1,43 +0,0 @@ -"""Regression tests for openmc.deplete.integrator.epc_rk4 algorithm. - -These tests integrate a simple test problem described in dummy_geometry.py. -""" - -from pytest import approx -import openmc.deplete - -from tests import dummy_operator - - -def test_epc_rk4(run_in_tmpdir): - """Integral regression test of integrator algorithm using epc_rk4""" - - op = dummy_operator.DummyOperator() - op.output_dir = "test_integrator_regression" - - # Perform simulation using the epc_rk4 algorithm - dt = [0.75, 0.75] - power = 1.0 - openmc.deplete.epc_rk4(op, dt, power, print_out=False) - - # Load the files - res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") - - _, y1 = res.get_atoms("1", "1") - _, y2 = res.get_atoms("1", "2") - - # Reference solution - s1 = [2.01978516, 1.42038037] - s2 = [2.05246421, 3.06177191] - - assert y1[1] == approx(s1[0]) - assert y2[1] == approx(s1[1]) - - assert y1[2] == approx(s2[0]) - assert y2[2] == approx(s2[1]) - - # Test structure of depletion time dataset - - dep_time = res.get_depletion_time() - assert dep_time.shape == (len(dt), ) - assert all(dep_time > 0) diff --git a/tests/unit_tests/test_deplete_fission_yields.py b/tests/unit_tests/test_deplete_fission_yields.py new file mode 100644 index 0000000000..bedd702a90 --- /dev/null +++ b/tests/unit_tests/test_deplete_fission_yields.py @@ -0,0 +1,293 @@ +"""Test the FissionYieldHelpers""" + +import os +from collections import namedtuple +from unittest.mock import Mock +import bisect + +import pytest +import numpy as np +import openmc +from openmc import capi +from openmc.deplete.nuclide import Nuclide, FissionYieldDistribution +from openmc.deplete.helpers import ( + FissionYieldCutoffHelper, ConstantFissionYieldHelper, + AveragedFissionYieldHelper) + + +@pytest.fixture(scope="module") +def materials(tmpdir_factory): + """Use C API to construct realistic materials for testing tallies""" + tmpdir = tmpdir_factory.mktemp("capi") + orig = tmpdir.chdir() + # Create proxy problem to please openmc + mfuel = openmc.Material(name="test_fuel") + mfuel.volume = 1.0 + for nuclide in ["U235", "U238", "Xe135", "Pu239"]: + mfuel.add_nuclide(nuclide, 1.0) + openmc.Materials([mfuel]).export_to_xml() + # Geometry + box = openmc.rectangular_prism(1.0, 1.0, boundary_type="reflective") + cell = openmc.Cell(fill=mfuel, region=box) + root = openmc.Universe(cells=[cell]) + openmc.Geometry(root).export_to_xml() + # settings + settings = openmc.Settings() + settings.particles = 100 + settings.inactive = 0 + settings.batches = 10 + settings.verbosity = 1 + settings.export_to_xml() + + try: + with capi.run_in_memory(): + yield [capi.Material(), capi.Material()] + finally: + # Convert to strings as os.remove in py 3.5 doesn't support Paths + for file_path in ("settings.xml", "geometry.xml", "materials.xml", + "summary.h5"): + os.remove(str(tmpdir / file_path)) + orig.chdir() + os.rmdir(str(tmpdir)) + + +def proxy_tally_data(tally, fill=None): + """Construct an empty matrix built from a C tally + + The shape of tally.results will be + ``(n_bins, n_nuc * n_scores, 3)`` + """ + n_nucs = max(len(tally.nuclides), 1) + n_scores = max(len(tally.scores), 1) + n_bins = 1 + for tfilter in tally.filters: + if not hasattr(tfilter, "bins"): + continue + this_bins = len(tfilter.bins) + if isinstance(tfilter, capi.EnergyFilter): + this_bins -= 1 + n_bins *= max(this_bins, 1) + data = np.empty((n_bins, n_nucs * n_scores, 3)) + if fill is not None: + data.fill(fill) + return data + + +@pytest.fixture(scope="module") +def nuclide_bundle(): + u5yield_dict = { + 0.0253: {"Xe135": 7.85e-4, "Gd155": 4.08e-12, "Sm149": 1.71e-12}, + 5.0e5: {"Xe135": 7.85e-4, "Sm149": 1.71e-12}, + 1.40e7: {"Xe135": 4.54e-3, "Gd155": 5.83e-8}} + u235 = Nuclide("U235") + u235.yield_data = FissionYieldDistribution(u5yield_dict) + + u8yield_dict = {5.00e5: {"Xe135": 1.12e-3, "Gd155": 1.32e-12}} + u238 = Nuclide("U238") + u238.yield_data = FissionYieldDistribution(u8yield_dict) + + xe135 = Nuclide("Xe135") + + pu239 = Nuclide("Pu239") + pu239.yield_data = FissionYieldDistribution({ + 5.0e5: {"Xe135": 6.14e-3, "Sm149": 9.429e-10, "Gd155": 5.24e-9}, + 2e6: {"Xe135": 6.15e-3, "Sm149": 9.42e-10, "Gd155": 5.29e-9}}) + + NuclideBundle = namedtuple("NuclideBundle", "u235 u238 xe135 pu239") + return NuclideBundle(u235, u238, xe135, pu239) + + +@pytest.mark.parametrize( + "input_energy, yield_energy", + ((0.0253, 0.0253), (0.01, 0.0253), (4e5, 5e5))) +def test_constant_helper(nuclide_bundle, input_energy, yield_energy): + helper = ConstantFissionYieldHelper(nuclide_bundle, energy=input_energy) + assert helper.energy == input_energy + assert helper.constant_yields == { + "U235": nuclide_bundle.u235.yield_data[yield_energy], + "U238": nuclide_bundle.u238.yield_data[5.00e5], + "Pu239": nuclide_bundle.pu239.yield_data[5e5]} + assert helper.constant_yields == helper.weighted_yields(1) + + +def test_cutoff_construction(nuclide_bundle): + u235 = nuclide_bundle.u235 + u238 = nuclide_bundle.u238 + pu239 = nuclide_bundle.pu239 + + # defaults + helper = FissionYieldCutoffHelper(nuclide_bundle, 1) + assert helper.constant_yields == { + "U238": u238.yield_data[5.0e5], + "Pu239": pu239.yield_data[5e5]} + assert helper.thermal_yields == {"U235": u235.yield_data[0.0253]} + assert helper.fast_yields == {"U235": u235.yield_data[5e5]} + + # use 14 MeV yields + helper = FissionYieldCutoffHelper(nuclide_bundle, 1, fast_energy=14e6) + assert helper.constant_yields == { + "U238": u238.yield_data[5.0e5], + "Pu239": pu239.yield_data[5e5]} + assert helper.thermal_yields == {"U235": u235.yield_data[0.0253]} + assert helper.fast_yields == {"U235": u235.yield_data[14e6]} + + # specify missing thermal yields -> use 0.0253 + helper = FissionYieldCutoffHelper(nuclide_bundle, 1, thermal_energy=1) + assert helper.thermal_yields == {"U235": u235.yield_data[0.0253]} + assert helper.fast_yields == {"U235": u235.yield_data[5e5]} + + # request missing fast yields -> use epithermal + helper = FissionYieldCutoffHelper(nuclide_bundle, 1, fast_energy=1e4) + assert helper.thermal_yields == {"U235": u235.yield_data[0.0253]} + assert helper.fast_yields == {"U235": u235.yield_data[5e5]} + + # higher cutoff energy -> obtain fast and "faster" yields + helper = FissionYieldCutoffHelper(nuclide_bundle, 1, cutoff=1e6, + thermal_energy=5e5, fast_energy=14e6) + assert helper.constant_yields == {"U238": u238.yield_data[5e5]} + assert helper.thermal_yields == { + "U235": u235.yield_data[5e5], "Pu239": pu239.yield_data[5e5]} + assert helper.fast_yields == { + "U235": u235.yield_data[14e6], "Pu239": pu239.yield_data[2e6]} + + # test super low and super high cutoff energies + helper = FissionYieldCutoffHelper( + nuclide_bundle, 1, thermal_energy=0.001, cutoff=0.002) + assert helper.fast_yields == {} + assert helper.thermal_yields == {} + assert helper.constant_yields == { + "U235": u235.yield_data[0.0253], "U238": u238.yield_data[5e5], + "Pu239": pu239.yield_data[5e5]} + + helper = FissionYieldCutoffHelper( + nuclide_bundle, 1, cutoff=15e6, fast_energy=17e6) + assert helper.thermal_yields == {} + assert helper.fast_yields == {} + assert helper.constant_yields == { + "U235": u235.yield_data[14e6], "U238": u238.yield_data[5e5], + "Pu239": pu239.yield_data[2e6]} + + +@pytest.mark.parametrize("key", ("cutoff", "thermal_energy", "fast_energy")) +def test_cutoff_failure(key): + with pytest.raises(TypeError, match=key): + FissionYieldCutoffHelper(None, None, **{key: None}) + with pytest.raises(ValueError, match=key): + FissionYieldCutoffHelper(None, None, **{key: -1}) + + +# emulate some split between fast and thermal U235 fissions +@pytest.mark.parametrize("therm_frac", (0.5, 0.2, 0.8)) +def test_cutoff_helper(materials, nuclide_bundle, therm_frac): + helper = FissionYieldCutoffHelper(nuclide_bundle, len(materials), + cutoff=1e6, fast_energy=14e6) + helper.generate_tallies(materials, [0]) + + non_zero_nucs = [n.name for n in nuclide_bundle] + tally_nucs = helper.update_tally_nuclides(non_zero_nucs) + assert tally_nucs == ["Pu239", "U235"] + + # Check tallies + fission_tally = helper._fission_rate_tally + assert fission_tally is not None + filters = fission_tally.filters + assert len(filters) == 2 + assert isinstance(filters[0], capi.MaterialFilter) + assert len(filters[0].bins) == len(materials) + assert isinstance(filters[1], capi.EnergyFilter) + # lower, cutoff, and upper energy + assert len(filters[1].bins) == 3 + + # Emulate building tallies + # material x energy, tallied_nuclides, 3 + tally_data = proxy_tally_data(fission_tally) + helper._fission_rate_tally = Mock() + helper_flux = 1e6 + tally_data[0, :, 1] = therm_frac * helper_flux + tally_data[1, :, 1] = (1 - therm_frac) * helper_flux + helper._fission_rate_tally.results = tally_data + + helper.unpack() + # expected results of shape (n_mats, 2, n_tnucs) + expected_results = np.empty((1, 2, len(tally_nucs))) + expected_results[:, 0] = therm_frac + expected_results[:, 1] = 1 - therm_frac + assert helper.results == pytest.approx(expected_results) + + actual_yields = helper.weighted_yields(0) + assert actual_yields["U238"] == nuclide_bundle.u238.yield_data[5e5] + for nuc in tally_nucs: + assert actual_yields[nuc] == ( + helper.thermal_yields[nuc] * therm_frac + + helper.fast_yields[nuc] * (1 - therm_frac)) + + +@pytest.mark.parametrize("avg_energy", (0.01, 6e5, 15e6)) +def test_averaged_helper(materials, nuclide_bundle, avg_energy): + helper = AveragedFissionYieldHelper(nuclide_bundle) + helper.generate_tallies(materials, [0]) + tallied_nucs = helper.update_tally_nuclides( + [n.name for n in nuclide_bundle]) + assert tallied_nucs == ["Pu239", "U235"] + + # check generated tallies + fission_tally = helper._fission_rate_tally + assert fission_tally is not None + fission_filters = fission_tally.filters + assert len(fission_filters) == 2 + assert isinstance(fission_filters[0], capi.MaterialFilter) + assert len(fission_filters[0].bins) == len(materials) + assert isinstance(fission_filters[1], capi.EnergyFilter) + assert len(fission_filters[1].bins) == 2 + assert fission_tally.scores == ["fission"] + assert fission_tally.nuclides == list(tallied_nucs) + + weighted_tally = helper._weighted_tally + assert weighted_tally is not None + weighted_filters = weighted_tally.filters + assert len(weighted_filters) == 2 + assert isinstance(weighted_filters[0], capi.MaterialFilter) + assert len(weighted_filters[0].bins) == len(materials) + assert isinstance(weighted_filters[1], capi.EnergyFunctionFilter) + assert len(weighted_filters[1].energy) == 2 + assert len(weighted_filters[1].y) == 2 + assert weighted_tally.scores == ["fission"] + assert weighted_tally.nuclides == list(tallied_nucs) + + helper_flux = 1e16 + fission_results = proxy_tally_data(fission_tally, helper_flux) + weighted_results = proxy_tally_data( + weighted_tally, helper_flux * avg_energy) + + helper._fission_rate_tally = Mock() + helper._weighted_tally = Mock() + helper._fission_rate_tally.results = fission_results + helper._weighted_tally.results = weighted_results + + helper.unpack() + expected_results = np.ones((1, len(tallied_nucs))) * avg_energy + assert helper.results == pytest.approx(expected_results) + + actual_yields = helper.weighted_yields(0) + # constant U238 => no interpolation + assert actual_yields["U238"] == nuclide_bundle.u238.yield_data[5e5] + # construct expected yields + exp_u235_yields = interp_average_yields(nuclide_bundle.u235, avg_energy) + assert actual_yields["U235"] == exp_u235_yields + exp_pu239_yields = interp_average_yields(nuclide_bundle.pu239, avg_energy) + assert actual_yields["Pu239"] == exp_pu239_yields + + +def interp_average_yields(nuc, avg_energy): + """Construct a set of yields by interpolation between neighbors""" + energies = nuc.yield_energies + yields = nuc.yield_data + if avg_energy < energies[0]: + return yields[energies[0]] + if avg_energy > energies[-1]: + return yields[energies[-1]] + thermal_ix = bisect.bisect_left(energies, avg_energy) + thermal_E, fast_E = energies[thermal_ix - 1:thermal_ix + 1] + assert thermal_E < avg_energy < fast_E + split = (avg_energy - thermal_E)/(fast_E - thermal_E) + return yields[thermal_E]*(1 - split) + yields[fast_E]*split diff --git a/tests/unit_tests/test_deplete_integrator.py b/tests/unit_tests/test_deplete_integrator.py index 40090264ae..47c9690ae9 100644 --- a/tests/unit_tests/test_deplete_integrator.py +++ b/tests/unit_tests/test_deplete_integrator.py @@ -7,12 +7,17 @@ will be left unimplemented and testing will be done via regression. """ import copy -import os from unittest.mock import MagicMock import numpy as np -from openmc.deplete import (ReactionRates, Results, ResultsList, comm, - OperatorResult) +from uncertainties import ufloat +import pytest + +from openmc.deplete import ( + ReactionRates, Results, ResultsList, comm, OperatorResult, + PredictorIntegrator, SICELIIntegrator) + +from tests import dummy_operator def test_results_save(run_in_tmpdir): @@ -37,10 +42,11 @@ def test_results_save(run_in_tmpdir): full_burn_list.append(str(2*i)) full_burn_list.append(str(2*i + 1)) - burn_list = full_burn_list[2*comm.rank : 2*comm.rank + 2] + burn_list = full_burn_list[2*comm.rank: 2*comm.rank + 2] nuc_list = ["na", "nb"] - op.get_results_info.return_value = vol_dict, nuc_list, burn_list, full_burn_list + op.get_results_info.return_value = ( + vol_dict, nuc_list, burn_list, full_burn_list) # Construct x x1 = [] @@ -74,13 +80,15 @@ def test_results_save(run_in_tmpdir): t1 = [0.0, 1.0] t2 = [1.0, 2.0] - op_result1 = [OperatorResult(k, rates) for k, rates in zip(eigvl1, rate1)] - op_result2 = [OperatorResult(k, rates) for k, rates in zip(eigvl2, rate2)] + op_result1 = [OperatorResult(ufloat(*k), rates) + for k, rates in zip(eigvl1, rate1)] + op_result2 = [OperatorResult(ufloat(*k), rates) + for k, rates in zip(eigvl2, rate2)] Results.save(op, x1, op_result1, t1, 0, 0) Results.save(op, x2, op_result2, t2, 0, 1) # Load the files - res = ResultsList("depletion_results.h5") + res = ResultsList.from_hdf5("depletion_results.h5") for i in range(stages): for mat_i, mat in enumerate(burn_list): @@ -95,3 +103,59 @@ def test_results_save(run_in_tmpdir): np.testing.assert_array_equal(res[1].k, eigvl2) np.testing.assert_array_equal(res[1].time, t2) + + +@pytest.mark.parametrize("timesteps", (1, [1])) +def test_bad_integrator_inputs(timesteps): + """Test failure modes for Integrator inputs""" + + op = MagicMock() + op.prev_res = None + op.chain = None + op.heavy_metal = 1.0 + + # No power nor power density given + with pytest.raises(ValueError, match="Either power or power density"): + PredictorIntegrator(op, timesteps) + + # Length of power != length time + with pytest.raises(ValueError, match="number of powers"): + PredictorIntegrator(op, timesteps, power=[1, 2]) + + # Length of power density != length time + with pytest.raises(ValueError, match="number of powers"): + PredictorIntegrator(op, timesteps, power_density=[1, 2]) + + # SI integrator with bad steps + with pytest.raises(TypeError, match="n_steps"): + SICELIIntegrator(op, timesteps, [1], n_steps=2.5) + + with pytest.raises(ValueError, match="n_steps"): + SICELIIntegrator(op, timesteps, [1], n_steps=0) + + +@pytest.mark.parametrize("scheme", dummy_operator.SCHEMES) +def test_integrator(run_in_tmpdir, scheme): + """Test the integrators against their expected values""" + + bundle = dummy_operator.SCHEMES[scheme] + operator = dummy_operator.DummyOperator() + bundle.solver(operator, [0.75, 0.75], 1.0).integrate() + + # get expected results + + res = ResultsList.from_hdf5( + operator.output_dir / "depletion_results.h5") + + t1, y1 = res.get_atoms("1", "1") + t2, y2 = res.get_atoms("1", "2") + + assert (t1 == [0.0, 0.75, 1.5]).all() + assert y1 == pytest.approx(bundle.atoms_1) + assert (t2 == [0.0, 0.75, 1.5]).all() + assert y2 == pytest.approx(bundle.atoms_2) + + # test structure of depletion time dataset + dep_time = res.get_depletion_time() + assert dep_time.shape == (2, ) + assert all(dep_time > 0) diff --git a/tests/unit_tests/test_deplete_leqi.py b/tests/unit_tests/test_deplete_leqi.py deleted file mode 100644 index a26329cf4a..0000000000 --- a/tests/unit_tests/test_deplete_leqi.py +++ /dev/null @@ -1,43 +0,0 @@ -"""Regression tests for openmc.deplete.integrator.leqi algorithm. - -These tests integrate a simple test problem described in dummy_geometry.py. -""" - -from pytest import approx -import openmc.deplete - -from tests import dummy_operator - - -def test_leqi(run_in_tmpdir): - """Integral regression test of integrator algorithm using leqi""" - - op = dummy_operator.DummyOperator() - op.output_dir = "test_integrator_regression" - - # Perform simulation using the leqi algorithm - dt = [0.75, 0.75] - power = 1.0 - openmc.deplete.leqi(op, dt, power, print_out=False) - - # Load the files - res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") - - _, y1 = res.get_atoms("1", "1") - _, y2 = res.get_atoms("1", "2") - - # Reference solution - s1 = [1.82078767, 0.97122898] - s2 = [2.74526197, 0.23339915] - - assert y1[1] == approx(s1[0]) - assert y2[1] == approx(s1[1]) - - assert y1[2] == approx(s2[0]) - assert y2[2] == approx(s2[1]) - - # Test structure of depletion time dataset - - dep_time = res.get_depletion_time() - assert dep_time.shape == (len(dt), ) - assert all(dep_time > 0) diff --git a/tests/unit_tests/test_deplete_nuclide.py b/tests/unit_tests/test_deplete_nuclide.py index f2a101d2a9..7d68b3a3d8 100644 --- a/tests/unit_tests/test_deplete_nuclide.py +++ b/tests/unit_tests/test_deplete_nuclide.py @@ -2,6 +2,8 @@ import xml.etree.ElementTree as ET +import numpy +import pytest from openmc.deplete import nuclide @@ -69,18 +71,20 @@ def test_from_xml(): nuclide.ReactionTuple('(n,gamma)', 'U236', 6545200.0, 1.0), nuclide.ReactionTuple('fission', None, 193405400.0, 1.0), ] - assert u235.yield_energies == [0.0253] - assert u235.yield_data == { - 0.0253: [('Te134', 0.062155), ('Zr100', 0.0497641), - ('Xe138', 0.0481413)] - } + expected_yield_data = nuclide.FissionYieldDistribution({ + 0.0253: {"Xe138": 0.0481413, "Zr100": 0.0497641, "Te134": 0.062155}}) + assert u235.yield_data == expected_yield_data + # test accessing the yield energies through the FissionYieldDistribution + assert u235.yield_energies == (0.0253,) + assert u235.yield_energies is u235.yield_data.energies + with pytest.raises(AttributeError): # not settable + u235.yield_energies = [0.0253, 5e5] def test_to_xml_element(): """Test writing nuclide data to an XML element.""" - C = nuclide.Nuclide() - C.name = "C" + C = nuclide.Nuclide("C") C.half_life = 0.123 C.decay_modes = [ nuclide.DecayTuple('beta-', 'B', 0.99), @@ -90,8 +94,8 @@ def test_to_xml_element(): nuclide.ReactionTuple('fission', None, 2.0e8, 1.0), nuclide.ReactionTuple('(n,gamma)', 'A', 0.0, 1.0) ] - C.yield_energies = [0.0253] - C.yield_data = {0.0253: [("A", 0.0292737), ("B", 0.002566345)]} + C.yield_data = nuclide.FissionYieldDistribution( + {0.0253: {"A": 0.0292737, "B": 0.002566345}}) element = C.to_xml_element() assert element.get("half_life") == "0.123" @@ -114,3 +118,143 @@ def test_to_xml_element(): assert float(rx_elems[1].get("Q")) == 0.0 assert element.find('neutron_fission_yields') is not None + + +def test_fission_yield_distribution(): + """Test an energy-dependent yield distribution""" + yield_dict = { + 0.0253: {"Xe135": 7.85e-4, "Gd155": 4.08e-12, "Sm149": 1.71e-12}, + 1.40e7: {"Xe135": 4.54e-3, "Gd155": 5.83e-8, "Sm149": 2.69e-8}, + 5.00e5: {"Xe135": 1.12e-3, "Gd155": 1.32e-12}, # drop Sm149 + } + yield_dist = nuclide.FissionYieldDistribution(yield_dict) + assert len(yield_dist) == len(yield_dict) + assert yield_dist.energies == tuple(sorted(yield_dict.keys())) + for exp_ene, exp_dist in yield_dict.items(): + act_dist = yield_dict[exp_ene] + for exp_prod, exp_yield in exp_dist.items(): + assert act_dist[exp_prod] == exp_yield + exp_yield = numpy.array([ + [4.08e-12, 1.71e-12, 7.85e-4], + [1.32e-12, 0.0, 1.12e-3], + [5.83e-8, 2.69e-8, 4.54e-3]]) + assert numpy.array_equal(yield_dist.yield_matrix, exp_yield) + + # Test the operations / special methods for fission yield + orig_yields = yield_dist[0.0253] + assert len(orig_yields) == len(yield_dict[0.0253]) + for key, value in yield_dict[0.0253].items(): + assert key in orig_yields + assert orig_yields[key] == value + # __getitem__ return yields as a view into yield matrix + assert orig_yields.yields.base is yield_dist.yield_matrix + + # Fission yield feature uses scaled and incremented + mod_yields = orig_yields * 2 + assert numpy.array_equal(orig_yields.yields * 2, mod_yields.yields) + mod_yields += orig_yields + assert numpy.array_equal(orig_yields.yields * 3, mod_yields.yields) + + # Failure modes for adding, multiplying yields + similar = numpy.empty_like(orig_yields.yields) + with pytest.raises(TypeError): + orig_yields + similar + with pytest.raises(TypeError): + similar + orig_yields + with pytest.raises(TypeError): + orig_yields += similar + with pytest.raises(TypeError): + orig_yields * similar + with pytest.raises(TypeError): + similar * orig_yields + with pytest.raises(TypeError): + orig_yields *= similar + + +def test_validate(): + + nuc = nuclide.Nuclide() + nuc.name = "Test" + + # decay modes: type, target, branching_ratio + + nuc.decay_modes = [ + nuclide.DecayTuple("type 0", "0", 0.5), + nuclide.DecayTuple("type 1", "1", 0.5), + ] + + # reactions: type, target, Q, branching_ratio + nuc.reactions = [ + nuclide.ReactionTuple("0", "0", 1000, 0.3), + nuclide.ReactionTuple("0", "1", 1000, 0.3), + nuclide.ReactionTuple("1", "2", 1000, 1.0), + nuclide.ReactionTuple("0", "3", 1000, 0.4), + ] + + # fission yields + + nuc.yield_data = { + 0.0253: {"0": 1.5, "1": 0.5}, + 1e6: {"0": 1.5, "1": 0.5}, + } + + # nuclide is good and should have no warnings raise + with pytest.warns(None) as record: + assert nuc.validate(strict=True, quiet=False, tolerance=0.0) + assert len(record) == 0 + + # invalidate decay modes + decay = nuc.decay_modes.pop() + with pytest.raises(ValueError, match="decay mode"): + nuc.validate(strict=True, quiet=False, tolerance=0.0) + + with pytest.warns(UserWarning) as record: + assert not nuc.validate(strict=False, quiet=False, tolerance=0.0) + assert not nuc.validate(strict=False, quiet=True, tolerance=0.0) + assert len(record) == 1 + assert "decay mode" in record[0].message.args[0] + + # restore decay modes, invalidate reactions + nuc.decay_modes.append(decay) + reaction = nuc.reactions.pop() + + with pytest.raises(ValueError, match="0 reaction"): + nuc.validate(strict=True, quiet=False, tolerance=0.0) + + with pytest.warns(UserWarning) as record: + assert not nuc.validate(strict=False, quiet=False, tolerance=0.0) + assert not nuc.validate(strict=False, quiet=True, tolerance=0.0) + assert len(record) == 1 + assert "0 reaction" in record[0].message.args[0] + + # restore reactions, invalidate fission yields + nuc.reactions.append(reaction) + nuc.yield_data[1e6].yields *= 2 + + with pytest.raises(ValueError, match=r"fission yields.*1\.0*e"): + nuc.validate(strict=True, quiet=False, tolerance=0.0) + + with pytest.warns(UserWarning) as record: + assert not nuc.validate(strict=False, quiet=False, tolerance=0.0) + assert not nuc.validate(strict=False, quiet=True, tolerance=0.0) + assert len(record) == 1 + assert "1.0" in record[0].message.args[0] + + # invalidate everything, check that error is raised at decay modes + + decay = nuc.decay_modes.pop() + reaction = nuc.reactions.pop() + + with pytest.raises(ValueError, match="decay mode"): + nuc.validate(strict=True, quiet=False, tolerance=0.0) + + # check for warnings + # should be one warning for decay modes, reactions, fission yields + + with pytest.warns(UserWarning) as record: + assert not nuc.validate(strict=False, quiet=False, tolerance=0.0) + assert not nuc.validate(strict=False, quiet=True, tolerance=0.0) + assert len(record) == 3 + assert "decay mode" in record[0].message.args[0] + assert "0 reaction" in record[1].message.args[0] + assert "1.0" in record[2].message.args[0] diff --git a/tests/unit_tests/test_deplete_operator.py b/tests/unit_tests/test_deplete_operator.py index 45c2aada4e..3574d293dc 100644 --- a/tests/unit_tests/test_deplete_operator.py +++ b/tests/unit_tests/test_deplete_operator.py @@ -37,14 +37,20 @@ def bare_xs(run_in_tmpdir): class BareDepleteOperator(TransportOperator): """Very basic class for testing the initialization.""" - # declare abstract methods so object can be created - def __call__(self, *args, **kwargs): + @staticmethod + def __call__(*args, **kwargs): pass - def initial_condition(self): + @staticmethod + def initial_condition(): pass - def get_results_info(self): + @staticmethod + def get_results_info(): + pass + + @staticmethod + def write_bos_data(): pass diff --git a/tests/unit_tests/test_deplete_predictor.py b/tests/unit_tests/test_deplete_predictor.py deleted file mode 100644 index 6af6f8bca4..0000000000 --- a/tests/unit_tests/test_deplete_predictor.py +++ /dev/null @@ -1,43 +0,0 @@ -"""Regression tests for openmc.deplete.integrator.predictor algorithm. - -These tests integrate a simple test problem described in dummy_geometry.py. -""" - -from pytest import approx -import openmc.deplete - -from tests import dummy_operator - - -def test_predictor(run_in_tmpdir): - """Integral regression test of integrator algorithm using predictor""" - - op = dummy_operator.DummyOperator() - op.output_dir = "test_integrator_regression" - - # Perform simulation using the predictor algorithm - dt = [0.75, 0.75] - power = 1.0 - openmc.deplete.predictor(op, dt, power, print_out=False) - - # Load the files - res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") - - _, y1 = res.get_atoms("1", "1") - _, y2 = res.get_atoms("1", "2") - - # Mathematica solution - s1 = [2.46847546272295, 0.986431226850467] - s2 = [4.11525874568034, -0.0581692232513460] - - assert y1[1] == approx(s1[0]) - assert y2[1] == approx(s1[1]) - - assert y1[2] == approx(s2[0]) - assert y2[2] == approx(s2[1]) - - # Test structure of depletion time dataset - - dep_time = res.get_depletion_time() - assert dep_time.shape == (len(dt), ) - assert all(dep_time > 0) diff --git a/tests/unit_tests/test_deplete_restart.py b/tests/unit_tests/test_deplete_restart.py index ec62064a4e..82ef05ac7b 100644 --- a/tests/unit_tests/test_deplete_restart.py +++ b/tests/unit_tests/test_deplete_restart.py @@ -4,93 +4,15 @@ These tests run in two steps, a first run then a restart run, a simple test problem described in dummy_geometry.py. """ -from pytest import approx +import pytest + import openmc.deplete from tests import dummy_operator -def test_restart_predictor(run_in_tmpdir): - """Integral regression test of integrator algorithm using predictor.""" - - op = dummy_operator.DummyOperator() - output_dir = "test_restart_predictor" - op.output_dir = output_dir - - # Perform simulation using the predictor algorithm - dt = [0.75] - power = 1.0 - openmc.deplete.predictor(op, dt, power, print_out=False) - - # Load the files - prev_res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") - - # Re-create depletion operator and load previous results - op = dummy_operator.DummyOperator(prev_res) - op.output_dir = output_dir - - # Perform restarts simulation using the predictor algorithm - openmc.deplete.predictor(op, dt, power, print_out=False) - - # Load the files - res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") - - _, y1 = res.get_atoms("1", "1") - _, y2 = res.get_atoms("1", "2") - - # Mathematica solution - s1 = [2.46847546272295, 0.986431226850467] - s2 = [4.11525874568034, -0.0581692232513460] - - assert y1[1] == approx(s1[0]) - assert y2[1] == approx(s1[1]) - - assert y1[2] == approx(s2[0]) - assert y2[2] == approx(s2[1]) - - -def test_restart_cecm(run_in_tmpdir): - """Integral regression test of integrator algorithm using CE/CM.""" - - op = dummy_operator.DummyOperator() - output_dir = "test_restart_cecm" - op.output_dir = output_dir - - # Perform simulation using the MCNPX/MCNP6 algorithm - dt = [0.75] - power = 1.0 - openmc.deplete.cecm(op, dt, power, print_out=False) - - # Load the files - prev_res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") - - # Re-create depletion operator and load previous results - op = dummy_operator.DummyOperator(prev_res) - op.output_dir = output_dir - - # Perform restarts simulation using the MCNPX/MCNP6 algorithm - openmc.deplete.cecm(op, dt, power, print_out=False) - - # Load the files - res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") - - _, y1 = res.get_atoms("1", "1") - _, y2 = res.get_atoms("1", "2") - - # Mathematica solution - s1 = [1.86872629872102, 1.395525772416039] - s2 = [2.18097439443550, 2.69429754646747] - - assert y1[1] == approx(s1[0]) - assert y2[1] == approx(s1[1]) - - assert y1[3] == approx(s2[0]) - assert y2[3] == approx(s2[1]) - - def test_restart_predictor_cecm(run_in_tmpdir): - """Integral regression test of integrator algorithm using predictor - for the first run then CE/CM for the restart run.""" + """Test to ensure that schemes with different stages are not compatible""" op = dummy_operator.DummyOperator() output_dir = "test_restart_predictor_cecm" @@ -99,33 +21,19 @@ def test_restart_predictor_cecm(run_in_tmpdir): # Perform simulation using the predictor algorithm dt = [0.75] power = 1.0 - openmc.deplete.predictor(op, dt, power, print_out=False) + openmc.deplete.PredictorIntegrator(op, dt, power).integrate() # Load the files - prev_res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") + prev_res = openmc.deplete.ResultsList.from_hdf5( + op.output_dir / "depletion_results.h5") # Re-create depletion operator and load previous results op = dummy_operator.DummyOperator(prev_res) op.output_dir = output_dir - # Perform restarts simulation using the MCNPX/MCNP6 algorithm - openmc.deplete.cecm(op, dt, power, print_out=False) - - # Load the files - res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") - - _, y1 = res.get_atoms("1", "1") - _, y2 = res.get_atoms("1", "2") - - # Test solution - s1 = [2.46847546272295, 0.986431226850467] - s2 = [3.09106948392, 0.607102912398] - - assert y1[1] == approx(s1[0]) - assert y2[1] == approx(s1[1]) - - assert y1[2] == approx(s2[0]) - assert y2[2] == approx(s2[1]) + # check ValueError is raised, indicating previous and current stages + with pytest.raises(ValueError, match="incompatible.* 1.*2"): + openmc.deplete.CECMIntegrator(op, dt, power) def test_restart_cecm_predictor(run_in_tmpdir): @@ -139,263 +47,48 @@ def test_restart_cecm_predictor(run_in_tmpdir): # Perform simulation using the MCNPX/MCNP6 algorithm dt = [0.75] power = 1.0 - openmc.deplete.cecm(op, dt, power, print_out=False) + cecm = openmc.deplete.CECMIntegrator(op, dt, power) + cecm.integrate() # Load the files - prev_res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") + prev_res = openmc.deplete.ResultsList.from_hdf5( + op.output_dir / "depletion_results.h5") # Re-create depletion operator and load previous results op = dummy_operator.DummyOperator(prev_res) op.output_dir = output_dir - # Perform restarts simulation using the predictor algorithm - openmc.deplete.predictor(op, dt, power, print_out=False) + # check ValueError is raised, indicating previous and current stages + with pytest.raises(ValueError, match="incompatible.* 2.*1"): + openmc.deplete.PredictorIntegrator(op, dt, power) - # Load the files - res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") - _, y1 = res.get_atoms("1", "1") - _, y2 = res.get_atoms("1", "2") +@pytest.mark.parametrize("scheme", dummy_operator.SCHEMES) +def test_restart(run_in_tmpdir, scheme): + # set up the problem - # Test solution - s1 = [1.86872629872102, 1.395525772416039] - s2 = [3.32776806576, 2.391425905] + bundle = dummy_operator.SCHEMES[scheme] - assert y1[1] == approx(s1[0]) - assert y2[1] == approx(s1[1]) + operator = dummy_operator.DummyOperator() - assert y1[2] == approx(s2[0]) - assert y2[2] == approx(s2[1]) + # take first step + bundle.solver(operator, [0.75], 1.0).integrate() + # restart + prev_res = openmc.deplete.ResultsList.from_hdf5( + operator.output_dir / "depletion_results.h5") + operator = dummy_operator.DummyOperator(prev_res) -def test_restart_cf4(run_in_tmpdir): - """Integral regression test of integrator algorithm using CF4.""" + # take second step + bundle.solver(operator, [0.75], 1.0).integrate() - op = dummy_operator.DummyOperator() - output_dir = "test_restart_cf4" - op.output_dir = output_dir + # compare results - # Perform simulation - dt = [0.75] - power = 1.0 - openmc.deplete.cf4(op, dt, power, print_out=False) + results = openmc.deplete.ResultsList.from_hdf5( + operator.output_dir / "depletion_results.h5") - # Load the files - prev_res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") + _t, y1 = results.get_atoms("1", "1") + _t, y2 = results.get_atoms("1", "2") - # Re-create depletion operator and load previous results - op = dummy_operator.DummyOperator(prev_res) - op.output_dir = output_dir - - # Perform restarts simulation - openmc.deplete.cf4(op, dt, power, print_out=False) - - # Load the files - res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") - - _, y1 = res.get_atoms("1", "1") - _, y2 = res.get_atoms("1", "2") - - # Reference solution - s1 = [2.06101629, 1.37783588] - s2 = [2.57241318, 2.63731630] - - assert y1[1] == approx(s1[0]) - assert y2[1] == approx(s1[1]) - - assert y1[3] == approx(s2[0]) - assert y2[3] == approx(s2[1]) - - -def test_restart_epc_rk4(run_in_tmpdir): - """Integral regression test of integrator algorithm using EPC-RK4.""" - - op = dummy_operator.DummyOperator() - output_dir = "test_restart_epc_rk4" - op.output_dir = output_dir - - # Perform simulation - dt = [0.75] - power = 1.0 - openmc.deplete.epc_rk4(op, dt, power, print_out=False) - - # Load the files - prev_res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") - - # Re-create depletion operator and load previous results - op = dummy_operator.DummyOperator(prev_res) - op.output_dir = output_dir - - # Perform restarts simulation - openmc.deplete.epc_rk4(op, dt, power, print_out=False) - - # Load the files - res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") - - _, y1 = res.get_atoms("1", "1") - _, y2 = res.get_atoms("1", "2") - - # Reference solution - s1 = [2.01978516, 1.42038037] - s2 = [2.05246421, 3.06177191] - - assert y1[1] == approx(s1[0]) - assert y2[1] == approx(s1[1]) - - assert y1[3] == approx(s2[0]) - assert y2[3] == approx(s2[1]) - - -def test_restart_celi(run_in_tmpdir): - """Integral regression test of integrator algorithm using CELI.""" - - op = dummy_operator.DummyOperator() - output_dir = "test_restart_celi" - op.output_dir = output_dir - - # Perform simulation - dt = [0.75] - power = 1.0 - openmc.deplete.celi(op, dt, power, print_out=False) - - # Load the files - prev_res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") - - # Re-create depletion operator and load previous results - op = dummy_operator.DummyOperator(prev_res) - op.output_dir = output_dir - - # Perform restarts simulation - openmc.deplete.celi(op, dt, power, print_out=False) - - # Load the files - res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") - - _, y1 = res.get_atoms("1", "1") - _, y2 = res.get_atoms("1", "2") - - # Reference solution - s1 = [1.82078767, 0.97122898] - s2 = [2.68441779, 0.05125966] - - assert y1[1] == approx(s1[0]) - assert y2[1] == approx(s1[1]) - - assert y1[3] == approx(s2[0]) - assert y2[3] == approx(s2[1]) - - -def test_restart_leqi(run_in_tmpdir): - """Integral regression test of integrator algorithm using LEQI.""" - - op = dummy_operator.DummyOperator() - output_dir = "test_restart_leqi" - op.output_dir = output_dir - - # Perform simulation - dt = [0.75] - power = 1.0 - openmc.deplete.leqi(op, dt, power, print_out=False) - - # Load the files - prev_res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") - - # Re-create depletion operator and load previous results - op = dummy_operator.DummyOperator(prev_res) - op.output_dir = output_dir - - # Perform restarts simulation - openmc.deplete.leqi(op, dt, power, print_out=False) - - # Load the files - res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") - - _, y1 = res.get_atoms("1", "1") - _, y2 = res.get_atoms("1", "2") - - # Reference solution - s1 = [1.82078767, 0.97122898] - s2 = [2.74526197, 0.23339915] - - assert y1[1] == approx(s1[0]) - assert y2[1] == approx(s1[1]) - - assert y1[3] == approx(s2[0]) - assert y2[3] == approx(s2[1]) - -def test_restart_si_celi(run_in_tmpdir): - """Integral regression test of integrator algorithm using SI-CELI.""" - - op = dummy_operator.DummyOperator() - output_dir = "test_restart_si_celi" - op.output_dir = output_dir - - # Perform simulation - dt = [0.75] - power = 1.0 - openmc.deplete.si_celi(op, dt, power, print_out=False) - - # Load the files - prev_res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") - - # Re-create depletion operator and load previous results - op = dummy_operator.DummyOperator(prev_res) - op.output_dir = output_dir - - # Perform restarts simulation - openmc.deplete.si_celi(op, dt, power, print_out=False) - - # Load the files - res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") - - _, y1 = res.get_atoms("1", "1") - _, y2 = res.get_atoms("1", "2") - - # Reference solution - s1 = [2.03325094, 1.16826254] - s2 = [2.69291933, 0.37907772] - - assert y1[1] == approx(s1[0]) - assert y2[1] == approx(s1[1]) - - assert y1[3] == approx(s2[0]) - assert y2[3] == approx(s2[1]) - - -def test_restart_si_leqi(run_in_tmpdir): - """Integral regression test of integrator algorithm using SI-LEQI.""" - - op = dummy_operator.DummyOperator() - output_dir = "test_restart_si_leqi" - op.output_dir = output_dir - - # Perform simulation - dt = [0.75] - power = 1.0 - openmc.deplete.si_leqi(op, dt, power, print_out=False) - - # Load the files - prev_res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") - - # Re-create depletion operator and load previous results - op = dummy_operator.DummyOperator(prev_res) - op.output_dir = output_dir - - # Perform restarts simulation - openmc.deplete.si_leqi(op, dt, power, print_out=False) - - # Load the files - res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") - - _, y1 = res.get_atoms("1", "1") - _, y2 = res.get_atoms("1", "2") - - # Reference solution - s1 = [2.03325094, 1.16826254] - s2 = [2.92711288, 0.53753236] - - assert y1[1] == approx(s1[0]) - assert y2[1] == approx(s1[1]) - - assert y1[3] == approx(s2[0]) - assert y2[3] == approx(s2[1]) + assert y1 == pytest.approx(bundle.atoms_1) + assert y2 == pytest.approx(bundle.atoms_2) diff --git a/tests/unit_tests/test_deplete_resultslist.py b/tests/unit_tests/test_deplete_resultslist.py index c4b7aceba5..c3ceda5d56 100644 --- a/tests/unit_tests/test_deplete_resultslist.py +++ b/tests/unit_tests/test_deplete_resultslist.py @@ -12,7 +12,7 @@ def res(): """Load the reference results""" filename = (Path(__file__).parents[1] / 'regression_tests' / 'deplete' / 'test_reference.h5') - return openmc.deplete.ResultsList(filename) + return openmc.deplete.ResultsList.from_hdf5(filename) def test_get_atoms(res): diff --git a/tests/unit_tests/test_deplete_si_celi.py b/tests/unit_tests/test_deplete_si_celi.py deleted file mode 100644 index 71679439c5..0000000000 --- a/tests/unit_tests/test_deplete_si_celi.py +++ /dev/null @@ -1,43 +0,0 @@ -"""Regression tests for openmc.deplete.integrator.si_celi algorithm. - -These tests integrate a simple test problem described in dummy_geometry.py. -""" - -from pytest import approx -import openmc.deplete - -from tests import dummy_operator - - -def test_si_celi(run_in_tmpdir): - """Integral regression test of integrator algorithm using si_celi""" - - op = dummy_operator.DummyOperator() - op.output_dir = "test_integrator_regression" - - # Perform simulation using the si_celi algorithm - dt = [0.75, 0.75] - power = 1.0 - openmc.deplete.si_celi(op, dt, power, print_out=False) - - # Load the files - res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") - - _, y1 = res.get_atoms("1", "1") - _, y2 = res.get_atoms("1", "2") - - # Reference solution - s1 = [2.03325094, 1.16826254] - s2 = [2.69291933, 0.37907772] - - assert y1[1] == approx(s1[0]) - assert y2[1] == approx(s1[1]) - - assert y1[2] == approx(s2[0]) - assert y2[2] == approx(s2[1]) - - # Test structure of depletion time dataset - - dep_time = res.get_depletion_time() - assert dep_time.shape == (len(dt), ) - assert all(dep_time > 0) diff --git a/tests/unit_tests/test_deplete_si_leqi.py b/tests/unit_tests/test_deplete_si_leqi.py deleted file mode 100644 index 1396065c0f..0000000000 --- a/tests/unit_tests/test_deplete_si_leqi.py +++ /dev/null @@ -1,43 +0,0 @@ -"""Regression tests for openmc.deplete.integrator.si_leqi algorithm. - -These tests integrate a simple test problem described in dummy_geometry.py. -""" - -from pytest import approx -import openmc.deplete - -from tests import dummy_operator - - -def test_si_leqi(run_in_tmpdir): - """Integral regression test of integrator algorithm using si_leqi""" - - op = dummy_operator.DummyOperator() - op.output_dir = "test_integrator_regression" - - # Perform simulation using the si_leqi algorithm - dt = [0.75, 0.75] - power = 1.0 - openmc.deplete.si_leqi(op, dt, power, print_out=False) - - # Load the files - res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") - - _, y1 = res.get_atoms("1", "1") - _, y2 = res.get_atoms("1", "2") - - # Reference solution - s1 = [2.03325094, 1.16826254] - s2 = [2.92711288, 0.53753236] - - assert y1[1] == approx(s1[0]) - assert y2[1] == approx(s1[1]) - - assert y1[2] == approx(s2[0]) - assert y2[2] == approx(s2[1]) - - # Test structure of depletion time dataset - - dep_time = res.get_depletion_time() - assert dep_time.shape == (len(dt), ) - assert all(dep_time > 0) diff --git a/tests/unit_tests/test_pin.py b/tests/unit_tests/test_pin.py index 42241de5f2..c2a5bc9c0e 100644 --- a/tests/unit_tests/test_pin.py +++ b/tests/unit_tests/test_pin.py @@ -2,7 +2,7 @@ Tests for constructing Pin universes """ -import numpy +import numpy as np import pytest import openmc @@ -98,8 +98,8 @@ def test_subdivide(pin_mats, good_radii, surf_type): # check volumes of new rings radii = get_pin_radii(div0) bounds = [0] + radii[:N] - sqrs = numpy.square(bounds) - assert sqrs[1:] - sqrs[:-1] == pytest.approx(good_radii[0] ** 2 / N) + sqrs = np.square(bounds) + assert np.all(sqrs[1:] - sqrs[:-1] == pytest.approx(good_radii[0] ** 2 / N)) # subdivide non-inner most region new_pin = pin(surfs, pin_mats, {1: N}) @@ -112,6 +112,6 @@ def test_subdivide(pin_mats, good_radii, surf_type): # check volumes of new rings radii = get_pin_radii(new_pin) - sqrs = numpy.square(radii[:N + 1]) - assert sqrs[1:] - sqrs[:-1] == pytest.approx( - (good_radii[1] ** 2 - good_radii[0] ** 2) / N) + sqrs = np.square(radii[:N + 1]) + assert np.all(sqrs[1:] - sqrs[:-1] == pytest.approx( + (good_radii[1] ** 2 - good_radii[0] ** 2) / N)) diff --git a/tests/unit_tests/test_transfer_volumes.py b/tests/unit_tests/test_transfer_volumes.py index 9128ed9694..2e735c911e 100644 --- a/tests/unit_tests/test_transfer_volumes.py +++ b/tests/unit_tests/test_transfer_volumes.py @@ -4,7 +4,8 @@ """ from pytest import approx -import openmc.deplete +import openmc +from openmc.deplete import PredictorIntegrator, ResultsList from tests import dummy_operator @@ -18,13 +19,13 @@ def test_transfer_volumes(run_in_tmpdir): # Perform simulation using the predictor algorithm dt = [0.75] power = 1.0 - openmc.deplete.predictor(op, dt, power, print_out=False) + PredictorIntegrator(op, dt, power).integrate() # Load the files - res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") + res = openmc.deplete.ResultsList.from_hdf5(op.output_dir / "depletion_results.h5") # Create a dictionary of volumes to transfer - res[0].volume['1'] = 1.5 + res[0].volume['1'] = 1.5 res[0].volume['2'] = 2.5 # Create dummy geometry