From cd444510cf701f87c5015b2c1124162c1f0a751a Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Wed, 10 Jul 2019 13:09:38 -0500 Subject: [PATCH 001/137] Demonstrate Integrator class with cecm New abstract class openmc.deplete.integrate.Integrator and concrete CECMIntegrator for performing depletion analysis. Concrete classes only have to implement the __call__ method responsible for performing the time integration across a time interval. The abstract base class is responsible for iterating through all time steps, collecting and writing results to file, and executing intermediate transport solutions. --- openmc/deplete/integrator/__init__.py | 1 + openmc/deplete/integrator/abc.py | 152 ++++++++++++++++++++++++++ openmc/deplete/integrator/cecm.py | 132 +++++++--------------- 3 files changed, 194 insertions(+), 91 deletions(-) create mode 100644 openmc/deplete/integrator/abc.py diff --git a/openmc/deplete/integrator/__init__.py b/openmc/deplete/integrator/__init__.py index db906b3c13..961afefb6c 100644 --- a/openmc/deplete/integrator/__init__.py +++ b/openmc/deplete/integrator/__init__.py @@ -5,6 +5,7 @@ Integrator The integrator subcomponents. """ +from .abc import Integrator from .cf4 import * from .cecm import * from .celi import * diff --git a/openmc/deplete/integrator/abc.py b/openmc/deplete/integrator/abc.py new file mode 100644 index 0000000000..d6f318fa4c --- /dev/null +++ b/openmc/deplete/integrator/abc.py @@ -0,0 +1,152 @@ +from copy import deepcopy +from abc import ABC, abstractmethod +from collections.abc import Iterable + +from openmc.deplete import Results + + +class Integrator(ABC): + """Abstract class for solving the time-integration for depletion + + """ + + def __init__(self, operator, timesteps, power=None, power_density=None): + """ + 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. + """ + 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): + # TODO Maybe use itertools.zip_longest? + # Ensure that power is single value if that is the case + power = [power] * len(self.timesteps) + + self.power = power + + @abstractmethod + def __call__(self, conc, rates, dt, power): + """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 [W] + + Returns + ------- + proc_time : float + Time spent in CRAM routines for all materials + 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 + """ + + def __iter__(self): + for dt, p in zip(self.timesteps, self.power): + yield dt, p + + def __len__(self): + return len(self.timesteps) + + def _get_bos_data(self, step_index, step_power, prev_conc): + if step_index > 0 or self.operator.prev_res is None: + x = deepcopy(prev_conc) + res = self.operator(x, step_power) + else: + # Get previous concentration + x = self.operator.prev_res[-1].data[0] + + # Get reaction rates and keff + res = self.operator.prev_res[-1] + res.rates = res.rates[0] + res.k = res.k[0] + + # Scale rates by ratio of powers + res.rates *= step_power / res.power[0] + return x, res + + 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) + + def integrate_all(self): + """Perform the entire depletion process across all steps""" + with self.operator as conc: + t, i_start = self._get_start_data() + + for i, (dt, p) in enumerate(self): + conc, res = self._get_bos_data(i, p, conc) + proc_time, conc_list, res_list = self(conc, res.rates, dt, p) + + # 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() + + self._save_results( + conc_list, res_list, [t, t + dt], p, i_start + i, + proc_time) + + t += dt + + # Final simulation + res_list = [self.operator(conc, p)] + self._save_results( + [conc], res_list, [t, t], p, i_start + len(self)) + + def _save_results(self, conc_list, results_list, time_list, power, + index, proc_time=None): + """Save the results at the end of of one step + + Abstracted to support the predictor's unique save location + """ + Results.save( + self.operator, conc_list, results_list, time_list, + power, index, proc_time) + + @classmethod + def integrate(cls, operator, timesteps, power=None, power_density=None): + """High-level interface for depleting with this integrator""" + return cls(operator, timesteps, power, power_density).integrate_all() diff --git a/openmc/deplete/integrator/cecm.py b/openmc/deplete/integrator/cecm.py index 758302b027..031e4ce7b1 100644 --- a/openmc/deplete/integrator/cecm.py +++ b/openmc/deplete/integrator/cecm.py @@ -1,13 +1,12 @@ """The CE/CM integrator.""" -import copy -from collections.abc import Iterable +from textwrap import dedent +from .abc import Integrator from .cram import timed_deplete -from ..results import Results -def cecm(operator, timesteps, power=None, power_density=None, print_out=True): +class CECMIntegrator(Integrator): r"""Deplete using the CE/CM algorithm. Implements the second order `CE/CM predictor-corrector algorithm @@ -24,100 +23,51 @@ def cecm(operator, timesteps, power=None, power_density=None, print_out=True): 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) + def __call__(self, conc, rates, dt, power): + """Integrate using CE/CM - # 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) + 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] - chain = operator.chain + Returns + ------- + proc_time : float + Time spent in CRAM routines for all materials + 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) - 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)] + # deplete across entire interval with BOS concentrations, + # MOS reaction rates + time1, x_end = timed_deplete(self.chain, conc, res_middle.rates, dt) - else: - # Get initial concentration - x = [operator.prev_res[-1].data[0]] + return time0 + time1, [x_middle, x_end], [res_middle] - # 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] +def cecm(operator, timesteps, power=None, power_density=None, print_out=False): + # TODO Remove print_out since depletion timings are stored + return CECMIntegrator( + operator, timesteps, power, power_density).integrate_all() - # 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)) +try: + cecm.__doc__ = ( + dedent(CECMIntegrator.__doc__) + dedent(Integrator.__init__.__doc__)) +except AttributeError: + pass From f432be0c055f889426ec3ab01c7316cd8d491e1d Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Tue, 16 Jul 2019 11:04:50 -0500 Subject: [PATCH 002/137] Remove cecm function for deplete.CECMIntegrator The cecm function has been removed from the python api. In order to use the cecm integration scheme, the following class based approach is now expected: >>> from openmc.deplete import CECMIntegrator >>> cecm = CECMIntegrator(operator, timesteps, power) >>> cecm.integrate() if the integrator is not needed, this above expression can be one-lined with >>> CECMIntegrator(operator, timesteps, power).integrate() Unit tests have been updated to no longer use the cecm function, and cecm has been removed from documentation. The CECMIntegrator has been added to documentation. --- docs/source/pythonapi/deplete.rst | 8 +++++++- openmc/deplete/integrator/abc.py | 10 ++-------- openmc/deplete/integrator/cecm.py | 13 ------------- openmc/deplete/operator.py | 2 +- tests/unit_tests/test_deplete_cecm.py | 4 +++- tests/unit_tests/test_deplete_restart.py | 13 +++++++++---- 6 files changed, 22 insertions(+), 28 deletions(-) diff --git a/docs/source/pythonapi/deplete.rst b/docs/source/pythonapi/deplete.rst index 532e066559..32f953ed46 100644 --- a/docs/source/pythonapi/deplete.rst +++ b/docs/source/pythonapi/deplete.rst @@ -17,7 +17,6 @@ transport-depletion coupling algorithms `_. :template: myfunction.rst integrator.predictor - integrator.cecm integrator.celi integrator.leqi integrator.cf4 @@ -25,6 +24,13 @@ transport-depletion coupling algorithms `_. integrator.si_celi integrator.si_leqi +.. autosummary:: + :toctree: generated + :nosignatures: + :template: myclassinherit.rst + + integrator.CECMIntegrator + Each of these functions expects a "transport operator" to be passed. An operator specific to OpenMC is available using the following class: diff --git a/openmc/deplete/integrator/abc.py b/openmc/deplete/integrator/abc.py index d6f318fa4c..51877e5e6e 100644 --- a/openmc/deplete/integrator/abc.py +++ b/openmc/deplete/integrator/abc.py @@ -81,8 +81,7 @@ class Integrator(ABC): """ def __iter__(self): - for dt, p in zip(self.timesteps, self.power): - yield dt, p + return zip(self.timesteps, self.power) def __len__(self): return len(self.timesteps) @@ -109,7 +108,7 @@ class Integrator(ABC): return 0.0, 0 return self.operator.prev_res[-1].time[-1], len(self.operator.prev_res) - def integrate_all(self): + def integrate(self): """Perform the entire depletion process across all steps""" with self.operator as conc: t, i_start = self._get_start_data() @@ -145,8 +144,3 @@ class Integrator(ABC): Results.save( self.operator, conc_list, results_list, time_list, power, index, proc_time) - - @classmethod - def integrate(cls, operator, timesteps, power=None, power_density=None): - """High-level interface for depleting with this integrator""" - return cls(operator, timesteps, power, power_density).integrate_all() diff --git a/openmc/deplete/integrator/cecm.py b/openmc/deplete/integrator/cecm.py index 031e4ce7b1..1bf6e5809b 100644 --- a/openmc/deplete/integrator/cecm.py +++ b/openmc/deplete/integrator/cecm.py @@ -58,16 +58,3 @@ class CECMIntegrator(Integrator): time1, x_end = timed_deplete(self.chain, conc, res_middle.rates, dt) return time0 + time1, [x_middle, x_end], [res_middle] - - -def cecm(operator, timesteps, power=None, power_density=None, print_out=False): - # TODO Remove print_out since depletion timings are stored - return CECMIntegrator( - operator, timesteps, power, power_density).integrate_all() - - -try: - cecm.__doc__ = ( - dedent(CECMIntegrator.__doc__) + dedent(Integrator.__init__.__doc__)) -except AttributeError: - pass diff --git a/openmc/deplete/operator.py b/openmc/deplete/operator.py index 21c96b0471..a38c4e5016 100644 --- a/openmc/deplete/operator.py +++ b/openmc/deplete/operator.py @@ -55,7 +55,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 ---------- diff --git a/tests/unit_tests/test_deplete_cecm.py b/tests/unit_tests/test_deplete_cecm.py index dd8b769fe6..8ad1ec95ea 100644 --- a/tests/unit_tests/test_deplete_cecm.py +++ b/tests/unit_tests/test_deplete_cecm.py @@ -5,6 +5,7 @@ These tests integrate a simple test problem described in dummy_geometry.py. from pytest import approx import openmc.deplete +from openmc.deplete import CECMIntegrator from tests import dummy_operator @@ -18,7 +19,8 @@ def test_cecm(run_in_tmpdir): # Perform simulation using the MCNPX/MCNP6 algorithm dt = [0.75, 0.75] power = 1.0 - openmc.deplete.cecm(op, dt, power, print_out=False) + integrator = CECMIntegrator(op, dt, power) + integrator.integrate() # Load the files res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") diff --git a/tests/unit_tests/test_deplete_restart.py b/tests/unit_tests/test_deplete_restart.py index ec62064a4e..97f0a8b70c 100644 --- a/tests/unit_tests/test_deplete_restart.py +++ b/tests/unit_tests/test_deplete_restart.py @@ -6,6 +6,7 @@ problem described in dummy_geometry.py. from pytest import approx import openmc.deplete +from openmc.deplete import CECMIntegrator from tests import dummy_operator @@ -59,7 +60,8 @@ def test_restart_cecm(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 = CECMIntegrator(op, dt, power) + cecm.integrate() # Load the files prev_res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") @@ -69,7 +71,8 @@ def test_restart_cecm(run_in_tmpdir): op.output_dir = output_dir # Perform restarts simulation using the MCNPX/MCNP6 algorithm - openmc.deplete.cecm(op, dt, power, print_out=False) + cecm_restart = CECMIntegrator(op, dt, power) + cecm_restart.integrate() # Load the files res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") @@ -109,7 +112,8 @@ def test_restart_predictor_cecm(run_in_tmpdir): op.output_dir = output_dir # Perform restarts simulation using the MCNPX/MCNP6 algorithm - openmc.deplete.cecm(op, dt, power, print_out=False) + cecm = CECMIntegrator(op, dt, power) + cecm.integrate() # Load the files res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") @@ -139,7 +143,8 @@ 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 = CECMIntegrator(op, dt, power) + cecm.integrate() # Load the files prev_res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") From f0bb6002713ed622074c79513c6f8ec3e468c6fa Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Wed, 17 Jul 2019 13:36:32 -0500 Subject: [PATCH 003/137] Remove predictor function for deplete.PredictorIntegrator The depletion function openmc.deplete.predictor has been removed in favor of a class-based approach. The following syntax will replicate the behavior of the predictor integration scheme: >>> from openmc.deplete import PredictorIntegrator >>> predictor = PredictorIntegrator(operator, time, power) >>> predictor.integrate()` The expression can be reduced to a single line: >>> PredictorIntegrator(operator, time, power).integrate() --- docs/source/pythonapi/deplete.rst | 2 +- openmc/deplete/integrator/predictor.py | 174 +++++++++++++-------- tests/regression_tests/deplete/test.py | 2 +- tests/unit_tests/test_deplete_predictor.py | 7 +- tests/unit_tests/test_deplete_restart.py | 10 +- 5 files changed, 119 insertions(+), 76 deletions(-) diff --git a/docs/source/pythonapi/deplete.rst b/docs/source/pythonapi/deplete.rst index 32f953ed46..452361e73c 100644 --- a/docs/source/pythonapi/deplete.rst +++ b/docs/source/pythonapi/deplete.rst @@ -16,7 +16,6 @@ transport-depletion coupling algorithms `_. :nosignatures: :template: myfunction.rst - integrator.predictor integrator.celi integrator.leqi integrator.cf4 @@ -29,6 +28,7 @@ transport-depletion coupling algorithms `_. :nosignatures: :template: myclassinherit.rst + integrator.PredictorIntegrator integrator.CECMIntegrator Each of these functions expects a "transport operator" to be passed. An operator diff --git a/openmc/deplete/integrator/predictor.py b/openmc/deplete/integrator/predictor.py index b30484c695..7345b11009 100644 --- a/openmc/deplete/integrator/predictor.py +++ b/openmc/deplete/integrator/predictor.py @@ -1,14 +1,12 @@ """First-order predictor algorithm.""" -import copy -from collections.abc import Iterable +from copy import deepcopy +from .abc import Integrator from .cram import timed_deplete -from ..results import Results -def predictor(operator, timesteps, power=None, power_density=None, - print_out=True): +class PredictorIntegrator(Integrator): r"""Deplete using a first-order predictor algorithm. Implements the first-order predictor algorithm. This algorithm is @@ -26,81 +24,125 @@ def predictor(operator, timesteps, power=None, power_density=None, 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. + 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 + 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. - + 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. """ - 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) + def __init__(self, operator, timesteps, power=None, power_density=None): + """ + 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. + """ + super().__init__(operator, timesteps, power, power_density) + self._dep_proc_time = None - proc_time = None + def __call__(self, conc, rates, dt, power): + """Perform the integration across one time step - # 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 + 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] - chain = operator.chain + Returns + ------- + proc_time : float + Time spent in CRAM routines for all materials + 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 - 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)] + """ + proc_time, conc_end = timed_deplete(self.chain, conc, rates, dt) + return proc_time, conc_end, [] - # Create results, write to disk - Results.save(operator, x, op_results, [t, t + dt], p, i_res + i, proc_time) + 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 _get_bos_data(self, step_index, step_power, prev_conc): + if step_index > 0 or self.operator.prev_res is None: + conc = deepcopy(prev_conc) + res = self.operator(conc, step_power) + if step_index == len(self) - 1: + tvec = [self.timesteps[step_index]] * 2 else: - # Get initial concentration - x = [operator.prev_res[-1].data[0]] + tvec = self.timesteps[step_index:step_index + 2] - # Get rates - op_results = [operator.prev_res[-1]] - op_results[0].rates = op_results[0].rates[0] + self._save_results( + [conc], [res], tvec, step_power, + self._istart + step_index, self._dep_proc_time) + else: + # Get previous concentration + conc = self.operator.prev_res[-1].data[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] + # Get reaction rates and keff + res = self.operator.prev_res[-1] + res.rates = res.rates[0] + res.k = res.k[0] - # Deplete for full timestep - proc_time, x_end = timed_deplete( - chain, x[0], op_results[0].rates, dt, print_out) + # Scale rates by ratio of powers + res.rates *= step_power / res.power[0] - # Advance time, update vector - t += dt - vec = copy.deepcopy(x_end) + return conc, res - # Perform one last simulation - x = [copy.deepcopy(vec)] - op_results = [operator(x[0], power[-1])] + def integrate(self): + """Perform the entire depletion process across all steps""" - # Create results, write to disk - Results.save(operator, x, op_results, [t, t], p, i_res + len(timesteps), proc_time) + with self.operator as conc: + t, self._istart = self._get_start_data() + + for i, (dt, p) in enumerate(self): + conc, res = self._get_bos_data(i, p, conc) + # __call__ returns empty list since there aren't + # intermediate transport solutions + self._dep_proc_time, conc, _res_list = self( + conc, res.rates, dt, p) + + t += dt + + # Final simulation + res_list = [self.operator(conc, p)] + self._save_results( + [conc], res_list, [t, t], p, + self._istart + len(self), self._dep_proc_time) diff --git a/tests/regression_tests/deplete/test.py b/tests/regression_tests/deplete/test.py index 059a44ea35..5f2d8cc454 100644 --- a/tests/regression_tests/deplete/test.py +++ b/tests/regression_tests/deplete/test.py @@ -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' diff --git a/tests/unit_tests/test_deplete_predictor.py b/tests/unit_tests/test_deplete_predictor.py index 6af6f8bca4..e212f46514 100644 --- a/tests/unit_tests/test_deplete_predictor.py +++ b/tests/unit_tests/test_deplete_predictor.py @@ -4,7 +4,7 @@ These tests integrate a simple test problem described in dummy_geometry.py. """ from pytest import approx -import openmc.deplete +from openmc.deplete import PredictorIntegrator, ResultsList from tests import dummy_operator @@ -18,10 +18,11 @@ def test_predictor(run_in_tmpdir): # Perform simulation using the predictor algorithm dt = [0.75, 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 = ResultsList(op.output_dir / "depletion_results.h5") _, y1 = res.get_atoms("1", "1") _, y2 = res.get_atoms("1", "2") diff --git a/tests/unit_tests/test_deplete_restart.py b/tests/unit_tests/test_deplete_restart.py index 97f0a8b70c..cf7eeb0ede 100644 --- a/tests/unit_tests/test_deplete_restart.py +++ b/tests/unit_tests/test_deplete_restart.py @@ -6,7 +6,7 @@ problem described in dummy_geometry.py. from pytest import approx import openmc.deplete -from openmc.deplete import CECMIntegrator +from openmc.deplete import CECMIntegrator, PredictorIntegrator from tests import dummy_operator @@ -21,7 +21,7 @@ def test_restart_predictor(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 prev_res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") @@ -31,7 +31,7 @@ def test_restart_predictor(run_in_tmpdir): op.output_dir = output_dir # Perform restarts simulation using the predictor algorithm - 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") @@ -102,7 +102,7 @@ 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) + PredictorIntegrator(op, dt, power).integrate() # Load the files prev_res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") @@ -154,7 +154,7 @@ def test_restart_cecm_predictor(run_in_tmpdir): op.output_dir = output_dir # Perform restarts simulation using the predictor algorithm - 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") From 1d791a16c9298a3bd97d033ceaf35f76a3aa09a8 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Wed, 17 Jul 2019 16:29:55 -0500 Subject: [PATCH 004/137] Pass iteration index to Integrator.__call__ For the higher order methods, like LEQI, a lower order method may be called for the first few steps. The LEQI method, with no restart data, will call CELI for the first iteration. To aid this, concrete Integrator classes will be passed an integer for the current iteration. Some methods, like the CECM and CELI, do not need this information. To maintain a consistent API, the iteration is an optional value to be passed to CECM.__call__, but will not be used. The default value in the call signature is ``_i=-1``, with documentation repeating that the value is not used nor needed. --- openmc/deplete/integrator/abc.py | 12 +++++++----- openmc/deplete/integrator/cecm.py | 4 +++- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/openmc/deplete/integrator/abc.py b/openmc/deplete/integrator/abc.py index 51877e5e6e..c2324b35c8 100644 --- a/openmc/deplete/integrator/abc.py +++ b/openmc/deplete/integrator/abc.py @@ -54,7 +54,7 @@ class Integrator(ABC): self.power = power @abstractmethod - def __call__(self, conc, rates, dt, power): + def __call__(self, conc, rates, dt, power, i): """Perform the integration across one time step Parameters @@ -67,6 +67,8 @@ class Integrator(ABC): Time in [s] for the entire depletion interval power : float Power of the system [W] + i : int + Current depletion step index Returns ------- @@ -111,11 +113,11 @@ class Integrator(ABC): def integrate(self): """Perform the entire depletion process across all steps""" with self.operator as conc: - t, i_start = self._get_start_data() + t, self._ires = self._get_start_data() for i, (dt, p) in enumerate(self): conc, res = self._get_bos_data(i, p, conc) - proc_time, conc_list, res_list = self(conc, res.rates, dt, p) + proc_time, conc_list, res_list = self(conc, res.rates, dt, p, i) # Insert BOS concentration, transport results conc_list.insert(0, conc) @@ -125,7 +127,7 @@ class Integrator(ABC): conc = conc_list.pop() self._save_results( - conc_list, res_list, [t, t + dt], p, i_start + i, + conc_list, res_list, [t, t + dt], p, self._ires + i, proc_time) t += dt @@ -133,7 +135,7 @@ class Integrator(ABC): # Final simulation res_list = [self.operator(conc, p)] self._save_results( - [conc], res_list, [t, t], p, i_start + len(self)) + [conc], res_list, [t, t], p, self._ires + len(self)) def _save_results(self, conc_list, results_list, time_list, power, index, proc_time=None): diff --git a/openmc/deplete/integrator/cecm.py b/openmc/deplete/integrator/cecm.py index 1bf6e5809b..b9b7dedc61 100644 --- a/openmc/deplete/integrator/cecm.py +++ b/openmc/deplete/integrator/cecm.py @@ -25,7 +25,7 @@ class CECMIntegrator(Integrator): \end{aligned} """ - def __call__(self, conc, rates, dt, power): + def __call__(self, conc, rates, dt, power, _i=-1): """Integrate using CE/CM Parameters @@ -38,6 +38,8 @@ class CECMIntegrator(Integrator): Time in [s] for the entire depletion interval power : float Power of the system [W] + _i : int, optional + Current iteration count. Not used Returns ------- From 769f903dbc85ca460604c3f0a858286dde6fa2ea Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Wed, 17 Jul 2019 16:46:30 -0500 Subject: [PATCH 005/137] Remove celi, leqi functions for CELIIntegrator, LEQIIntegrator The depletion functions openmc.deplete.celi and openmc.deplete.leqi hvae been removed in favor of a class-based approach. The following syntax will replicate the behavior of the integration schemes: >>> from openmc.deplete import CELIIntegrator >>> celi = CELIIntegrator(operator, time, power) >>> celi.integrate() The expression can be reduced to a single line: >>> LEQIIntegrator(operator, time, power).integrate() --- docs/source/pythonapi/deplete.rst | 4 +- openmc/deplete/integrator/celi.py | 179 +++++------------- openmc/deplete/integrator/leqi.py | 229 ++++++++++------------- tests/unit_tests/test_deplete_celi.py | 6 +- tests/unit_tests/test_deplete_leqi.py | 6 +- tests/unit_tests/test_deplete_restart.py | 12 +- 6 files changed, 160 insertions(+), 276 deletions(-) diff --git a/docs/source/pythonapi/deplete.rst b/docs/source/pythonapi/deplete.rst index 452361e73c..99c32044dd 100644 --- a/docs/source/pythonapi/deplete.rst +++ b/docs/source/pythonapi/deplete.rst @@ -16,8 +16,6 @@ transport-depletion coupling algorithms `_. :nosignatures: :template: myfunction.rst - integrator.celi - integrator.leqi integrator.cf4 integrator.epc_rk4 integrator.si_celi @@ -30,6 +28,8 @@ transport-depletion coupling algorithms `_. integrator.PredictorIntegrator integrator.CECMIntegrator + integrator.CELIIntegrator + integrator.LEQIIntegrator Each of these functions expects a "transport operator" to be passed. An operator specific to OpenMC is available using the following class: diff --git a/openmc/deplete/integrator/celi.py b/openmc/deplete/integrator/celi.py index 2e80c8b739..9346347a34 100644 --- a/openmc/deplete/integrator/celi.py +++ b/openmc/deplete/integrator/celi.py @@ -1,25 +1,10 @@ """The CE/LI CFQ4 integrator.""" -import copy -from collections.abc import Iterable - from .cram import timed_deplete -from ..results import Results +from .abc import Integrator -# 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): +class CELIIntegrator(Integrator): r"""Deplete using the CE/LI CFQ4 algorithm. Implements the CE/LI Predictor-Corrector algorithm using the `fourth order @@ -37,131 +22,57 @@ def celi(operator, timesteps, power=None, power_density=None, 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 + def __call__(self, bos_conc, rates, dt, power, _i=-1): + """Perform the integration across one time step - # 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)] + 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 [W] + _i : int + Current iteration count. Not used - else: - # Get initial concentration - x = [operator.prev_res[-1].data[0]] + Returns + ------- + proc_time : float + Time spent in CRAM routines for all materials + 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) - # Get rates - op_results = [operator.prev_res[-1]] - op_results[0].rates = op_results[0].rates[0] + # deplete using two matrix exponeitials + list_rates = list(zip(rates, res_ce.rates)) - # Set first stage value of keff - op_results[0].k = op_results[0].k[0] + time_le1, conc_inter = timed_deplete( + self.chain, bos_conc, list_rates, dt, matrix_func=_celi_f1) - # 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] + time_le2, conc_end = timed_deplete( + self.chain, conc_inter, list_rates, dt, matrix_func=_celi_f2) - # 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)) + return proc_time + time_le1 + time_le1, [conc_ce, conc_end], [res_ce] - # 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) +# 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]) - # return updated time and vectors - return x_end, t + dt, op_results[0] + +def _celi_f2(chain, rates): + return 1/12 * chain.form_matrix(rates[0]) + \ + 5/12 * chain.form_matrix(rates[1]) diff --git a/openmc/deplete/integrator/leqi.py b/openmc/deplete/integrator/leqi.py index 19b1025e5c..4b1c072b52 100644 --- a/openmc/deplete/integrator/leqi.py +++ b/openmc/deplete/integrator/leqi.py @@ -1,12 +1,109 @@ """The LE/QI CFQ4 integrator.""" import copy -from collections.abc import Iterable from itertools import repeat -from .celi import celi_inner +from .abc import Integrator +from .celi import CELIIntegrator from .cram import timed_deplete -from ..results import Results + + +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. + """ + + 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 [W] + i : int + Current depletion step index + + Returns + ------- + proc_time : float + Time spent in CRAM routines for all materials + 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._ires <= 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] + prevdt = self.timesteps[i] - prev_res.time[0] + self._prev_rates = prev_res.rates[0] + else: + prevdt = 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(prevdt), 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(prevdt), 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]) # Functions to form the special matrix for depletion @@ -42,129 +139,3 @@ def _leqi_f4(chain, inputs): 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/tests/unit_tests/test_deplete_celi.py b/tests/unit_tests/test_deplete_celi.py index 47d3319fdb..570ebe151d 100644 --- a/tests/unit_tests/test_deplete_celi.py +++ b/tests/unit_tests/test_deplete_celi.py @@ -4,7 +4,7 @@ These tests integrate a simple test problem described in dummy_geometry.py. """ from pytest import approx -import openmc.deplete +from openmc.deplete import ResultsList, CELIIntegrator from tests import dummy_operator @@ -18,10 +18,10 @@ def test_celi(run_in_tmpdir): # Perform simulation using the celi algorithm dt = [0.75, 0.75] power = 1.0 - openmc.deplete.celi(op, dt, power, print_out=False) + CELIIntegrator(op, dt, power).integrate() # Load the files - res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") + res = ResultsList(op.output_dir / "depletion_results.h5") _, y1 = res.get_atoms("1", "1") _, y2 = res.get_atoms("1", "2") diff --git a/tests/unit_tests/test_deplete_leqi.py b/tests/unit_tests/test_deplete_leqi.py index a26329cf4a..0ab4350f6e 100644 --- a/tests/unit_tests/test_deplete_leqi.py +++ b/tests/unit_tests/test_deplete_leqi.py @@ -4,7 +4,7 @@ These tests integrate a simple test problem described in dummy_geometry.py. """ from pytest import approx -import openmc.deplete +from openmc.deplete import LEQIIntegrator, ResultsList from tests import dummy_operator @@ -18,10 +18,10 @@ def test_leqi(run_in_tmpdir): # Perform simulation using the leqi algorithm dt = [0.75, 0.75] power = 1.0 - openmc.deplete.leqi(op, dt, power, print_out=False) + LEQIIntegrator(op, dt, power).integrate() # Load the files - res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") + res = ResultsList(op.output_dir / "depletion_results.h5") _, y1 = res.get_atoms("1", "1") _, y2 = res.get_atoms("1", "2") diff --git a/tests/unit_tests/test_deplete_restart.py b/tests/unit_tests/test_deplete_restart.py index cf7eeb0ede..a914c86494 100644 --- a/tests/unit_tests/test_deplete_restart.py +++ b/tests/unit_tests/test_deplete_restart.py @@ -6,7 +6,9 @@ problem described in dummy_geometry.py. from pytest import approx import openmc.deplete -from openmc.deplete import CECMIntegrator, PredictorIntegrator +from openmc.deplete import ( + CECMIntegrator, PredictorIntegrator, CELIIntegrator, LEQIIntegrator, +) from tests import dummy_operator @@ -261,7 +263,7 @@ def test_restart_celi(run_in_tmpdir): # Perform simulation dt = [0.75] power = 1.0 - openmc.deplete.celi(op, dt, power, print_out=False) + CELIIntegrator(op, dt, power).integrate() # Load the files prev_res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") @@ -271,7 +273,7 @@ def test_restart_celi(run_in_tmpdir): op.output_dir = output_dir # Perform restarts simulation - openmc.deplete.celi(op, dt, power, print_out=False) + CELIIntegrator(op, dt, power).integrate() # Load the files res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") @@ -300,7 +302,7 @@ def test_restart_leqi(run_in_tmpdir): # Perform simulation dt = [0.75] power = 1.0 - openmc.deplete.leqi(op, dt, power, print_out=False) + LEQIIntegrator(op, dt, power).integrate() # Load the files prev_res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") @@ -310,7 +312,7 @@ def test_restart_leqi(run_in_tmpdir): op.output_dir = output_dir # Perform restarts simulation - openmc.deplete.leqi(op, dt, power, print_out=False) + LEQIIntegrator(op, dt, power).integrate() # Load the files res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") From dae48b77e012b660d974e5ab30cf5ac90b4d11ee Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Wed, 17 Jul 2019 17:09:13 -0500 Subject: [PATCH 006/137] Remove cf4 function for deplete.CF4Integrator The function openmc.deplete.cf4 has been removed in favor of openmc.deplete.CF4Integrator. The CF4 integration scheme can be peformed with >>> from openmc.deplete import CF4Integrator >>> CF4Integrator(operator, time, power).integrate() --- docs/source/pythonapi/deplete.rst | 2 +- openmc/deplete/integrator/cf4.py | 77 ++++++++++++++++++++++++++++ tests/unit_tests/test_deplete_cf4.py | 6 +-- 3 files changed, 81 insertions(+), 4 deletions(-) diff --git a/docs/source/pythonapi/deplete.rst b/docs/source/pythonapi/deplete.rst index 99c32044dd..37707e91ed 100644 --- a/docs/source/pythonapi/deplete.rst +++ b/docs/source/pythonapi/deplete.rst @@ -16,7 +16,6 @@ transport-depletion coupling algorithms `_. :nosignatures: :template: myfunction.rst - integrator.cf4 integrator.epc_rk4 integrator.si_celi integrator.si_leqi @@ -27,6 +26,7 @@ transport-depletion coupling algorithms `_. :template: myclassinherit.rst integrator.PredictorIntegrator + integrator.CF4Integrator integrator.CECMIntegrator integrator.CELIIntegrator integrator.LEQIIntegrator diff --git a/openmc/deplete/integrator/cf4.py b/openmc/deplete/integrator/cf4.py index 8b7f3a05a0..a614a42986 100644 --- a/openmc/deplete/integrator/cf4.py +++ b/openmc/deplete/integrator/cf4.py @@ -4,9 +4,86 @@ import copy from collections.abc import Iterable from .cram import timed_deplete +from .abc import Integrator from ..results import Results +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} + """ + + def __call__(self, bos_conc, bos_rates, dt, power, i): + """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 [W] + i : int + Current depletion step index + + Returns + ------- + proc_time : float + Time spent in CRAM routines for all materials + 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]) + + # Functions to form the special matrix for depletion def _cf4_f1(chain, rates): return 1/2 * chain.form_matrix(rates) diff --git a/tests/unit_tests/test_deplete_cf4.py b/tests/unit_tests/test_deplete_cf4.py index 0c6199c3e5..eb3d8a0bc4 100644 --- a/tests/unit_tests/test_deplete_cf4.py +++ b/tests/unit_tests/test_deplete_cf4.py @@ -4,7 +4,7 @@ These tests integrate a simple test problem described in dummy_geometry.py. """ from pytest import approx -import openmc.deplete +from openmc.deplete import CF4Integrator, ResultsList from tests import dummy_operator @@ -18,10 +18,10 @@ def test_cf4(run_in_tmpdir): # Perform simulation using the cf4 algorithm dt = [0.75, 0.75] power = 1.0 - openmc.deplete.cf4(op, dt, power, print_out=False) + CF4Integrator(op, dt, power).integrate() # Load the files - res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") + res = ResultsList(op.output_dir / "depletion_results.h5") _, y1 = res.get_atoms("1", "1") _, y2 = res.get_atoms("1", "2") From d3a7e73320459eac4688b21d70c0bc50aa093424 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Thu, 18 Jul 2019 08:37:17 -0500 Subject: [PATCH 007/137] Update transfer volumes test with PredictorIntegrator --- tests/unit_tests/test_transfer_volumes.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/tests/unit_tests/test_transfer_volumes.py b/tests/unit_tests/test_transfer_volumes.py index 9128ed9694..ae2151b2d7 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 = ResultsList(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 From a2545d7d7b296b419372c6918c226fe0df4c6aa3 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Fri, 19 Jul 2019 16:08:19 -0500 Subject: [PATCH 008/137] Provide Integrator get bos data from openmc|restart methods All of the integrators pull the previous concentration and operator results from a restart file in the same way, but vary in how subsequent steps are handled. The predictor saves data here, while the SIE-based methods scale and reset the particles used. To this end, the _get_bos_data function has been broken into two methods: one for calling the operator and one for pulling from a restart file. --- openmc/deplete/integrator/abc.py | 33 ++++++++++++----------- openmc/deplete/integrator/predictor.py | 37 ++++++++++---------------- 2 files changed, 31 insertions(+), 39 deletions(-) diff --git a/openmc/deplete/integrator/abc.py b/openmc/deplete/integrator/abc.py index c2324b35c8..89015ea2df 100644 --- a/openmc/deplete/integrator/abc.py +++ b/openmc/deplete/integrator/abc.py @@ -88,23 +88,21 @@ class Integrator(ABC): def __len__(self): return len(self.timesteps) - def _get_bos_data(self, step_index, step_power, prev_conc): - if step_index > 0 or self.operator.prev_res is None: - x = deepcopy(prev_conc) - res = self.operator(x, step_power) - else: - # Get previous concentration - x = self.operator.prev_res[-1].data[0] - - # Get reaction rates and keff - res = self.operator.prev_res[-1] - res.rates = res.rates[0] - res.k = res.k[0] - - # Scale rates by ratio of powers - res.rates *= step_power / res.power[0] + def _get_bos_data_from_openmc(self, step_index, step_power, bos_conc): + x = deepcopy(bos_conc) + res = self.operator(x, step_power) return x, res + def _get_bos_data_from_restart(self, step_index, step_power, bos_conc): + res = self.operator.prev_res[-1] + bos_conc = res.data[0] + res.rates = res.rates[0] + res.k = res.k[0] + + # Scale rates by ratio of powers + res.rates *= step_power / res.power[0] + return bos_conc, res + def _get_start_data(self): if self.operator.prev_res is None: return 0.0, 0 @@ -116,7 +114,10 @@ class Integrator(ABC): t, self._ires = self._get_start_data() for i, (dt, p) in enumerate(self): - conc, res = self._get_bos_data(i, p, conc) + if i > 0 or self.operator.prev_res is None: + conc, res = self._get_bos_data_from_openmc(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 diff --git a/openmc/deplete/integrator/predictor.py b/openmc/deplete/integrator/predictor.py index 7345b11009..45783b2793 100644 --- a/openmc/deplete/integrator/predictor.py +++ b/openmc/deplete/integrator/predictor.py @@ -100,30 +100,17 @@ class PredictorIntegrator(Integrator): self.operator.prev_res[-1].time[-1], len(self.operator.prev_res) - 1) - def _get_bos_data(self, step_index, step_power, prev_conc): - if step_index > 0 or self.operator.prev_res is None: - conc = deepcopy(prev_conc) - res = self.operator(conc, step_power) - if step_index == len(self) - 1: - tvec = [self.timesteps[step_index]] * 2 - else: - tvec = self.timesteps[step_index:step_index + 2] - - self._save_results( - [conc], [res], tvec, step_power, - self._istart + step_index, self._dep_proc_time) + def _get_bos_data_from_openmc(self, step_index, step_power, prev_conc): + conc = deepcopy(prev_conc) + res = self.operator(conc, step_power) + if step_index == len(self) - 1: + tvec = [self.timesteps[step_index]] * 2 else: - # Get previous concentration - conc = self.operator.prev_res[-1].data[0] - - # Get reaction rates and keff - res = self.operator.prev_res[-1] - res.rates = res.rates[0] - res.k = res.k[0] - - # Scale rates by ratio of powers - res.rates *= step_power / res.power[0] + tvec = self.timesteps[step_index:step_index + 2] + self._save_results( + [conc], [res], tvec, step_power, + self._istart + step_index, self._dep_proc_time) return conc, res def integrate(self): @@ -133,7 +120,11 @@ class PredictorIntegrator(Integrator): t, self._istart = self._get_start_data() for i, (dt, p) in enumerate(self): - conc, res = self._get_bos_data(i, p, conc) + if i > 0 or self.operator.prev_res is None: + conc, res = self._get_bos_data_from_openmc(i, p, conc) + else: + conc, res = self._get_bos_data_from_restart(i, p, conc) + # __call__ returns empty list since there aren't # intermediate transport solutions self._dep_proc_time, conc, _res_list = self( From 39b4d8a1fe0222477d6de8cf88b24d7e65617aa0 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Fri, 26 Jul 2019 09:49:58 -0500 Subject: [PATCH 009/137] Remove epc_rk4 for EPC_RK4_Integrator class The function openmc.deplete.epc_rk4 has been removed in favor of openmc.deplete.EPC_RK4_Integrator. The scheme can be used with:: >>> from openmc.deplete import EPC_RK4_Integrator >>> EPC_RK4_Integrator(op, dt, power).integrate() epc_rk4 has been removed from the documentation, and the EPC_RK4_Integrator class has been added to the depletion API documentation --- docs/source/pythonapi/deplete.rst | 2 +- openmc/deplete/integrator/epc_rk4.py | 166 ++++++++--------------- tests/unit_tests/test_deplete_epc_rk4.py | 6 +- tests/unit_tests/test_deplete_restart.py | 5 +- 4 files changed, 60 insertions(+), 119 deletions(-) diff --git a/docs/source/pythonapi/deplete.rst b/docs/source/pythonapi/deplete.rst index 37707e91ed..feb2f6dd63 100644 --- a/docs/source/pythonapi/deplete.rst +++ b/docs/source/pythonapi/deplete.rst @@ -16,7 +16,6 @@ transport-depletion coupling algorithms `_. :nosignatures: :template: myfunction.rst - integrator.epc_rk4 integrator.si_celi integrator.si_leqi @@ -29,6 +28,7 @@ transport-depletion coupling algorithms `_. integrator.CF4Integrator integrator.CECMIntegrator integrator.CELIIntegrator + integrator.EPC_RK4_Integrator integrator.LEQIIntegrator Each of these functions expects a "transport operator" to be passed. An operator diff --git a/openmc/deplete/integrator/epc_rk4.py b/openmc/deplete/integrator/epc_rk4.py index 373c323e42..ce6223c448 100644 --- a/openmc/deplete/integrator/epc_rk4.py +++ b/openmc/deplete/integrator/epc_rk4.py @@ -1,23 +1,10 @@ """The EPC-RK4 integrator.""" -import copy -from collections.abc import Iterable - from .cram import timed_deplete -from ..results import Results +from .abc import Integrator -# 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): +class EPC_RK4_Integrator(Integrator): r"""Deplete using the EPC-RK4 algorithm. Implements an extended predictor-corrector algorithm with traditional @@ -35,114 +22,67 @@ def epc_rk4(operator, timesteps, power=None, power_density=None, print_out=True) 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) + def __call__(self, conc, rates, dt, power, _i): + """Perform the integration across one time step - # 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) + 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 + Current depletion step index, unused. - chain = operator.chain + Returns + ------- + proc_time : float + Time spent in CRAM routines for all materials + 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 + """ - 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)] + # 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) - else: - # Get initial concentration - x = [operator.prev_res[-1].data[0]] + # 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) - # Get rates - op_results = [operator.prev_res[-1]] - op_results[0].rates = op_results[0].rates[0] + # Step 3: deplete with matrix A(y2) + time3, conc3 = timed_deplete( + self.chain, conc, res2.rates, dt) + res3 = self.operator(conc3, power) - # Set first stage value of keff - op_results[0].k = op_results[0].k[0] + # 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) - # 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] + return (time1 + time2 + time3 + time4, [conc1, conc2, conc3, conc4], + [res1, res2, res3]) - # 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)) +# Functions to form the special matrix for depletion +def _rk4_f1(chain, rates): + return 1/2 * chain.form_matrix(rates) - # 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)) +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]) diff --git a/tests/unit_tests/test_deplete_epc_rk4.py b/tests/unit_tests/test_deplete_epc_rk4.py index ea687bfd5d..71cda3b9ff 100644 --- a/tests/unit_tests/test_deplete_epc_rk4.py +++ b/tests/unit_tests/test_deplete_epc_rk4.py @@ -4,7 +4,7 @@ These tests integrate a simple test problem described in dummy_geometry.py. """ from pytest import approx -import openmc.deplete +from openmc.deplete import EPC_RK4_Integrator, ResultsList from tests import dummy_operator @@ -18,10 +18,10 @@ def test_epc_rk4(run_in_tmpdir): # 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) + EPC_RK4_Integrator(op, dt, power).integrate() # Load the files - res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") + res = ResultsList(op.output_dir / "depletion_results.h5") _, y1 = res.get_atoms("1", "1") _, y2 = res.get_atoms("1", "2") diff --git a/tests/unit_tests/test_deplete_restart.py b/tests/unit_tests/test_deplete_restart.py index a914c86494..9323c57798 100644 --- a/tests/unit_tests/test_deplete_restart.py +++ b/tests/unit_tests/test_deplete_restart.py @@ -8,6 +8,7 @@ from pytest import approx import openmc.deplete from openmc.deplete import ( CECMIntegrator, PredictorIntegrator, CELIIntegrator, LEQIIntegrator, + EPC_RK4_Integrator, ) from tests import dummy_operator @@ -224,7 +225,7 @@ def test_restart_epc_rk4(run_in_tmpdir): # Perform simulation dt = [0.75] power = 1.0 - openmc.deplete.epc_rk4(op, dt, power, print_out=False) + EPC_RK4_Integrator(op, dt, power).integrate() # Load the files prev_res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") @@ -234,7 +235,7 @@ def test_restart_epc_rk4(run_in_tmpdir): op.output_dir = output_dir # Perform restarts simulation - openmc.deplete.epc_rk4(op, dt, power, print_out=False) + EPC_RK4_Integrator(op, dt, power).integrate() # Load the files res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") From e30e6e3f99edcf037f63851764adafdeafea275d Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Fri, 26 Jul 2019 09:52:55 -0500 Subject: [PATCH 010/137] Use CF4Integrator in restart tests --- tests/unit_tests/test_deplete_restart.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/unit_tests/test_deplete_restart.py b/tests/unit_tests/test_deplete_restart.py index 9323c57798..6175757201 100644 --- a/tests/unit_tests/test_deplete_restart.py +++ b/tests/unit_tests/test_deplete_restart.py @@ -8,7 +8,7 @@ from pytest import approx import openmc.deplete from openmc.deplete import ( CECMIntegrator, PredictorIntegrator, CELIIntegrator, LEQIIntegrator, - EPC_RK4_Integrator, + EPC_RK4_Integrator, CF4Integrator ) from tests import dummy_operator @@ -186,7 +186,7 @@ def test_restart_cf4(run_in_tmpdir): # Perform simulation dt = [0.75] power = 1.0 - openmc.deplete.cf4(op, dt, power, print_out=False) + CF4Integrator(op, dt, power).integrate() # Load the files prev_res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") @@ -196,7 +196,7 @@ def test_restart_cf4(run_in_tmpdir): op.output_dir = output_dir # Perform restarts simulation - openmc.deplete.cf4(op, dt, power, print_out=False) + CF4Integrator(op, dt, power).integrate() # Load the files res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") From 58117628b8ae8384278c5ef5ef5d32e7c922fb4e Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Fri, 26 Jul 2019 09:53:14 -0500 Subject: [PATCH 011/137] Fully remove openmc.deplete.cf4 function from API --- openmc/deplete/integrator/cf4.py | 133 ------------------------------- 1 file changed, 133 deletions(-) diff --git a/openmc/deplete/integrator/cf4.py b/openmc/deplete/integrator/cf4.py index a614a42986..7e0b603ae1 100644 --- a/openmc/deplete/integrator/cf4.py +++ b/openmc/deplete/integrator/cf4.py @@ -106,136 +106,3 @@ def _cf4_f4(chain, rates): 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)) From 98957fb47350d8b012236f00cc89a030c38e4590 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Fri, 26 Jul 2019 10:31:24 -0500 Subject: [PATCH 012/137] Store OperatorResults.k as uncertainties.ufloat This allows the propagation of the uncertainty through the SIE iterations, and alows the expressions for updating k to be preserved. The OperatorResult.k was previous stored as a tuple of two floats. The interface into the depletion_results file is not changed, as the Results.save method, which writes the OperatorResult data, breaks the ufloat into k and uncertainties. Documentation has been updated on the OperatorResult. --- openmc/deplete/abc.py | 4 ++-- openmc/deplete/integrator/abc.py | 12 +++++++----- openmc/deplete/integrator/si_celi.py | 4 +++- openmc/deplete/integrator/si_leqi.py | 4 +++- openmc/deplete/operator.py | 3 ++- openmc/deplete/results.py | 6 +++--- tests/dummy_operator.py | 4 +++- tests/unit_tests/test_deplete_integrator.py | 6 ++++-- 8 files changed, 27 insertions(+), 16 deletions(-) diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py index 1be3e27ff9..5e2f1dc64f 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -20,8 +20,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 diff --git a/openmc/deplete/integrator/abc.py b/openmc/deplete/integrator/abc.py index 89015ea2df..07151d13b4 100644 --- a/openmc/deplete/integrator/abc.py +++ b/openmc/deplete/integrator/abc.py @@ -2,7 +2,9 @@ from copy import deepcopy from abc import ABC, abstractmethod from collections.abc import Iterable -from openmc.deplete import Results +from uncertainties import ufloat + +from openmc.deplete import Results, OperatorResult class Integrator(ABC): @@ -96,12 +98,12 @@ class Integrator(ABC): def _get_bos_data_from_restart(self, step_index, step_power, bos_conc): res = self.operator.prev_res[-1] bos_conc = res.data[0] - res.rates = res.rates[0] - res.k = res.k[0] + rates = res.rates[0] + k = ufloat(res.k[0,0], res.k[0, 1]) # Scale rates by ratio of powers - res.rates *= step_power / res.power[0] - return bos_conc, res + rates *= step_power / res.power[0] + return bos_conc, OperatorResult(k, rates) def _get_start_data(self): if self.operator.prev_res is None: diff --git a/openmc/deplete/integrator/si_celi.py b/openmc/deplete/integrator/si_celi.py index 26087c1f2c..872863ef22 100644 --- a/openmc/deplete/integrator/si_celi.py +++ b/openmc/deplete/integrator/si_celi.py @@ -3,6 +3,8 @@ import copy from collections.abc import Iterable +from uncertainties import ufloat + from .cram import timed_deplete from ..results import Results from ..abc import OperatorResult @@ -82,7 +84,7 @@ def si_celi(operator, timesteps, power=None, power_density=None, op_results[0].rates = op_results[0].rates[0] # Set first stage value of keff - op_results[0].k = op_results[0].k[0] + op_results[0].k = ufloat(*op_results[0].k[0]) # Scale reaction rates by ratio of powers power_res = operator.prev_res[-1].power diff --git a/openmc/deplete/integrator/si_leqi.py b/openmc/deplete/integrator/si_leqi.py index 7d01782bae..86b184038f 100644 --- a/openmc/deplete/integrator/si_leqi.py +++ b/openmc/deplete/integrator/si_leqi.py @@ -4,6 +4,8 @@ import copy from collections.abc import Iterable from itertools import repeat +from uncertainties import ufloat + from .si_celi import si_celi_inner from .leqi import _leqi_f1, _leqi_f2, _leqi_f3, _leqi_f4 from .cram import timed_deplete @@ -84,7 +86,7 @@ def si_leqi(operator, timesteps, power=None, power_density=None, op_results[0].rates = op_results[0].rates[0] # Set first stage value of keff - op_results[0].k = op_results[0].k[0] + op_results[0].k = ufloat(*op_results[0].k[0]) # Scale reaction rates by ratio of powers power_res = operator.prev_res[-1].power diff --git a/openmc/deplete/operator.py b/openmc/deplete/operator.py index a38c4e5016..e9c41093aa 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 @@ -526,7 +527,7 @@ class Operator(TransportOperator): rates[:, :, :] = 0.0 # Get k and uncertainty - k_combined = openmc.capi.keff() + k_combined = ufloat(*openmc.capi.keff()) # Extract tally bins materials = self.burnable_mats diff --git a/openmc/deplete/results.py b/openmc/deplete/results.py index 3771f9683b..a2462b80a1 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 @@ -446,7 +446,7 @@ class Results(object): for mat_i in range(n_mat): results[i, mat_i, :] = x[offset + 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/tests/dummy_operator.py b/tests/dummy_operator.py index c66070ad40..23dc22288a 100644 --- a/tests/dummy_operator.py +++ b/tests/dummy_operator.py @@ -1,5 +1,7 @@ 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 @@ -48,7 +50,7 @@ class DummyOperator(TransportOperator): reaction_rates[0, 1, 0] = vec[0][1] # Create a fake rates object - return OperatorResult(0.0, reaction_rates) + return OperatorResult(ufloat(0.0, 0.0), reaction_rates) @property def chain(self): diff --git a/tests/unit_tests/test_deplete_integrator.py b/tests/unit_tests/test_deplete_integrator.py index 40090264ae..c50ac66a94 100644 --- a/tests/unit_tests/test_deplete_integrator.py +++ b/tests/unit_tests/test_deplete_integrator.py @@ -11,6 +11,8 @@ import os from unittest.mock import MagicMock import numpy as np +from uncertainties import ufloat + from openmc.deplete import (ReactionRates, Results, ResultsList, comm, OperatorResult) @@ -74,8 +76,8 @@ 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) From d28546bcd671d7905d59ff3fa66cbb8ae36a54c5 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Fri, 26 Jul 2019 12:05:06 -0500 Subject: [PATCH 013/137] Add SI_Integrator, SI_CELI_Integrator classes; remove si_celi The si_celi depletion function has been removed. The SI-CELI method can be implemented with >>> from openmc.deplete import SI_CELI_Integrator >>> SI_CELI_Integrator(op, dt, power, stages).integrate() The stages parameter is optional, and defaults to 10 to be consistent with the previous default for si_celi. The SI_CELI_Integrator inherits from the new abstract base class SI_Integrator. There are a few differences in how the SI-based methods perform the integration. - The initial, t=0.0, i=0, no restart transport solution is scaled by the number of stages. - The SI methods also do not perform a new transport solution at each successive iteration [t>0, i>0]. Instead, the reaction rates are pulled from the last stage of the previous step. - There is no final transport solution once all the depletion steps have been taken. The final reaction rates are saved as those from the last stage of the last depletion step. The si_celi function has been removed from documentation and testing, and replaced with the SI_CELI_Integrator. --- docs/source/pythonapi/deplete.rst | 2 +- openmc/deplete/integrator/abc.py | 84 +++++++++++++++ openmc/deplete/integrator/si_celi.py | 131 +++++++++-------------- tests/unit_tests/test_deplete_restart.py | 6 +- tests/unit_tests/test_deplete_si_celi.py | 6 +- 5 files changed, 143 insertions(+), 86 deletions(-) diff --git a/docs/source/pythonapi/deplete.rst b/docs/source/pythonapi/deplete.rst index feb2f6dd63..60628a61b6 100644 --- a/docs/source/pythonapi/deplete.rst +++ b/docs/source/pythonapi/deplete.rst @@ -16,7 +16,6 @@ transport-depletion coupling algorithms `_. :nosignatures: :template: myfunction.rst - integrator.si_celi integrator.si_leqi .. autosummary:: @@ -30,6 +29,7 @@ transport-depletion coupling algorithms `_. integrator.CELIIntegrator integrator.EPC_RK4_Integrator integrator.LEQIIntegrator + integrator.SI_CELI_Integrator Each of these functions expects a "transport operator" to be passed. An operator specific to OpenMC is available using the following class: diff --git a/openmc/deplete/integrator/abc.py b/openmc/deplete/integrator/abc.py index 07151d13b4..4223119643 100644 --- a/openmc/deplete/integrator/abc.py +++ b/openmc/deplete/integrator/abc.py @@ -149,3 +149,87 @@ class Integrator(ABC): Results.save( self.operator, conc_list, results_list, time_list, power, index, proc_time) + + +class SI_Integrator(Integrator): + """Abstract 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 + """ + def __init__(self, operator, timesteps, power=None, power_density=None, + n_steps=10): + """ + 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_stages : int, optional + Number of stages. Default : 10 + """ + + def __init__(self, operator, timesteps, power=None, power_density=None, + n_stages=10): + super().__init__(operator, timesteps, power, power_density) + self.n_stages = n_stages + + def _get_bos_data_from_openmc(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_openmc( + 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._ires = 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_openmc(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() + + self._save_results( + conc_list, res_list, [t, t + dt], p, self._ires + i, + proc_time) + + t += dt + + # No final simulation for SIE, use last iteration results + self._save_results( + [conc], [res_list[-1]], [t, t], p, self._ires + len(self)) diff --git a/openmc/deplete/integrator/si_celi.py b/openmc/deplete/integrator/si_celi.py index 872863ef22..4d6ce2f089 100644 --- a/openmc/deplete/integrator/si_celi.py +++ b/openmc/deplete/integrator/si_celi.py @@ -1,103 +1,76 @@ """The SI-CE/LI CFQ4 integrator.""" import copy -from collections.abc import Iterable from uncertainties import ufloat +from .abc import SI_Integrator from .cram import timed_deplete -from ..results import Results -from ..abc import OperatorResult from .celi import _celi_f1, _celi_f2 +from ..abc import OperatorResult +from ..results import Results -def si_celi(operator, timesteps, power=None, power_density=None, - print_out=True, m=10): - r"""Deplete using the SI-CE/LI CFQ4 algorithm. +class SI_CELI_Integrator(SI_Integrator): + r"""Deplete using the si-ce/li cfq4 algorithm. - Implements the Stochastic Implicit CE/LI Predictor-Corrector algorithm using + 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 + 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] + def __call__(self, bos_conc, bos_rates, dt, power, _i): + """Perform the integration across one time step - if not isinstance(power, Iterable): - power = [power]*len(timesteps) + 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 [W] + _i : int + Current depletion step index. Unused - # 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) + Returns + ------- + proc_time : float + Time spent in CRAM routines for all materials + 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) - # 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]] + # Begin iteration + for j in range(self.n_stages + 1): + inter_res = self.operator(inter_conc, power) - # Get rates - op_results = [operator.prev_res[-1]] - op_results[0].rates = op_results[0].rates[0] + 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) - # Set first stage value of keff - op_results[0].k = ufloat(*op_results[0].k[0]) + 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 - # 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)) + # end iteration + return proc_time, [eos_conc, inter_conc], [res_bar] def si_celi_inner(operator, x, op_results, p, i, i_res, t, dt, print_out, m=10): diff --git a/tests/unit_tests/test_deplete_restart.py b/tests/unit_tests/test_deplete_restart.py index 6175757201..01ee56a828 100644 --- a/tests/unit_tests/test_deplete_restart.py +++ b/tests/unit_tests/test_deplete_restart.py @@ -8,7 +8,7 @@ from pytest import approx import openmc.deplete from openmc.deplete import ( CECMIntegrator, PredictorIntegrator, CELIIntegrator, LEQIIntegrator, - EPC_RK4_Integrator, CF4Integrator + EPC_RK4_Integrator, CF4Integrator, SI_CELI_Integrator ) from tests import dummy_operator @@ -341,7 +341,7 @@ def test_restart_si_celi(run_in_tmpdir): # Perform simulation dt = [0.75] power = 1.0 - openmc.deplete.si_celi(op, dt, power, print_out=False) + SI_CELI_Integrator(op, dt, power).integrate() # Load the files prev_res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") @@ -351,7 +351,7 @@ def test_restart_si_celi(run_in_tmpdir): op.output_dir = output_dir # Perform restarts simulation - openmc.deplete.si_celi(op, dt, power, print_out=False) + SI_CELI_Integrator(op, dt, power).integrate() # Load the files res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") diff --git a/tests/unit_tests/test_deplete_si_celi.py b/tests/unit_tests/test_deplete_si_celi.py index 71679439c5..954c9d8907 100644 --- a/tests/unit_tests/test_deplete_si_celi.py +++ b/tests/unit_tests/test_deplete_si_celi.py @@ -4,7 +4,7 @@ These tests integrate a simple test problem described in dummy_geometry.py. """ from pytest import approx -import openmc.deplete +from openmc.deplete import SI_CELI_Integrator, ResultsList from tests import dummy_operator @@ -18,10 +18,10 @@ def test_si_celi(run_in_tmpdir): # 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) + SI_CELI_Integrator(op, dt, power).integrate() # Load the files - res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") + res = ResultsList(op.output_dir / "depletion_results.h5") _, y1 = res.get_atoms("1", "1") _, y2 = res.get_atoms("1", "2") From 791f7c78b783ecd3ed94b74c8ba58ef3ac488ecc Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Mon, 29 Jul 2019 08:36:09 -0500 Subject: [PATCH 014/137] Return BOS concentration as list of vectors from restarts Operator.initial_condition return list of vectors representing the initial composition for a burnable material as an element in the list. This format is expected from depletion solvers as well. Previously, when pulling from the previous results on an operator, the initial concentration was returned simply as a 2D array of compositions. This commit converts the 2D array to a list of vectors to be consistent across the integration schemes. --- openmc/deplete/integrator/abc.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openmc/deplete/integrator/abc.py b/openmc/deplete/integrator/abc.py index 4223119643..24bd06395e 100644 --- a/openmc/deplete/integrator/abc.py +++ b/openmc/deplete/integrator/abc.py @@ -97,7 +97,9 @@ class Integrator(ABC): def _get_bos_data_from_restart(self, step_index, step_power, bos_conc): res = self.operator.prev_res[-1] - bos_conc = res.data[0] + # Depletion methods expect list of arrays + # See Operator.__call__, Operator.initial_condition + bos_conc = list(res.data[0]) rates = res.rates[0] k = ufloat(res.k[0,0], res.k[0, 1]) From 1f3e7fdac1e0433d38b82ff13f5d585754fac0de Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Mon, 29 Jul 2019 10:29:24 -0500 Subject: [PATCH 015/137] Remove si_leqi in favor of SI_LEQI_Integrator The depletion function openmc.deplete.si_leqi has been removed in favor of the SI_LEQI_Integrator class. The same depletion scheme can be obtained with the following commands: >>> leqi = openmc.deplete.SI_LEQI_Integrator(operator, dt, power) >>> leqi.integrate() The expression can be onlined for compactness. The si_celi_inner function has been removed completely now, as the SI_CELI iteration is performed by directly calling SI_CELI_Integrator.__call__ through the SI_LEQI_Integrator. This is similar to how the LEQIIntegrator handles the initial steps. Tests have been updated to use this class, and the class has been added to the documentation. No pure-function integration schemes exist anymore. --- docs/source/pythonapi/deplete.rst | 8 +- openmc/deplete/integrator/si_celi.py | 68 -------- openmc/deplete/integrator/si_leqi.py | 197 ++++++++--------------- tests/unit_tests/test_deplete_restart.py | 7 +- tests/unit_tests/test_deplete_si_leqi.py | 6 +- 5 files changed, 75 insertions(+), 211 deletions(-) diff --git a/docs/source/pythonapi/deplete.rst b/docs/source/pythonapi/deplete.rst index 60628a61b6..aa12884111 100644 --- a/docs/source/pythonapi/deplete.rst +++ b/docs/source/pythonapi/deplete.rst @@ -11,13 +11,6 @@ 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 - - integrator.si_leqi - .. autosummary:: :toctree: generated :nosignatures: @@ -30,6 +23,7 @@ transport-depletion coupling algorithms `_. integrator.EPC_RK4_Integrator integrator.LEQIIntegrator integrator.SI_CELI_Integrator + integrator.SI_LEQI_Integrator Each of these functions expects a "transport operator" to be passed. An operator specific to OpenMC is available using the following class: diff --git a/openmc/deplete/integrator/si_celi.py b/openmc/deplete/integrator/si_celi.py index 4d6ce2f089..dde3cfb812 100644 --- a/openmc/deplete/integrator/si_celi.py +++ b/openmc/deplete/integrator/si_celi.py @@ -71,71 +71,3 @@ class SI_CELI_Integrator(SI_Integrator): # end iteration return proc_time, [eos_conc, inter_conc], [res_bar] - - -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 index 86b184038f..bd42104ea1 100644 --- a/openmc/deplete/integrator/si_leqi.py +++ b/openmc/deplete/integrator/si_leqi.py @@ -6,15 +6,15 @@ from itertools import repeat from uncertainties import ufloat -from .si_celi import si_celi_inner +from .abc import SI_Integrator +from .si_celi import SI_CELI_Integrator 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): +class SI_LEQI_Integrator(SI_Integrator): r"""Deplete using the SI-LE/QI CFQ4 algorithm. Implements the Stochastic Implicit LE/QI Predictor-Corrector algorithm using @@ -22,138 +22,75 @@ def si_leqi(operator, timesteps, power=None, power_density=None, 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 + + 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 [W] + i : int + Current depletion step index + + Returns + ------- + proc_time : float + Time spent in CRAM routines for all materials + 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._ires <= 1: + self._prev_rates = bos_rates + # Perform CELI for initial steps + return SI_CELI_Integrator.__call__( + self, bos_conc, bos_rates, dt, power, i) + prev_res = self.operator.prev_res[-2] + prevdt = self.timesteps[i] - prev_res.time[0] + self._prev_rates = prev_res.rates[0] else: - power = [i*operator.heavy_metal for i in power_density] + prevdt = self.timesteps[i - 1] - if not isinstance(power, Iterable): - power = [power]*len(timesteps) + # Perform remaining LE/QI + inputs = list(zip(self._prev_rates, bos_rates, + repeat(prevdt), 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) - # 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) + proc_time += time1 + inter_conc = copy.deepcopy(eos_conc) - # 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]] + for j in range(self.n_stages + 1): + inter_res = self.operator(inter_conc, power) - # Get rates - op_results = [operator.prev_res[-1]] - op_results[0].rates = op_results[0].rates[0] + 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) - # Set first stage value of keff - op_results[0].k = ufloat(*op_results[0].k[0]) + inputs = list(zip(self._prev_rates, bos_rates, res_bar.rates, + repeat(prevdt), 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 - # 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)) + return proc_time, [eos_conc, inter_conc], [res_bar] diff --git a/tests/unit_tests/test_deplete_restart.py b/tests/unit_tests/test_deplete_restart.py index 01ee56a828..78a910d416 100644 --- a/tests/unit_tests/test_deplete_restart.py +++ b/tests/unit_tests/test_deplete_restart.py @@ -8,7 +8,7 @@ from pytest import approx import openmc.deplete from openmc.deplete import ( CECMIntegrator, PredictorIntegrator, CELIIntegrator, LEQIIntegrator, - EPC_RK4_Integrator, CF4Integrator, SI_CELI_Integrator + EPC_RK4_Integrator, CF4Integrator, SI_CELI_Integrator, SI_LEQI_Integrator ) from tests import dummy_operator @@ -380,7 +380,8 @@ def test_restart_si_leqi(run_in_tmpdir): # Perform simulation dt = [0.75] power = 1.0 - openmc.deplete.si_leqi(op, dt, power, print_out=False) + nstages = 10 + SI_LEQI_Integrator(op, dt, power, nstages).integrate() # Load the files prev_res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") @@ -390,7 +391,7 @@ def test_restart_si_leqi(run_in_tmpdir): op.output_dir = output_dir # Perform restarts simulation - openmc.deplete.si_leqi(op, dt, power, print_out=False) + SI_LEQI_Integrator(op, dt, power, nstages).integrate() # Load the files res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") diff --git a/tests/unit_tests/test_deplete_si_leqi.py b/tests/unit_tests/test_deplete_si_leqi.py index 1396065c0f..c26c06d035 100644 --- a/tests/unit_tests/test_deplete_si_leqi.py +++ b/tests/unit_tests/test_deplete_si_leqi.py @@ -4,7 +4,7 @@ These tests integrate a simple test problem described in dummy_geometry.py. """ from pytest import approx -import openmc.deplete +from openmc.deplete import SI_LEQI_Integrator, ResultsList from tests import dummy_operator @@ -18,10 +18,10 @@ def test_si_leqi(run_in_tmpdir): # 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) + SI_LEQI_Integrator(op, dt, power, 10).integrate() # Load the files - res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") + res = ResultsList(op.output_dir / "depletion_results.h5") _, y1 = res.get_atoms("1", "1") _, y2 = res.get_atoms("1", "2") From d5fa7b5c0091abc9ad124dc996d284dc9c001cc7 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Mon, 29 Jul 2019 14:46:02 -0500 Subject: [PATCH 016/137] Set correct index for final nuclides in restart tests Many repetitions of the following change: - assert y1[3] == approx(s2[0]) - assert y2[3] == approx(s2[1]) + assert y1[2] == approx(s2[0]) + assert y2[2] == approx(s2[1]) This causes many of the restart tests to fail, because all schemes but the predictor save repeated data under restart conditions. Performing one normal depletion event, non-restarted, and then one restart event, as these tests do, should produce vectors for time and densities like: t = [t0, t1, t2] a = [a0, a1, a2] | ^ after restart ^ non-restart run With this, the current tests would fail due to an IndexError, as the atom vectors should only have three elements. Instead, non-predictor schemes return vectors t = [t0, t1, t1, t2] a = [a0, a1, a1, a2] and one can access a[3] to obtain the desired EOS concentrations. --- tests/unit_tests/test_deplete_restart.py | 28 ++++++++++++------------ 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/tests/unit_tests/test_deplete_restart.py b/tests/unit_tests/test_deplete_restart.py index 78a910d416..493828fd8c 100644 --- a/tests/unit_tests/test_deplete_restart.py +++ b/tests/unit_tests/test_deplete_restart.py @@ -90,8 +90,8 @@ def test_restart_cecm(run_in_tmpdir): 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[2] == approx(s2[0]) + assert y2[2] == approx(s2[1]) def test_restart_predictor_cecm(run_in_tmpdir): @@ -211,8 +211,8 @@ def test_restart_cf4(run_in_tmpdir): 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[2] == approx(s2[0]) + assert y2[2] == approx(s2[1]) def test_restart_epc_rk4(run_in_tmpdir): @@ -250,8 +250,8 @@ def test_restart_epc_rk4(run_in_tmpdir): 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[2] == approx(s2[0]) + assert y2[2] == approx(s2[1]) def test_restart_celi(run_in_tmpdir): @@ -289,8 +289,8 @@ def test_restart_celi(run_in_tmpdir): 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[2] == approx(s2[0]) + assert y2[2] == approx(s2[1]) def test_restart_leqi(run_in_tmpdir): @@ -328,8 +328,8 @@ def test_restart_leqi(run_in_tmpdir): 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[2] == approx(s2[0]) + assert y2[2] == approx(s2[1]) def test_restart_si_celi(run_in_tmpdir): """Integral regression test of integrator algorithm using SI-CELI.""" @@ -366,8 +366,8 @@ def test_restart_si_celi(run_in_tmpdir): 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[2] == approx(s2[0]) + assert y2[2] == approx(s2[1]) def test_restart_si_leqi(run_in_tmpdir): @@ -406,5 +406,5 @@ def test_restart_si_leqi(run_in_tmpdir): 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[2] == approx(s2[0]) + assert y2[2] == approx(s2[1]) From afe1b5b68ced2b07991097132e16fe9f17bbf6ec Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Tue, 30 Jul 2019 08:46:23 -0500 Subject: [PATCH 017/137] Fix restart integration offset bugs Changes in d32139b993053912ba0d8117424d1585eb0aed0b highlighted the repeated data that was saved during non-predictor restart runs. All Integrator subclasses now have the same _get_bos_data_from_openmc and _get_bos_data_from_restart methods, where the restart index ``self._ires`` is one less than the number of previous results stored on the Operator. This allows all schemes to write their data in the correct index during restart calculations. A side benefit of this is that now, the Predictor implementation can rely entirely on the updated Integrator, without the need for re-writing the integrate method nor __init__ method. --- openmc/deplete/integrator/abc.py | 2 +- openmc/deplete/integrator/leqi.py | 2 +- openmc/deplete/integrator/predictor.py | 76 ++------------------------ openmc/deplete/integrator/si_leqi.py | 2 +- 4 files changed, 7 insertions(+), 75 deletions(-) diff --git a/openmc/deplete/integrator/abc.py b/openmc/deplete/integrator/abc.py index 24bd06395e..ca21f2a1b9 100644 --- a/openmc/deplete/integrator/abc.py +++ b/openmc/deplete/integrator/abc.py @@ -110,7 +110,7 @@ class Integrator(ABC): 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) + 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""" diff --git a/openmc/deplete/integrator/leqi.py b/openmc/deplete/integrator/leqi.py index 4b1c072b52..4bf4cf4d90 100644 --- a/openmc/deplete/integrator/leqi.py +++ b/openmc/deplete/integrator/leqi.py @@ -66,7 +66,7 @@ class LEQIIntegrator(Integrator): simulation """ if i == 0: - if self._ires <= 1: # need at least previous transport solution + if self._ires < 1: # need at least previous transport solution self._prev_rates = bos_rates return CELIIntegrator.__call__( self, bos_conc, bos_rates, dt, power, i) diff --git a/openmc/deplete/integrator/predictor.py b/openmc/deplete/integrator/predictor.py index 45783b2793..0e4dce423e 100644 --- a/openmc/deplete/integrator/predictor.py +++ b/openmc/deplete/integrator/predictor.py @@ -40,32 +40,7 @@ class PredictorIntegrator(Integrator): is not speficied. """ - def __init__(self, operator, timesteps, power=None, power_density=None): - """ - 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. - """ - super().__init__(operator, timesteps, power, power_density) - self._dep_proc_time = None - - def __call__(self, conc, rates, dt, power): + def __call__(self, conc, rates, dt, power, _i=None): """Perform the integration across one time step Parameters @@ -78,6 +53,8 @@ class PredictorIntegrator(Integrator): Time in [s] for the entire depletion interval power : float Power of the system [W] + _i : int or None + Iteration index. Not used Returns ------- @@ -91,49 +68,4 @@ class PredictorIntegrator(Integrator): """ proc_time, conc_end = timed_deplete(self.chain, conc, rates, dt) - return proc_time, conc_end, [] - - 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 _get_bos_data_from_openmc(self, step_index, step_power, prev_conc): - conc = deepcopy(prev_conc) - res = self.operator(conc, step_power) - if step_index == len(self) - 1: - tvec = [self.timesteps[step_index]] * 2 - else: - tvec = self.timesteps[step_index:step_index + 2] - - self._save_results( - [conc], [res], tvec, step_power, - self._istart + step_index, self._dep_proc_time) - return conc, res - - def integrate(self): - """Perform the entire depletion process across all steps""" - - with self.operator as conc: - t, self._istart = 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_openmc(i, p, conc) - else: - conc, res = self._get_bos_data_from_restart(i, p, conc) - - # __call__ returns empty list since there aren't - # intermediate transport solutions - self._dep_proc_time, conc, _res_list = self( - conc, res.rates, dt, p) - - t += dt - - # Final simulation - res_list = [self.operator(conc, p)] - self._save_results( - [conc], res_list, [t, t], p, - self._istart + len(self), self._dep_proc_time) + return proc_time, [conc_end], [] diff --git a/openmc/deplete/integrator/si_leqi.py b/openmc/deplete/integrator/si_leqi.py index bd42104ea1..015650269d 100644 --- a/openmc/deplete/integrator/si_leqi.py +++ b/openmc/deplete/integrator/si_leqi.py @@ -53,7 +53,7 @@ class SI_LEQI_Integrator(SI_Integrator): simulation """ if i == 0: - if self._ires <= 1: + if self._ires < 1: self._prev_rates = bos_rates # Perform CELI for initial steps return SI_CELI_Integrator.__call__( From c4567b6d9d17c6d81aa5dfb9074d4ecad3657e29 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Tue, 30 Jul 2019 09:19:26 -0500 Subject: [PATCH 018/137] Remove print_out options to Operator.__call__, deplete Time spent running openmc is already presented in after each transport run. Time spent performing depletion is written in the depletion file. --- openmc/deplete/abc.py | 4 +--- openmc/deplete/integrator/cram.py | 17 +++++------------ openmc/deplete/operator.py | 11 +---------- 3 files changed, 7 insertions(+), 25 deletions(-) diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py index 5e2f1dc64f..f1321379d9 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -87,15 +87,13 @@ class TransportOperator(metaclass=ABCMeta): self.chain = Chain.from_xml(chain_file, fission_q) @abstractmethod - def __call__(self, vec, print_out=True): + def __call__(self, vec): """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. Returns ------- diff --git a/openmc/deplete/integrator/cram.py b/openmc/deplete/integrator/cram.py index f5da6d696b..c57ff53132 100644 --- a/openmc/deplete/integrator/cram.py +++ b/openmc/deplete/integrator/cram.py @@ -14,7 +14,7 @@ import scipy.sparse.linalg as sla from .. import comm -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,29 +27,22 @@ 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 of two variables: ``chain`` and ``rates``. + 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() # 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) - return x_result diff --git a/openmc/deplete/operator.py b/openmc/deplete/operator.py index e9c41093aa..6ee992c4fd 100644 --- a/openmc/deplete/operator.py +++ b/openmc/deplete/operator.py @@ -153,7 +153,7 @@ class Operator(TransportOperator): self.reaction_rates = ReactionRates( self.local_mats, self._burnable_nucs, self.chain.reactions) - def __call__(self, vec, power, print_out=True): + def __call__(self, vec, power): """Runs a simulation. Parameters @@ -162,8 +162,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 ------- @@ -192,13 +190,6 @@ 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) def _differentiate_burnable_mats(self): From d7bcf60eb4fd4031abf4dd0f2cfac423ca694155 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Tue, 30 Jul 2019 10:13:54 -0500 Subject: [PATCH 019/137] Store min/max data temperatures. Use in Cell::set_temperature to check bounds --- include/openmc/nuclide.h | 6 ++++++ src/cell.cpp | 12 +++++++++++- src/cross_sections.cpp | 6 ++++++ src/finalize.cpp | 2 ++ src/nuclide.cpp | 10 +++++++++- 5 files changed, 34 insertions(+), 2 deletions(-) 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/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..f9bfe1d229 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"); + write_message("Maximum neutron data temperature: " + + std::to_string(data::temperature_max) + " K"); + // If the user wants multipole, make sure we found a multipole library. if (settings::temperature_multipole) { bool mp_found = false; diff --git a/src/finalize.cpp b/src/finalize.cpp index 633cf2991d..120903f3fe 100644 --- a/src/finalize.cpp +++ b/src/finalize.cpp @@ -117,6 +117,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); 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"}; From 9b7ab0bacb3e2781d64b12756c0e5d416fffdad5 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Thu, 25 Jul 2019 15:49:14 -0500 Subject: [PATCH 020/137] Expose/improve EnergyFunctionFilter through C-API Add three functions that can be used to modify EnergyFunctionFilters through the C-API: - openmc_energyfunc_filter_set_data: set energy and y data - openmc_energyfunc_filter_get_energy: obtain energies used in interpolation - openmc_energyfunc_filter_get_y: obtain ordinate values These functions are modeled after openmc_energy_filter_[get|set]_bins. The set_data function relies upon the new EnergyFunctionFilter::set_data function, which is analogous to EnergyFilter::set_bins function. Checks are performed to make sure the energy and ordinate vectors are of equal size before resetting and populating energy and y private members. An EnergyFunctionFilter did exist in openmc.capi, and has now been flushed out to provide a better __init__ method, as well as properties for retrieving energies and ordinates for interpolation. --- include/openmc/capi.h | 4 + include/openmc/tallies/filter_energyfunc.h | 7 ++ openmc/capi/filter.py | 45 ++++++++++ src/tallies/filter_energyfunc.cpp | 97 +++++++++++++++++++++- tests/unit_tests/test_capi.py | 31 ++++++- 5 files changed, 180 insertions(+), 4 deletions(-) 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/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/openmc/capi/filter.py b/openmc/capi/filter.py index bda3c3178f..f1f0cdc46d 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,39 @@ 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_interp_data(energy, y) + + def set_interp_data(self, energy, y): + 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/src/tallies/filter_energyfunc.cpp b/src/tallies/filter_energyfunc.cpp index 9b28e0f765..cf74d9836c 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/tests/unit_tests/test_capi.py b/tests/unit_tests/test_capi.py index 6953186eb9..31db130db3 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() @@ -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] + assert len(efunc.y) == 2 + assert efunc.y == [0.0, 2.0] + + 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_interp_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): From 9d952c3c5807e5426155d4ae5bd79a06ea1bd48a Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Fri, 26 Jul 2019 10:14:20 -0500 Subject: [PATCH 021/137] Use a.all() for equality in capi EnergyFunctionFilter tests --- tests/unit_tests/test_capi.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/unit_tests/test_capi.py b/tests/unit_tests/test_capi.py index 31db130db3..57978838f7 100644 --- a/tests/unit_tests/test_capi.py +++ b/tests/unit_tests/test_capi.py @@ -188,9 +188,9 @@ 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] + assert (efunc.energy == [0.0, 1.0]).all() assert len(efunc.y) == 2 - assert efunc.y == [0.0, 2.0] + assert (efunc.y == [0.0, 2.0]).all() def test_tally(capi_init): From 8d920b792a6d0b5f9bb4f97472cc274ff7065c88 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Mon, 29 Jul 2019 15:39:55 -0500 Subject: [PATCH 022/137] set_interp_data -> set_data for capi.EnergyFunctionFilter Add doctring to set_data method. Update test_capi.py with this change --- openmc/capi/filter.py | 13 +++++++++++-- src/tallies/filter_energyfunc.cpp | 6 +++--- tests/unit_tests/test_capi.py | 2 +- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/openmc/capi/filter.py b/openmc/capi/filter.py index f1f0cdc46d..92818a2aa1 100644 --- a/openmc/capi/filter.py +++ b/openmc/capi/filter.py @@ -221,9 +221,18 @@ class EnergyFunctionFilter(Filter): raise AttributeError("Need both energy and y or neither") super().__init__(uid, new, index) if energy is not None: - self.set_interp_data(energy, y) + self.set_data(energy, y) - def set_interp_data(self, 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)) diff --git a/src/tallies/filter_energyfunc.cpp b/src/tallies/filter_energyfunc.cpp index cf74d9836c..3e3fbd909c 100644 --- a/src/tallies/filter_energyfunc.cpp +++ b/src/tallies/filter_energyfunc.cpp @@ -94,7 +94,7 @@ EnergyFunctionFilter::text_label(int bin) const // C-API functions //============================================================================== -extern"C" int +extern "C" int openmc_energyfunc_filter_set_data(int32_t index, size_t n, const double* energy, const double* y) { @@ -116,7 +116,7 @@ openmc_energyfunc_filter_set_data(int32_t index, size_t n, const double* energy, return 0; } -extern"C" int +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 @@ -137,7 +137,7 @@ openmc_energyfunc_filter_get_energy(int32_t index, size_t *n, const double** ene return 0; } -extern"C" int +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 diff --git a/tests/unit_tests/test_capi.py b/tests/unit_tests/test_capi.py index 57978838f7..ed0bfd441d 100644 --- a/tests/unit_tests/test_capi.py +++ b/tests/unit_tests/test_capi.py @@ -234,7 +234,7 @@ def test_tally(capi_init): assert isinstance(t3_f, openmc.capi.EnergyFunctionFilter) assert len(t3_f.energy) == 2 assert len(t3_f.y) == 2 - t3_f.set_interp_data([0.0, 1.0, 2.0], [0.0, 1.0, 4.0]) + 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 From 51add8f6e9ec4e28f025e5f17940a44c8d92f64e Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Tue, 30 Jul 2019 09:25:15 -0500 Subject: [PATCH 023/137] Clean up changes introduced by Integrators Remove some unused imports, redeclared __init__, long lines, etc. --- openmc/deplete/integrator/abc.py | 17 ++++++++--------- openmc/deplete/integrator/cecm.py | 2 -- openmc/deplete/integrator/predictor.py | 2 -- openmc/deplete/integrator/si_celi.py | 10 ++++------ openmc/deplete/integrator/si_leqi.py | 4 +--- tests/unit_tests/test_deplete_integrator.py | 6 ++++-- 6 files changed, 17 insertions(+), 24 deletions(-) diff --git a/openmc/deplete/integrator/abc.py b/openmc/deplete/integrator/abc.py index ca21f2a1b9..585c96bdb8 100644 --- a/openmc/deplete/integrator/abc.py +++ b/openmc/deplete/integrator/abc.py @@ -101,7 +101,7 @@ class Integrator(ABC): # See Operator.__call__, Operator.initial_condition bos_conc = list(res.data[0]) rates = res.rates[0] - k = ufloat(res.k[0,0], res.k[0, 1]) + k = ufloat(res.k[0, 0], res.k[0, 1]) # Scale rates by ratio of powers rates *= step_power / res.power[0] @@ -110,7 +110,8 @@ class Integrator(ABC): 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 + 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""" @@ -181,14 +182,12 @@ class SI_Integrator(Integrator): 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_stages : int, optional - Number of stages. Default : 10 - """ - - def __init__(self, operator, timesteps, power=None, power_density=None, - n_stages=10): + n_steps : int, optional + Number of stochastic iterations per depletion interval. + Default : 10 + """ super().__init__(operator, timesteps, power, power_density) - self.n_stages = n_stages + self.n_steps = n_steps def _get_bos_data_from_openmc(self, step_index, step_power, bos_conc): reset_particles = False diff --git a/openmc/deplete/integrator/cecm.py b/openmc/deplete/integrator/cecm.py index b9b7dedc61..571c332cba 100644 --- a/openmc/deplete/integrator/cecm.py +++ b/openmc/deplete/integrator/cecm.py @@ -1,7 +1,5 @@ """The CE/CM integrator.""" -from textwrap import dedent - from .abc import Integrator from .cram import timed_deplete diff --git a/openmc/deplete/integrator/predictor.py b/openmc/deplete/integrator/predictor.py index 0e4dce423e..7234b375a1 100644 --- a/openmc/deplete/integrator/predictor.py +++ b/openmc/deplete/integrator/predictor.py @@ -1,7 +1,5 @@ """First-order predictor algorithm.""" -from copy import deepcopy - from .abc import Integrator from .cram import timed_deplete diff --git a/openmc/deplete/integrator/si_celi.py b/openmc/deplete/integrator/si_celi.py index dde3cfb812..12021e870e 100644 --- a/openmc/deplete/integrator/si_celi.py +++ b/openmc/deplete/integrator/si_celi.py @@ -2,20 +2,18 @@ import copy -from uncertainties import ufloat - from .abc import SI_Integrator from .cram import timed_deplete from .celi import _celi_f1, _celi_f2 from ..abc import OperatorResult -from ..results import Results class SI_CELI_Integrator(SI_Integrator): 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 `_. + 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 `_. @@ -52,7 +50,7 @@ class SI_CELI_Integrator(SI_Integrator): inter_conc = copy.deepcopy(eos_conc) # Begin iteration - for j in range(self.n_stages + 1): + for j in range(self.n_steps + 1): inter_res = self.operator(inter_conc, power) if j <= 1: diff --git a/openmc/deplete/integrator/si_leqi.py b/openmc/deplete/integrator/si_leqi.py index 015650269d..88e723b102 100644 --- a/openmc/deplete/integrator/si_leqi.py +++ b/openmc/deplete/integrator/si_leqi.py @@ -4,8 +4,6 @@ import copy from collections.abc import Iterable from itertools import repeat -from uncertainties import ufloat - from .abc import SI_Integrator from .si_celi import SI_CELI_Integrator from .leqi import _leqi_f1, _leqi_f2, _leqi_f3, _leqi_f4 @@ -75,7 +73,7 @@ class SI_LEQI_Integrator(SI_Integrator): proc_time += time1 inter_conc = copy.deepcopy(eos_conc) - for j in range(self.n_stages + 1): + for j in range(self.n_steps + 1): inter_res = self.operator(inter_conc, power) if j <= 1: diff --git a/tests/unit_tests/test_deplete_integrator.py b/tests/unit_tests/test_deplete_integrator.py index c50ac66a94..1750abd3d1 100644 --- a/tests/unit_tests/test_deplete_integrator.py +++ b/tests/unit_tests/test_deplete_integrator.py @@ -76,8 +76,10 @@ def test_results_save(run_in_tmpdir): t1 = [0.0, 1.0] t2 = [1.0, 2.0] - 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)] + 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) From a02feed171d9e59c908fab4bc725d0fd3d6dfedd Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Tue, 30 Jul 2019 14:03:30 -0500 Subject: [PATCH 024/137] Restrict depletion restart to schemes with common stages Each scheme is capable of different amounts of concentration and reaction data, depending on the number of intermediate transport solutions of interest. The PredictorIntegrator stores data from a single solution, while the CF4Integrator and EPC_RK4_Integrator store four stages. This discrepancy can cause issues when saving data in the depletion output file, as either some data is calculated and thrown away [more stages going to fewer stages] or excess space is not utilized [fewer stages going in to a restart that previously used more stages]. Each subclass of Integrator is expected to declare a _N_STAGES attribute that provides information on the number of stages expected. Iterative schemes like SI_CELI_Integrator only report two stages, for the initial concentration and first prediction, rather than at each iteration point. Logic that checks for consistent sizes is included in the initialization of the Integrators, and has been removed from Results.save --- openmc/deplete/integrator/abc.py | 18 +++++++++ openmc/deplete/integrator/cecm.py | 1 + openmc/deplete/integrator/celi.py | 1 + openmc/deplete/integrator/cf4.py | 1 + openmc/deplete/integrator/epc_rk4.py | 1 + openmc/deplete/integrator/leqi.py | 1 + openmc/deplete/integrator/predictor.py | 1 + openmc/deplete/integrator/si_celi.py | 2 + openmc/deplete/integrator/si_leqi.py | 1 + openmc/deplete/results.py | 11 +----- tests/unit_tests/test_deplete_restart.py | 49 ++++-------------------- 11 files changed, 36 insertions(+), 51 deletions(-) diff --git a/openmc/deplete/integrator/abc.py b/openmc/deplete/integrator/abc.py index 585c96bdb8..cb4ac13a25 100644 --- a/openmc/deplete/integrator/abc.py +++ b/openmc/deplete/integrator/abc.py @@ -34,6 +34,16 @@ class Integrator(ABC): initial heavy metal inventory to get total power if ``power`` is not speficied. """ + # Check number of stages previously used + if operator.prev_res is not None: + res = operator.prev_res[-1] + if res.data.shape[0] != self._N_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._N_STAGES)) self.operator = operator self.chain = operator.chain if not isinstance(timesteps, Iterable): @@ -84,6 +94,14 @@ class Integrator(ABC): simulations """ + @property + @abstractmethod + def _N_STAGES(self): + """Number of intermediate transport solutions + + Needed to ensure schemes are consistent with restarts + """ + def __iter__(self): return zip(self.timesteps, self.power) diff --git a/openmc/deplete/integrator/cecm.py b/openmc/deplete/integrator/cecm.py index 571c332cba..8155b55de8 100644 --- a/openmc/deplete/integrator/cecm.py +++ b/openmc/deplete/integrator/cecm.py @@ -22,6 +22,7 @@ class CECMIntegrator(Integrator): y_{n+1} &= \text{expm}(A_c h) y_n \end{aligned} """ + _N_STAGES = 2 def __call__(self, conc, rates, dt, power, _i=-1): """Integrate using CE/CM diff --git a/openmc/deplete/integrator/celi.py b/openmc/deplete/integrator/celi.py index 9346347a34..b6bbe231b8 100644 --- a/openmc/deplete/integrator/celi.py +++ b/openmc/deplete/integrator/celi.py @@ -23,6 +23,7 @@ class CELIIntegrator(Integrator): \text{expm}(\frac{5h}{12} A_0 + \frac{h}{12} A1) y_n \end{aligned} """ + _N_STAGES = 2 def __call__(self, bos_conc, rates, dt, power, _i=-1): """Perform the integration across one time step diff --git a/openmc/deplete/integrator/cf4.py b/openmc/deplete/integrator/cf4.py index 7e0b603ae1..96c97b067c 100644 --- a/openmc/deplete/integrator/cf4.py +++ b/openmc/deplete/integrator/cf4.py @@ -28,6 +28,7 @@ class CF4Integrator(Integrator): \text{expm}(-1/12 F_1 + 1/6 F_2 + 1/6 F_3 + 1/4 F_4) y_0 \end{aligned} """ + _N_STAGES = 4 def __call__(self, bos_conc, bos_rates, dt, power, i): """Perform the integration across one time step diff --git a/openmc/deplete/integrator/epc_rk4.py b/openmc/deplete/integrator/epc_rk4.py index ce6223c448..879f176bab 100644 --- a/openmc/deplete/integrator/epc_rk4.py +++ b/openmc/deplete/integrator/epc_rk4.py @@ -23,6 +23,7 @@ class EPC_RK4_Integrator(Integrator): y_4 &= \text{expm}(1/6 F_1 + 1/3 F_2 + 1/3 F_3 + 1/6 F_4) y_0 \end{aligned} """ + _N_STAGES = 4 def __call__(self, conc, rates, dt, power, _i): """Perform the integration across one time step diff --git a/openmc/deplete/integrator/leqi.py b/openmc/deplete/integrator/leqi.py index 4bf4cf4d90..c4a819d389 100644 --- a/openmc/deplete/integrator/leqi.py +++ b/openmc/deplete/integrator/leqi.py @@ -37,6 +37,7 @@ class LEQIIntegrator(Integrator): It is initialized using the CE/LI algorithm. """ + _N_STAGES = 2 def __call__(self, bos_conc, bos_rates, dt, power, i): """Perform the integration across one time step diff --git a/openmc/deplete/integrator/predictor.py b/openmc/deplete/integrator/predictor.py index 7234b375a1..d534645b23 100644 --- a/openmc/deplete/integrator/predictor.py +++ b/openmc/deplete/integrator/predictor.py @@ -37,6 +37,7 @@ class PredictorIntegrator(Integrator): initial heavy metal inventory to get total power if ``power`` is not speficied. """ + _N_STAGES = 1 def __call__(self, conc, rates, dt, power, _i=None): """Perform the integration across one time step diff --git a/openmc/deplete/integrator/si_celi.py b/openmc/deplete/integrator/si_celi.py index 12021e870e..bfe4f7586b 100644 --- a/openmc/deplete/integrator/si_celi.py +++ b/openmc/deplete/integrator/si_celi.py @@ -18,6 +18,8 @@ class SI_CELI_Integrator(SI_Integrator): detailed algorithm can be found in section 3.2 in `colin josey's thesis `_. """ + _N_STAGES = 2 + def __call__(self, bos_conc, bos_rates, dt, power, _i): """Perform the integration across one time step diff --git a/openmc/deplete/integrator/si_leqi.py b/openmc/deplete/integrator/si_leqi.py index 88e723b102..702b098efd 100644 --- a/openmc/deplete/integrator/si_leqi.py +++ b/openmc/deplete/integrator/si_leqi.py @@ -21,6 +21,7 @@ class SI_LEQI_Integrator(SI_Integrator): Detailed algorithm can be found in Section 3.2 in `Colin Josey's thesis `_. """ + _N_STAGES = 2 def __call__(self, bos_conc, bos_rates, dt, power, i): """Perform the integration across one time step diff --git a/openmc/deplete/results.py b/openmc/deplete/results.py index a2462b80a1..2b5bde0e57 100644 --- a/openmc/deplete/results.py +++ b/openmc/deplete/results.py @@ -425,16 +425,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,7 +435,7 @@ 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.nominal_value, r.k.std_dev) for r in op_results] results.rates = [r.rates for r in op_results] diff --git a/tests/unit_tests/test_deplete_restart.py b/tests/unit_tests/test_deplete_restart.py index 493828fd8c..847ad9b838 100644 --- a/tests/unit_tests/test_deplete_restart.py +++ b/tests/unit_tests/test_deplete_restart.py @@ -4,7 +4,7 @@ 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 +from pytest import approx, raises import openmc.deplete from openmc.deplete import ( CECMIntegrator, PredictorIntegrator, CELIIntegrator, LEQIIntegrator, @@ -95,8 +95,7 @@ def test_restart_cecm(run_in_tmpdir): 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" @@ -114,25 +113,9 @@ def test_restart_predictor_cecm(run_in_tmpdir): op = dummy_operator.DummyOperator(prev_res) op.output_dir = output_dir - # Perform restarts simulation using the MCNPX/MCNP6 algorithm - cecm = CECMIntegrator(op, dt, power) - cecm.integrate() - - # 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 raises(ValueError, match="incompatible.* 1.*2"): + CECMIntegrator(op, dt, power) def test_restart_cecm_predictor(run_in_tmpdir): @@ -156,25 +139,9 @@ def test_restart_cecm_predictor(run_in_tmpdir): op = dummy_operator.DummyOperator(prev_res) op.output_dir = output_dir - # Perform restarts simulation using the predictor algorithm - PredictorIntegrator(op, dt, power).integrate() - - # 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 = [1.86872629872102, 1.395525772416039] - s2 = [3.32776806576, 2.391425905] - - 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 raises(ValueError, match="incompatible.* 2.*1"): + PredictorIntegrator(op, dt, power) def test_restart_cf4(run_in_tmpdir): """Integral regression test of integrator algorithm using CF4.""" From 2ee14a4a0f1bfb12302d1b5a03127f4f6d1bf08e Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Tue, 30 Jul 2019 15:02:55 -0500 Subject: [PATCH 025/137] Write new step-specific statepoint files at BOS, EOL Closes #1283 by writing a statepoint file at the beginning of every depletion step [BOS] and at the final transport simulation, [EOL]. These files are named "openmc_simulation_n.h5", where represents the current depletion step, including restart steps. The new files are written using the C API. It is worth noting that OpenMC will still write statepoint files after each intermediate transport simulation used by various depletion schemes. This commit makes no effort to move or delete these files, nor stop their creation. It is not clear how, using the python nor C API, one can stop OpenMC from writing a statepoint file. The source bank is not written to these files as this causes a segmentation fault. Comparisons of k_combined pull from all statepoints, reference depletion_results file, and test depletion_results file have been added to depletion regression test. --- openmc/deplete/integrator/abc.py | 15 +++++++++++---- tests/regression_tests/deplete/test.py | 17 ++++++++++++++++- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/openmc/deplete/integrator/abc.py b/openmc/deplete/integrator/abc.py index cb4ac13a25..94ed2a86c3 100644 --- a/openmc/deplete/integrator/abc.py +++ b/openmc/deplete/integrator/abc.py @@ -4,6 +4,7 @@ from collections.abc import Iterable from uncertainties import ufloat +from openmc.capi import statepoint_write from openmc.deplete import Results, OperatorResult @@ -111,6 +112,7 @@ class Integrator(ABC): def _get_bos_data_from_openmc(self, step_index, step_power, bos_conc): x = deepcopy(bos_conc) res = self.operator(x, step_power) + self._write_statepoint(step_index) return x, res def _get_bos_data_from_restart(self, step_index, step_power, bos_conc): @@ -160,17 +162,21 @@ class Integrator(ABC): res_list = [self.operator(conc, p)] self._save_results( [conc], res_list, [t, t], p, self._ires + len(self)) + self._write_statepoint(len(self)) def _save_results(self, conc_list, results_list, time_list, power, index, proc_time=None): - """Save the results at the end of of one step - - Abstracted to support the predictor's unique save location - """ + """Save the results at the end of of one step""" Results.save( self.operator, conc_list, results_list, time_list, power, index, proc_time) + def _write_statepoint(self, step_index): + """Use capi to write a statepoint for this index""" + statepoint_write( + "openmc_simulation_n{}.h5".format(step_index + self._ires), + write_source=False) + class SI_Integrator(Integrator): """Abstract for the Stochastic Implicit Euler integrators @@ -252,3 +258,4 @@ class SI_Integrator(Integrator): # No final simulation for SIE, use last iteration results self._save_results( [conc], [res_list[-1]], [t, t], p, self._ires + len(self)) + self._write_statepoint(len(self)) diff --git a/tests/regression_tests/deplete/test.py b/tests/regression_tests/deplete/test.py index 5f2d8cc454..59eca4a545 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' @@ -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) From b1ea5b89421f0793e8c61defaecd1674750d160a Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Tue, 30 Jul 2019 15:56:24 -0500 Subject: [PATCH 026/137] Add type and value checking to Integrators - SI_Integrator must be passed positive integer. - Number of powers computed must be equal to the number of time steps Added unit test to ensure that the right errors are raised. --- openmc/deplete/integrator/abc.py | 11 +++++-- tests/unit_tests/test_deplete_integrator.py | 35 +++++++++++++++++++-- 2 files changed, 42 insertions(+), 4 deletions(-) diff --git a/openmc/deplete/integrator/abc.py b/openmc/deplete/integrator/abc.py index 94ed2a86c3..26ca6b5819 100644 --- a/openmc/deplete/integrator/abc.py +++ b/openmc/deplete/integrator/abc.py @@ -1,9 +1,11 @@ from copy import deepcopy from abc import ABC, abstractmethod from collections.abc import Iterable +from numbers import Integral from uncertainties import ufloat +from openmc.checkvalue import check_type, check_greater_than from openmc.capi import statepoint_write from openmc.deplete import Results, OperatorResult @@ -60,9 +62,12 @@ class Integrator(ABC): power = [p * operator.heavy_metal for p in power_density] if not isinstance(power, Iterable): - # TODO Maybe use itertools.zip_longest? # 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 @@ -208,8 +213,10 @@ class SI_Integrator(Integrator): is not speficied. n_steps : int, optional Number of stochastic iterations per depletion interval. - Default : 10 + Must be greater than zero. Default : 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 diff --git a/tests/unit_tests/test_deplete_integrator.py b/tests/unit_tests/test_deplete_integrator.py index 1750abd3d1..69f6ee6797 100644 --- a/tests/unit_tests/test_deplete_integrator.py +++ b/tests/unit_tests/test_deplete_integrator.py @@ -12,9 +12,11 @@ from unittest.mock import MagicMock import numpy as np from uncertainties import ufloat +import pytest -from openmc.deplete import (ReactionRates, Results, ResultsList, comm, - OperatorResult) +from openmc.deplete import ( + ReactionRates, Results, ResultsList, comm, OperatorResult, + PredictorIntegrator, SI_CELI_Integrator) def test_results_save(run_in_tmpdir): @@ -99,3 +101,32 @@ 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"): + SI_CELI_Integrator(op, timesteps, [1], n_steps=2.5) + + with pytest.raises(ValueError, match="n_steps"): + SI_CELI_Integrator(op, timesteps, [1], n_steps=0) From f0727c654ae4e490e9d6a16c79ed9b7a67a0bfc1 Mon Sep 17 00:00:00 2001 From: zxkjack123 Date: Mon, 5 Aug 2019 11:22:16 -0500 Subject: [PATCH 027/137] fix a typo --- docs/source/usersguide/geometry.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 From 0fdef55d425656548217f22d3eddf3127e16781c Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Mon, 5 Aug 2019 16:08:11 -0500 Subject: [PATCH 028/137] Create ResultsList from file using from_hdf5 The new class method ResultsList.from_hdf5 should be used to load in results data, rather than passing the file name into the __init__ method. __init__ passes directly to list.__init__ now. The motivation for this is to resolve the depletion restart with MPI bug #1275. To do this, each Operator will create it's own ResultsList and distribute reaction rates and densities according to what materials are burned on this process. Rather than use a normal list, this Operator expects the various accessor methods like get_atoms to be present on self.prev_res --- openmc/deplete/results_list.py | 28 ++++++++++---- tests/regression_tests/deplete/test.py | 4 +- tests/unit_tests/test_deplete_cecm.py | 2 +- tests/unit_tests/test_deplete_celi.py | 2 +- tests/unit_tests/test_deplete_cf4.py | 2 +- tests/unit_tests/test_deplete_epc_rk4.py | 2 +- tests/unit_tests/test_deplete_integrator.py | 2 +- tests/unit_tests/test_deplete_leqi.py | 2 +- tests/unit_tests/test_deplete_predictor.py | 2 +- tests/unit_tests/test_deplete_restart.py | 40 ++++++++++---------- tests/unit_tests/test_deplete_resultslist.py | 2 +- tests/unit_tests/test_deplete_si_celi.py | 2 +- tests/unit_tests/test_deplete_si_leqi.py | 2 +- tests/unit_tests/test_transfer_volumes.py | 4 +- 14 files changed, 54 insertions(+), 42 deletions(-) diff --git a/openmc/deplete/results_list.py b/openmc/deplete/results_list.py index 0ce5b01584..8a0efcdbf1 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/tests/regression_tests/deplete/test.py b/tests/regression_tests/deplete/test.py index 059a44ea35..d81bf607b9 100644 --- a/tests/regression_tests/deplete/test.py +++ b/tests/regression_tests/deplete/test.py @@ -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: diff --git a/tests/unit_tests/test_deplete_cecm.py b/tests/unit_tests/test_deplete_cecm.py index dd8b769fe6..f979e0b4d9 100644 --- a/tests/unit_tests/test_deplete_cecm.py +++ b/tests/unit_tests/test_deplete_cecm.py @@ -21,7 +21,7 @@ def test_cecm(run_in_tmpdir): openmc.deplete.cecm(op, dt, power, print_out=False) # 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") _, y1 = res.get_atoms("1", "1") _, y2 = res.get_atoms("1", "2") diff --git a/tests/unit_tests/test_deplete_celi.py b/tests/unit_tests/test_deplete_celi.py index 47d3319fdb..ffa5c304c3 100644 --- a/tests/unit_tests/test_deplete_celi.py +++ b/tests/unit_tests/test_deplete_celi.py @@ -21,7 +21,7 @@ def test_celi(run_in_tmpdir): openmc.deplete.celi(op, dt, power, print_out=False) # 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") _, y1 = res.get_atoms("1", "1") _, y2 = res.get_atoms("1", "2") diff --git a/tests/unit_tests/test_deplete_cf4.py b/tests/unit_tests/test_deplete_cf4.py index 0c6199c3e5..aee373b406 100644 --- a/tests/unit_tests/test_deplete_cf4.py +++ b/tests/unit_tests/test_deplete_cf4.py @@ -21,7 +21,7 @@ def test_cf4(run_in_tmpdir): openmc.deplete.cf4(op, dt, power, print_out=False) # 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") _, y1 = res.get_atoms("1", "1") _, y2 = res.get_atoms("1", "2") diff --git a/tests/unit_tests/test_deplete_epc_rk4.py b/tests/unit_tests/test_deplete_epc_rk4.py index ea687bfd5d..1b0af0365a 100644 --- a/tests/unit_tests/test_deplete_epc_rk4.py +++ b/tests/unit_tests/test_deplete_epc_rk4.py @@ -21,7 +21,7 @@ def test_epc_rk4(run_in_tmpdir): openmc.deplete.epc_rk4(op, dt, power, print_out=False) # 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") _, y1 = res.get_atoms("1", "1") _, y2 = res.get_atoms("1", "2") diff --git a/tests/unit_tests/test_deplete_integrator.py b/tests/unit_tests/test_deplete_integrator.py index 40090264ae..b8429392ff 100644 --- a/tests/unit_tests/test_deplete_integrator.py +++ b/tests/unit_tests/test_deplete_integrator.py @@ -80,7 +80,7 @@ def test_results_save(run_in_tmpdir): 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): diff --git a/tests/unit_tests/test_deplete_leqi.py b/tests/unit_tests/test_deplete_leqi.py index a26329cf4a..ac477a36ec 100644 --- a/tests/unit_tests/test_deplete_leqi.py +++ b/tests/unit_tests/test_deplete_leqi.py @@ -21,7 +21,7 @@ def test_leqi(run_in_tmpdir): openmc.deplete.leqi(op, dt, power, print_out=False) # 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") _, y1 = res.get_atoms("1", "1") _, y2 = res.get_atoms("1", "2") diff --git a/tests/unit_tests/test_deplete_predictor.py b/tests/unit_tests/test_deplete_predictor.py index 6af6f8bca4..092ebbc35e 100644 --- a/tests/unit_tests/test_deplete_predictor.py +++ b/tests/unit_tests/test_deplete_predictor.py @@ -21,7 +21,7 @@ def test_predictor(run_in_tmpdir): openmc.deplete.predictor(op, dt, power, print_out=False) # 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") _, y1 = res.get_atoms("1", "1") _, y2 = res.get_atoms("1", "2") diff --git a/tests/unit_tests/test_deplete_restart.py b/tests/unit_tests/test_deplete_restart.py index ec62064a4e..4c0c68a2e3 100644 --- a/tests/unit_tests/test_deplete_restart.py +++ b/tests/unit_tests/test_deplete_restart.py @@ -23,7 +23,7 @@ def test_restart_predictor(run_in_tmpdir): openmc.deplete.predictor(op, dt, power, print_out=False) # 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) @@ -33,7 +33,7 @@ def test_restart_predictor(run_in_tmpdir): openmc.deplete.predictor(op, dt, power, print_out=False) # 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") _, y1 = res.get_atoms("1", "1") _, y2 = res.get_atoms("1", "2") @@ -62,7 +62,7 @@ def test_restart_cecm(run_in_tmpdir): openmc.deplete.cecm(op, dt, power, print_out=False) # 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) @@ -72,7 +72,7 @@ def test_restart_cecm(run_in_tmpdir): openmc.deplete.cecm(op, dt, power, print_out=False) # 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") _, y1 = res.get_atoms("1", "1") _, y2 = res.get_atoms("1", "2") @@ -102,7 +102,7 @@ def test_restart_predictor_cecm(run_in_tmpdir): openmc.deplete.predictor(op, dt, power, print_out=False) # 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) @@ -112,7 +112,7 @@ def test_restart_predictor_cecm(run_in_tmpdir): openmc.deplete.cecm(op, dt, power, print_out=False) # 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") _, y1 = res.get_atoms("1", "1") _, y2 = res.get_atoms("1", "2") @@ -142,7 +142,7 @@ def test_restart_cecm_predictor(run_in_tmpdir): openmc.deplete.cecm(op, dt, power, print_out=False) # 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) @@ -152,7 +152,7 @@ def test_restart_cecm_predictor(run_in_tmpdir): openmc.deplete.predictor(op, dt, power, print_out=False) # 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") _, y1 = res.get_atoms("1", "1") _, y2 = res.get_atoms("1", "2") @@ -181,7 +181,7 @@ def test_restart_cf4(run_in_tmpdir): openmc.deplete.cf4(op, dt, power, print_out=False) # 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) @@ -191,7 +191,7 @@ def test_restart_cf4(run_in_tmpdir): openmc.deplete.cf4(op, dt, power, print_out=False) # 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") _, y1 = res.get_atoms("1", "1") _, y2 = res.get_atoms("1", "2") @@ -220,7 +220,7 @@ def test_restart_epc_rk4(run_in_tmpdir): openmc.deplete.epc_rk4(op, dt, power, print_out=False) # 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) @@ -230,7 +230,7 @@ def test_restart_epc_rk4(run_in_tmpdir): openmc.deplete.epc_rk4(op, dt, power, print_out=False) # 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") _, y1 = res.get_atoms("1", "1") _, y2 = res.get_atoms("1", "2") @@ -259,7 +259,7 @@ def test_restart_celi(run_in_tmpdir): openmc.deplete.celi(op, dt, power, print_out=False) # 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) @@ -269,7 +269,7 @@ def test_restart_celi(run_in_tmpdir): openmc.deplete.celi(op, dt, power, print_out=False) # 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") _, y1 = res.get_atoms("1", "1") _, y2 = res.get_atoms("1", "2") @@ -298,7 +298,7 @@ def test_restart_leqi(run_in_tmpdir): openmc.deplete.leqi(op, dt, power, print_out=False) # 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) @@ -308,7 +308,7 @@ def test_restart_leqi(run_in_tmpdir): openmc.deplete.leqi(op, dt, power, print_out=False) # 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") _, y1 = res.get_atoms("1", "1") _, y2 = res.get_atoms("1", "2") @@ -336,7 +336,7 @@ def test_restart_si_celi(run_in_tmpdir): openmc.deplete.si_celi(op, dt, power, print_out=False) # 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) @@ -346,7 +346,7 @@ def test_restart_si_celi(run_in_tmpdir): openmc.deplete.si_celi(op, dt, power, print_out=False) # 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") _, y1 = res.get_atoms("1", "1") _, y2 = res.get_atoms("1", "2") @@ -375,7 +375,7 @@ def test_restart_si_leqi(run_in_tmpdir): openmc.deplete.si_leqi(op, dt, power, print_out=False) # 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) @@ -385,7 +385,7 @@ def test_restart_si_leqi(run_in_tmpdir): openmc.deplete.si_leqi(op, dt, power, print_out=False) # 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") _, y1 = res.get_atoms("1", "1") _, y2 = res.get_atoms("1", "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 index 71679439c5..e4ea4f6acf 100644 --- a/tests/unit_tests/test_deplete_si_celi.py +++ b/tests/unit_tests/test_deplete_si_celi.py @@ -21,7 +21,7 @@ def test_si_celi(run_in_tmpdir): openmc.deplete.si_celi(op, dt, power, print_out=False) # 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") _, y1 = res.get_atoms("1", "1") _, y2 = res.get_atoms("1", "2") diff --git a/tests/unit_tests/test_deplete_si_leqi.py b/tests/unit_tests/test_deplete_si_leqi.py index 1396065c0f..cef64fbe88 100644 --- a/tests/unit_tests/test_deplete_si_leqi.py +++ b/tests/unit_tests/test_deplete_si_leqi.py @@ -21,7 +21,7 @@ def test_si_leqi(run_in_tmpdir): openmc.deplete.si_leqi(op, dt, power, print_out=False) # 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") _, y1 = res.get_atoms("1", "1") _, y2 = res.get_atoms("1", "2") diff --git a/tests/unit_tests/test_transfer_volumes.py b/tests/unit_tests/test_transfer_volumes.py index 9128ed9694..38520ea1e3 100644 --- a/tests/unit_tests/test_transfer_volumes.py +++ b/tests/unit_tests/test_transfer_volumes.py @@ -21,10 +21,10 @@ def test_transfer_volumes(run_in_tmpdir): openmc.deplete.predictor(op, dt, power, print_out=False) # 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 From 00095b8b7430890c7853d38b8ad7c67d1d9aa69b Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Mon, 5 Aug 2019 16:18:03 -0500 Subject: [PATCH 029/137] Distribute process-specific previous results to operator Closes #1275 by passing reaction rates and numbers from a previous result according to what materials are tracked on an operator. A new method, Results.distribute, creates a new Results object by - directly copying over "global" data, like time, keff, and maps describing where each material lives in the depletion_results hdf5 file - mapping volumes for local materials - Extracting slices of numbers and reaction rates corresponding to local material This allows the Operator to create a unique ResultsList instance containing reaction rates and compositions pertaining to the materials tracked on this process (local). This has been tested by comparing depletion_result files from restarts 1) using a serial restart run and 2) using two MPI process. A quarter PWR assembly with 71 burnable materials was used. Comparing with h5diff -r -p 0.01 where group=["eigenvalues", "number", "reaction rates"] and -p 0.01 reports errors over 0.01% revealed no differences. --- openmc/deplete/operator.py | 29 +++++++++++++++++++---------- openmc/deplete/results.py | 28 ++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 10 deletions(-) diff --git a/openmc/deplete/operator.py b/openmc/deplete/operator.py index 76163f922c..88f73b9958 100644 --- a/openmc/deplete/operator.py +++ b/openmc/deplete/operator.py @@ -23,6 +23,7 @@ from . import comm from .abc import TransportOperator, OperatorResult from .atom_number import AtomNumber from .reaction_rates import ReactionRates +from .results_list import ResultsList from .helpers import DirectReactionRateHelper, ChainFissionHelper @@ -72,7 +73,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``. @@ -121,19 +123,11 @@ class Operator(TransportOperator): dilute_initial=1.0e3): super().__init__(chain_file, fission_q, dilute_initial) 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 +141,21 @@ class Operator(TransportOperator): self._mat_index_map = { lm: self.burnable_mats.index(lm) for lm in self.local_mats} + if prev_results 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() diff --git a/openmc/deplete/results.py b/openmc/deplete/results.py index 3771f9683b..e05fd6bf94 100644 --- a/openmc/deplete/results.py +++ b/openmc/deplete/results.py @@ -153,6 +153,34 @@ 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 + + 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 From a7e1bca6da7cb4fc4baeb186870b38df06b8ffb3 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Tue, 6 Aug 2019 16:26:23 -0500 Subject: [PATCH 030/137] Apply suggestions from code review Co-Authored-By: Paul Romano --- openmc/deplete/integrator/abc.py | 6 +++--- openmc/deplete/integrator/cecm.py | 2 +- openmc/deplete/integrator/celi.py | 6 +++--- openmc/deplete/integrator/cf4.py | 4 ++-- openmc/deplete/integrator/epc_rk4.py | 4 ++-- openmc/deplete/integrator/leqi.py | 4 ++-- openmc/deplete/integrator/predictor.py | 4 ++-- openmc/deplete/integrator/si_celi.py | 4 ++-- openmc/deplete/integrator/si_leqi.py | 4 ++-- 9 files changed, 19 insertions(+), 19 deletions(-) diff --git a/openmc/deplete/integrator/abc.py b/openmc/deplete/integrator/abc.py index 26ca6b5819..52061de0f8 100644 --- a/openmc/deplete/integrator/abc.py +++ b/openmc/deplete/integrator/abc.py @@ -84,14 +84,14 @@ class Integrator(ABC): dt : float Time in [s] for the entire depletion interval power : float - Power of the system [W] + Power of the system in [W] i : int Current depletion step index Returns ------- proc_time : float - Time spent in CRAM routines for all materials + 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 @@ -177,7 +177,7 @@ class Integrator(ABC): power, index, proc_time) def _write_statepoint(self, step_index): - """Use capi to write a statepoint for this index""" + """Use C API to write a statepoint for this index""" statepoint_write( "openmc_simulation_n{}.h5".format(step_index + self._ires), write_source=False) diff --git a/openmc/deplete/integrator/cecm.py b/openmc/deplete/integrator/cecm.py index 8155b55de8..54efff09a2 100644 --- a/openmc/deplete/integrator/cecm.py +++ b/openmc/deplete/integrator/cecm.py @@ -43,7 +43,7 @@ class CECMIntegrator(Integrator): Returns ------- proc_time : float - Time spent in CRAM routines for all materials + 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 diff --git a/openmc/deplete/integrator/celi.py b/openmc/deplete/integrator/celi.py index b6bbe231b8..137912b6cc 100644 --- a/openmc/deplete/integrator/celi.py +++ b/openmc/deplete/integrator/celi.py @@ -37,14 +37,14 @@ class CELIIntegrator(Integrator): dt : float Time in [s] for the entire depletion interval power : float - Power of the system [W] + Power of the system in [W] _i : int Current iteration count. Not used Returns ------- proc_time : float - Time spent in CRAM routines for all materials + 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 @@ -56,7 +56,7 @@ class CELIIntegrator(Integrator): proc_time, conc_ce = timed_deplete(self.chain, bos_conc, rates, dt) res_ce = self.operator(conc_ce, power) - # deplete using two matrix exponeitials + # deplete using two matrix exponentials list_rates = list(zip(rates, res_ce.rates)) time_le1, conc_inter = timed_deplete( diff --git a/openmc/deplete/integrator/cf4.py b/openmc/deplete/integrator/cf4.py index 96c97b067c..82a2dec8b8 100644 --- a/openmc/deplete/integrator/cf4.py +++ b/openmc/deplete/integrator/cf4.py @@ -42,14 +42,14 @@ class CF4Integrator(Integrator): dt : float Time in [s] for the entire depletion interval power : float - Power of the system [W] + Power of the system in [W] i : int Current depletion step index Returns ------- proc_time : float - Time spent in CRAM routines for all materials + 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 diff --git a/openmc/deplete/integrator/epc_rk4.py b/openmc/deplete/integrator/epc_rk4.py index 879f176bab..00694ac9e0 100644 --- a/openmc/deplete/integrator/epc_rk4.py +++ b/openmc/deplete/integrator/epc_rk4.py @@ -37,14 +37,14 @@ class EPC_RK4_Integrator(Integrator): dt : float Time in [s] for the entire depletion interval power : float - Power of the system [W] + Power of the system in [W] i : int Current depletion step index, unused. Returns ------- proc_time : float - Time spent in CRAM routines for all materials + 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 diff --git a/openmc/deplete/integrator/leqi.py b/openmc/deplete/integrator/leqi.py index c4a819d389..5cfcd0684d 100644 --- a/openmc/deplete/integrator/leqi.py +++ b/openmc/deplete/integrator/leqi.py @@ -51,14 +51,14 @@ class LEQIIntegrator(Integrator): dt : float Time in [s] for the entire depletion interval power : float - Power of the system [W] + Power of the system in [W] i : int Current depletion step index Returns ------- proc_time : float - Time spent in CRAM routines for all materials + 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 diff --git a/openmc/deplete/integrator/predictor.py b/openmc/deplete/integrator/predictor.py index d534645b23..038e52fc95 100644 --- a/openmc/deplete/integrator/predictor.py +++ b/openmc/deplete/integrator/predictor.py @@ -51,14 +51,14 @@ class PredictorIntegrator(Integrator): dt : float Time in [s] for the entire depletion interval power : float - Power of the system [W] + 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 + 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 diff --git a/openmc/deplete/integrator/si_celi.py b/openmc/deplete/integrator/si_celi.py index bfe4f7586b..a0fa3cae23 100644 --- a/openmc/deplete/integrator/si_celi.py +++ b/openmc/deplete/integrator/si_celi.py @@ -32,14 +32,14 @@ class SI_CELI_Integrator(SI_Integrator): dt : float Time in [s] for the entire depletion interval power : float - Power of the system [W] + Power of the system in [W] _i : int Current depletion step index. Unused Returns ------- proc_time : float - Time spent in CRAM routines for all materials + 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 diff --git a/openmc/deplete/integrator/si_leqi.py b/openmc/deplete/integrator/si_leqi.py index 702b098efd..e63219f13f 100644 --- a/openmc/deplete/integrator/si_leqi.py +++ b/openmc/deplete/integrator/si_leqi.py @@ -36,14 +36,14 @@ class SI_LEQI_Integrator(SI_Integrator): dt : float Time in [s] for the entire depletion interval power : float - Power of the system [W] + Power of the system in [W] i : int Current depletion step index Returns ------- proc_time : float - Time spent in CRAM routines for all materials + 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 From 49ba67d480f5f1935a8432088b2ec4ce7f992769 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Tue, 6 Aug 2019 16:53:09 -0500 Subject: [PATCH 031/137] Use Results.save directly during Integrator.integrate Removed the private method for saving to avoid unnecessary redirection. Also renamed some private attributes: 1) Integrator._ires -> Integrator._i_res 2) Integrator._N_STAGES -> Integrator._num_stages --- openmc/deplete/integrator/abc.py | 37 ++++++++++---------------- openmc/deplete/integrator/cecm.py | 2 +- openmc/deplete/integrator/celi.py | 2 +- openmc/deplete/integrator/cf4.py | 2 +- openmc/deplete/integrator/epc_rk4.py | 2 +- openmc/deplete/integrator/leqi.py | 12 ++++----- openmc/deplete/integrator/predictor.py | 2 +- openmc/deplete/integrator/si_celi.py | 2 +- openmc/deplete/integrator/si_leqi.py | 12 ++++----- 9 files changed, 32 insertions(+), 41 deletions(-) diff --git a/openmc/deplete/integrator/abc.py b/openmc/deplete/integrator/abc.py index 52061de0f8..30d6e2da4b 100644 --- a/openmc/deplete/integrator/abc.py +++ b/openmc/deplete/integrator/abc.py @@ -40,13 +40,13 @@ class Integrator(ABC): # Check number of stages previously used if operator.prev_res is not None: res = operator.prev_res[-1] - if res.data.shape[0] != self._N_STAGES: + 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._N_STAGES)) + self._num_stages)) self.operator = operator self.chain = operator.chain if not isinstance(timesteps, Iterable): @@ -102,7 +102,7 @@ class Integrator(ABC): @property @abstractmethod - def _N_STAGES(self): + def _num_stages(self): """Number of intermediate transport solutions Needed to ensure schemes are consistent with restarts @@ -141,7 +141,7 @@ class Integrator(ABC): def integrate(self): """Perform the entire depletion process across all steps""" with self.operator as conc: - t, self._ires = self._get_start_data() + 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: @@ -157,29 +157,21 @@ class Integrator(ABC): # Remove actual EOS concentration for next step conc = conc_list.pop() - self._save_results( - conc_list, res_list, [t, t + dt], p, self._ires + i, - proc_time) + 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)] - self._save_results( - [conc], res_list, [t, t], p, self._ires + len(self)) + Results.save(self.operator, [conc], res_list, [t, t], + p, self._i_res + len(self), proc_time) self._write_statepoint(len(self)) - def _save_results(self, conc_list, results_list, time_list, power, - index, proc_time=None): - """Save the results at the end of of one step""" - Results.save( - self.operator, conc_list, results_list, time_list, - power, index, proc_time) - def _write_statepoint(self, step_index): """Use C API to write a statepoint for this index""" statepoint_write( - "openmc_simulation_n{}.h5".format(step_index + self._ires), + "openmc_simulation_n{}.h5".format(step_index + self._i_res), write_source=False) @@ -234,7 +226,7 @@ class SI_Integrator(Integrator): def integrate(self): """Perform the entire depletion process across all steps""" with self.operator as conc: - t, self._ires = self._get_start_data() + t, self._i_res = self._get_start_data() for i, (dt, p) in enumerate(self): if i == 0: @@ -256,13 +248,12 @@ class SI_Integrator(Integrator): # Remove actual EOS concentration for next step conc = conc_list.pop() - self._save_results( - conc_list, res_list, [t, t + dt], p, self._ires + i, - proc_time) + 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 - self._save_results( - [conc], [res_list[-1]], [t, t], p, self._ires + len(self)) + Results.save(self.operator, [conc], [res_list[-1]], [t, t], + p, self._i_res + len(self), proc_time) self._write_statepoint(len(self)) diff --git a/openmc/deplete/integrator/cecm.py b/openmc/deplete/integrator/cecm.py index 54efff09a2..647acfe0cc 100644 --- a/openmc/deplete/integrator/cecm.py +++ b/openmc/deplete/integrator/cecm.py @@ -22,7 +22,7 @@ class CECMIntegrator(Integrator): y_{n+1} &= \text{expm}(A_c h) y_n \end{aligned} """ - _N_STAGES = 2 + _num_stages = 2 def __call__(self, conc, rates, dt, power, _i=-1): """Integrate using CE/CM diff --git a/openmc/deplete/integrator/celi.py b/openmc/deplete/integrator/celi.py index 137912b6cc..6935f9f6d6 100644 --- a/openmc/deplete/integrator/celi.py +++ b/openmc/deplete/integrator/celi.py @@ -23,7 +23,7 @@ class CELIIntegrator(Integrator): \text{expm}(\frac{5h}{12} A_0 + \frac{h}{12} A1) y_n \end{aligned} """ - _N_STAGES = 2 + _num_stages = 2 def __call__(self, bos_conc, rates, dt, power, _i=-1): """Perform the integration across one time step diff --git a/openmc/deplete/integrator/cf4.py b/openmc/deplete/integrator/cf4.py index 82a2dec8b8..a88a6788c8 100644 --- a/openmc/deplete/integrator/cf4.py +++ b/openmc/deplete/integrator/cf4.py @@ -28,7 +28,7 @@ class CF4Integrator(Integrator): \text{expm}(-1/12 F_1 + 1/6 F_2 + 1/6 F_3 + 1/4 F_4) y_0 \end{aligned} """ - _N_STAGES = 4 + _num_stages = 4 def __call__(self, bos_conc, bos_rates, dt, power, i): """Perform the integration across one time step diff --git a/openmc/deplete/integrator/epc_rk4.py b/openmc/deplete/integrator/epc_rk4.py index 00694ac9e0..c8afe8784d 100644 --- a/openmc/deplete/integrator/epc_rk4.py +++ b/openmc/deplete/integrator/epc_rk4.py @@ -23,7 +23,7 @@ class EPC_RK4_Integrator(Integrator): y_4 &= \text{expm}(1/6 F_1 + 1/3 F_2 + 1/3 F_3 + 1/6 F_4) y_0 \end{aligned} """ - _N_STAGES = 4 + _num_stages = 4 def __call__(self, conc, rates, dt, power, _i): """Perform the integration across one time step diff --git a/openmc/deplete/integrator/leqi.py b/openmc/deplete/integrator/leqi.py index 5cfcd0684d..551514078b 100644 --- a/openmc/deplete/integrator/leqi.py +++ b/openmc/deplete/integrator/leqi.py @@ -37,7 +37,7 @@ class LEQIIntegrator(Integrator): It is initialized using the CE/LI algorithm. """ - _N_STAGES = 2 + _num_stages = 2 def __call__(self, bos_conc, bos_rates, dt, power, i): """Perform the integration across one time step @@ -67,21 +67,21 @@ class LEQIIntegrator(Integrator): simulation """ if i == 0: - if self._ires < 1: # need at least previous transport solution + 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] - prevdt = self.timesteps[i] - prev_res.time[0] + prev_dt = self.timesteps[i] - prev_res.time[0] self._prev_rates = prev_res.rates[0] else: - prevdt = self.timesteps[i - 1] + 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(prevdt), repeat(dt))) + 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) @@ -92,7 +92,7 @@ class LEQIIntegrator(Integrator): qi_inputs = list(zip( self._prev_rates, bos_res.rates, res_inter.rates, - repeat(prevdt), repeat(dt))) + repeat(prev_dt), repeat(dt))) time3, conc_inter = timed_deplete( self.chain, bos_conc, qi_inputs, dt, matrix_func=_leqi_f3) diff --git a/openmc/deplete/integrator/predictor.py b/openmc/deplete/integrator/predictor.py index 038e52fc95..f57bac79c1 100644 --- a/openmc/deplete/integrator/predictor.py +++ b/openmc/deplete/integrator/predictor.py @@ -37,7 +37,7 @@ class PredictorIntegrator(Integrator): initial heavy metal inventory to get total power if ``power`` is not speficied. """ - _N_STAGES = 1 + _num_stages = 1 def __call__(self, conc, rates, dt, power, _i=None): """Perform the integration across one time step diff --git a/openmc/deplete/integrator/si_celi.py b/openmc/deplete/integrator/si_celi.py index a0fa3cae23..14ff65bf40 100644 --- a/openmc/deplete/integrator/si_celi.py +++ b/openmc/deplete/integrator/si_celi.py @@ -18,7 +18,7 @@ class SI_CELI_Integrator(SI_Integrator): detailed algorithm can be found in section 3.2 in `colin josey's thesis `_. """ - _N_STAGES = 2 + _num_stages = 2 def __call__(self, bos_conc, bos_rates, dt, power, _i): """Perform the integration across one time step diff --git a/openmc/deplete/integrator/si_leqi.py b/openmc/deplete/integrator/si_leqi.py index e63219f13f..384b1499a1 100644 --- a/openmc/deplete/integrator/si_leqi.py +++ b/openmc/deplete/integrator/si_leqi.py @@ -21,7 +21,7 @@ class SI_LEQI_Integrator(SI_Integrator): Detailed algorithm can be found in Section 3.2 in `Colin Josey's thesis `_. """ - _N_STAGES = 2 + _num_stages = 2 def __call__(self, bos_conc, bos_rates, dt, power, i): """Perform the integration across one time step @@ -52,20 +52,20 @@ class SI_LEQI_Integrator(SI_Integrator): simulation """ if i == 0: - if self._ires < 1: + if self._i_res < 1: self._prev_rates = bos_rates # Perform CELI for initial steps return SI_CELI_Integrator.__call__( self, bos_conc, bos_rates, dt, power, i) prev_res = self.operator.prev_res[-2] - prevdt = self.timesteps[i] - prev_res.time[0] + prev_dt = self.timesteps[i] - prev_res.time[0] self._prev_rates = prev_res.rates[0] else: - prevdt = self.timesteps[i - 1] + prev_dt = self.timesteps[i - 1] # Perform remaining LE/QI inputs = list(zip(self._prev_rates, bos_rates, - repeat(prevdt), repeat(dt))) + 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( @@ -85,7 +85,7 @@ class SI_LEQI_Integrator(SI_Integrator): res_bar = OperatorResult(k, rates) inputs = list(zip(self._prev_rates, bos_rates, res_bar.rates, - repeat(prevdt), repeat(dt))) + 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( From 78943d1086f2e77e028356cb1a3fdc5fd13d7cb1 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Tue, 6 Aug 2019 16:57:21 -0500 Subject: [PATCH 032/137] Add power as argument to TransportOperator.__call__ --- openmc/deplete/abc.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py index 770a482abb..25c45b75cc 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -106,13 +106,15 @@ class TransportOperator(ABC): self._dilute_initial = value @abstractmethod - def __call__(self, vec): + def __call__(self, vec, power): """Runs a simulation. Parameters ---------- vec : list of numpy.ndarray Total atoms to be used in function. + power : float + Power of the reactor in [W] Returns ------- From e286940f8eb3e86ac318fdfe146eec6e23deed9a Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Tue, 6 Aug 2019 17:00:41 -0500 Subject: [PATCH 033/137] Pass prev_res onto TransportOperator.__init__ Document that this attribute will either be a ResultsList or None, both on TransportOperator and Operator. --- openmc/deplete/abc.py | 19 +++++++++++++------ openmc/deplete/operator.py | 16 ++++++---------- 2 files changed, 19 insertions(+), 16 deletions(-) diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py index 25c45b75cc..f074864c68 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -17,6 +17,7 @@ from numpy import nonzero, empty from openmc.data import DataLibrary, JOULE_PER_EV from openmc.checkvalue import check_type, check_greater_than from .chain import Chain +from .results_list import ResultsList OperatorResult = namedtuple('OperatorResult', ['k', 'rates']) OperatorResult.__doc__ = """\ @@ -61,6 +62,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 +71,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 +100,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_res @property def dilute_initial(self): @@ -122,7 +134,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 +168,6 @@ class TransportOperator(ABC): Total density for initial conditions. """ - pass - @abstractmethod def get_results_info(self): """Returns volume list, cell lists, and nuc lists. @@ -175,8 +184,6 @@ class TransportOperator(ABC): All burnable materials in the geometry. """ - pass - def finalize(self): pass diff --git a/openmc/deplete/operator.py b/openmc/deplete/operator.py index f60a4a2d4f..f3c432a346 100644 --- a/openmc/deplete/operator.py +++ b/openmc/deplete/operator.py @@ -112,28 +112,24 @@ 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 """ 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) + super().__init__(chain_file, fission_q, dilute_initial, prev_results) self.round_number = False self.settings = settings self.geometry = geometry self.diff_burnable_mats = diff_burnable_mats - if prev_results is not None: + if self.prev_res 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 + self.prev_results[-1].transfer_volumes(geometry) # Differentiate burnable materials with multiple instances if self.diff_burnable_mats: From a4905c03af8842e0c58483d15678f3399fe74d7a Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Tue, 6 Aug 2019 17:36:22 -0500 Subject: [PATCH 034/137] Add TransportOperator.write_bos_data abstract method Called with a single integer corresponding to the current depletion step. This method is intended to document the current beginning-of-step calculations prior to any depletion at each step and at the final simulation. The Operator uses this method to write a statepoint using the C API. Also rename the Integrator _get_bos_data_from_openmc to _get_bos_data_from_operator for generality. It is here that the write_bos_data method is called. --- openmc/deplete/abc.py | 13 ++++++++++++ openmc/deplete/integrator/abc.py | 25 +++++++++-------------- openmc/deplete/operator.py | 13 ++++++++++++ tests/dummy_operator.py | 3 +++ tests/unit_tests/test_deplete_operator.py | 14 +++++++++---- 5 files changed, 49 insertions(+), 19 deletions(-) diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py index f074864c68..8e77a8ff0d 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -187,6 +187,19 @@ class TransportOperator(ABC): 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 diff --git a/openmc/deplete/integrator/abc.py b/openmc/deplete/integrator/abc.py index 30d6e2da4b..2ed2634ee9 100644 --- a/openmc/deplete/integrator/abc.py +++ b/openmc/deplete/integrator/abc.py @@ -114,16 +114,17 @@ class Integrator(ABC): def __len__(self): return len(self.timesteps) - def _get_bos_data_from_openmc(self, step_index, step_power, bos_conc): + 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._write_statepoint(step_index) + 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 - # See Operator.__call__, Operator.initial_condition bos_conc = list(res.data[0]) rates = res.rates[0] k = ufloat(res.k[0, 0], res.k[0, 1]) @@ -145,7 +146,7 @@ class Integrator(ABC): for i, (dt, p) in enumerate(self): if i > 0 or self.operator.prev_res is None: - conc, res = self._get_bos_data_from_openmc(i, p, conc) + 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) @@ -166,13 +167,7 @@ class Integrator(ABC): res_list = [self.operator(conc, p)] Results.save(self.operator, [conc], res_list, [t, t], p, self._i_res + len(self), proc_time) - self._write_statepoint(len(self)) - - def _write_statepoint(self, step_index): - """Use C API to write a statepoint for this index""" - statepoint_write( - "openmc_simulation_n{}.h5".format(step_index + self._i_res), - write_source=False) + self.operator.write_bos_data(len(self) + self._i_res) class SI_Integrator(Integrator): @@ -212,12 +207,12 @@ class SI_Integrator(Integrator): super().__init__(operator, timesteps, power, power_density) self.n_steps = n_steps - def _get_bos_data_from_openmc(self, step_index, step_power, bos_conc): + 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_openmc( + inherited = super()._get_bos_data_from_operator( step_index, step_power, bos_conc) if reset_particles: self.operator.settings.particles //= self.n_stages @@ -231,7 +226,7 @@ class SI_Integrator(Integrator): for i, (dt, p) in enumerate(self): if i == 0: if self.operator.prev_res is None: - conc, res = self._get_bos_data_from_openmc(i, p, conc) + conc, res = self._get_bos_data_from_operator(i, p, conc) else: conc, res = self._get_bos_data_from_restart(i, p, conc) else: @@ -256,4 +251,4 @@ class SI_Integrator(Integrator): # 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._write_statepoint(len(self)) + self.operator.write_bos_data(self._i_res + len(self)) diff --git a/openmc/deplete/operator.py b/openmc/deplete/operator.py index f3c432a346..b845faad9d 100644 --- a/openmc/deplete/operator.py +++ b/openmc/deplete/operator.py @@ -204,6 +204,19 @@ class Operator(TransportOperator): 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 diff --git a/tests/dummy_operator.py b/tests/dummy_operator.py index 23dc22288a..c23abef631 100644 --- a/tests/dummy_operator.py +++ b/tests/dummy_operator.py @@ -116,6 +116,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): 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 From 1bd663daf67897c828c7a609fb3209b82c87d839 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Tue, 6 Aug 2019 17:54:12 -0500 Subject: [PATCH 035/137] Rename integrator classes to be PEP8 complaint Changes: - EPC_RK4_Integrator -> EPCRK4Integrator - SI_Integrator -> SIIntegrator - SI_CELI_Integrator -> SICELIIntegrator - SI_LEQI_Integrator -> SICELIIntegrator --- docs/source/pythonapi/deplete.rst | 6 +++--- openmc/deplete/integrator/abc.py | 2 +- openmc/deplete/integrator/epc_rk4.py | 2 +- openmc/deplete/integrator/si_celi.py | 10 +++++----- openmc/deplete/integrator/si_leqi.py | 10 ++++------ tests/unit_tests/test_deplete_epc_rk4.py | 4 ++-- tests/unit_tests/test_deplete_integrator.py | 6 +++--- tests/unit_tests/test_deplete_restart.py | 14 +++++++------- tests/unit_tests/test_deplete_si_celi.py | 4 ++-- tests/unit_tests/test_deplete_si_leqi.py | 4 ++-- 10 files changed, 30 insertions(+), 32 deletions(-) diff --git a/docs/source/pythonapi/deplete.rst b/docs/source/pythonapi/deplete.rst index 6e1afc5423..29a3b5b9a0 100644 --- a/docs/source/pythonapi/deplete.rst +++ b/docs/source/pythonapi/deplete.rst @@ -20,10 +20,10 @@ transport-depletion coupling algorithms `_. integrator.CF4Integrator integrator.CECMIntegrator integrator.CELIIntegrator - integrator.EPC_RK4_Integrator + integrator.EPCRK4Integrator integrator.LEQIIntegrator - integrator.SI_CELI_Integrator - integrator.SI_LEQI_Integrator + integrator.SICELIIntegrator + integrator.SILEQIIntegrator Each of these functions expects a "transport operator" to be passed. An operator specific to OpenMC is available using the following class: diff --git a/openmc/deplete/integrator/abc.py b/openmc/deplete/integrator/abc.py index 2ed2634ee9..03064d8bdb 100644 --- a/openmc/deplete/integrator/abc.py +++ b/openmc/deplete/integrator/abc.py @@ -170,7 +170,7 @@ class Integrator(ABC): self.operator.write_bos_data(len(self) + self._i_res) -class SI_Integrator(Integrator): +class SIIntegrator(Integrator): """Abstract for the Stochastic Implicit Euler integrators Does not provide a ``__call__`` method, but scales and resets diff --git a/openmc/deplete/integrator/epc_rk4.py b/openmc/deplete/integrator/epc_rk4.py index c8afe8784d..d5fba639f7 100644 --- a/openmc/deplete/integrator/epc_rk4.py +++ b/openmc/deplete/integrator/epc_rk4.py @@ -4,7 +4,7 @@ from .cram import timed_deplete from .abc import Integrator -class EPC_RK4_Integrator(Integrator): +class EPCRK4Integrator(Integrator): r"""Deplete using the EPC-RK4 algorithm. Implements an extended predictor-corrector algorithm with traditional diff --git a/openmc/deplete/integrator/si_celi.py b/openmc/deplete/integrator/si_celi.py index 14ff65bf40..69f7287baf 100644 --- a/openmc/deplete/integrator/si_celi.py +++ b/openmc/deplete/integrator/si_celi.py @@ -2,20 +2,20 @@ import copy -from .abc import SI_Integrator +from .abc import SIIntegrator from .cram import timed_deplete from .celi import _celi_f1, _celi_f2 from ..abc import OperatorResult -class SI_CELI_Integrator(SI_Integrator): - r"""Deplete using the si-ce/li cfq4 algorithm. +class SICELIIntegrator(SIIntegrator): + r"""Deplete using the SI-CE/LI CFQ4 algorithm. - Implements the stochastic implicit ce/li predictor-corrector 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 + Detailed algorithm can be found in section 3.2 in `colin josey's thesis `_. """ _num_stages = 2 diff --git a/openmc/deplete/integrator/si_leqi.py b/openmc/deplete/integrator/si_leqi.py index 384b1499a1..4a39207caf 100644 --- a/openmc/deplete/integrator/si_leqi.py +++ b/openmc/deplete/integrator/si_leqi.py @@ -1,18 +1,16 @@ """The SI-LE/QI CFQ4 integrator.""" import copy -from collections.abc import Iterable from itertools import repeat -from .abc import SI_Integrator -from .si_celi import SI_CELI_Integrator +from .abc import SIIntegrator +from .si_celi import SICELIIntegrator from .leqi import _leqi_f1, _leqi_f2, _leqi_f3, _leqi_f4 from .cram import timed_deplete -from ..results import Results from ..abc import OperatorResult -class SI_LEQI_Integrator(SI_Integrator): +class SILEQIIntegrator(SIIntegrator): r"""Deplete using the SI-LE/QI CFQ4 algorithm. Implements the Stochastic Implicit LE/QI Predictor-Corrector algorithm using @@ -55,7 +53,7 @@ class SI_LEQI_Integrator(SI_Integrator): if self._i_res < 1: self._prev_rates = bos_rates # Perform CELI for initial steps - return SI_CELI_Integrator.__call__( + 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] diff --git a/tests/unit_tests/test_deplete_epc_rk4.py b/tests/unit_tests/test_deplete_epc_rk4.py index 71cda3b9ff..8f1160164b 100644 --- a/tests/unit_tests/test_deplete_epc_rk4.py +++ b/tests/unit_tests/test_deplete_epc_rk4.py @@ -4,7 +4,7 @@ These tests integrate a simple test problem described in dummy_geometry.py. """ from pytest import approx -from openmc.deplete import EPC_RK4_Integrator, ResultsList +from openmc.deplete import EPCRK4Integrator, ResultsList from tests import dummy_operator @@ -18,7 +18,7 @@ def test_epc_rk4(run_in_tmpdir): # Perform simulation using the epc_rk4 algorithm dt = [0.75, 0.75] power = 1.0 - EPC_RK4_Integrator(op, dt, power).integrate() + EPCRK4Integrator(op, dt, power).integrate() # Load the files res = ResultsList(op.output_dir / "depletion_results.h5") diff --git a/tests/unit_tests/test_deplete_integrator.py b/tests/unit_tests/test_deplete_integrator.py index 69f6ee6797..302ec28d73 100644 --- a/tests/unit_tests/test_deplete_integrator.py +++ b/tests/unit_tests/test_deplete_integrator.py @@ -16,7 +16,7 @@ import pytest from openmc.deplete import ( ReactionRates, Results, ResultsList, comm, OperatorResult, - PredictorIntegrator, SI_CELI_Integrator) + PredictorIntegrator, SICELIIntegrator) def test_results_save(run_in_tmpdir): @@ -126,7 +126,7 @@ def test_bad_integrator_inputs(timesteps): # SI integrator with bad steps with pytest.raises(TypeError, match="n_steps"): - SI_CELI_Integrator(op, timesteps, [1], n_steps=2.5) + SICELIIntegrator(op, timesteps, [1], n_steps=2.5) with pytest.raises(ValueError, match="n_steps"): - SI_CELI_Integrator(op, timesteps, [1], n_steps=0) + SICELIIntegrator(op, timesteps, [1], n_steps=0) diff --git a/tests/unit_tests/test_deplete_restart.py b/tests/unit_tests/test_deplete_restart.py index 847ad9b838..912f7e301e 100644 --- a/tests/unit_tests/test_deplete_restart.py +++ b/tests/unit_tests/test_deplete_restart.py @@ -8,7 +8,7 @@ from pytest import approx, raises import openmc.deplete from openmc.deplete import ( CECMIntegrator, PredictorIntegrator, CELIIntegrator, LEQIIntegrator, - EPC_RK4_Integrator, CF4Integrator, SI_CELI_Integrator, SI_LEQI_Integrator + EPCRK4Integrator, CF4Integrator, SICELIIntegrator, SILEQIIntegrator ) from tests import dummy_operator @@ -192,7 +192,7 @@ def test_restart_epc_rk4(run_in_tmpdir): # Perform simulation dt = [0.75] power = 1.0 - EPC_RK4_Integrator(op, dt, power).integrate() + EPCRK4Integrator(op, dt, power).integrate() # Load the files prev_res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") @@ -202,7 +202,7 @@ def test_restart_epc_rk4(run_in_tmpdir): op.output_dir = output_dir # Perform restarts simulation - EPC_RK4_Integrator(op, dt, power).integrate() + EPCRK4Integrator(op, dt, power).integrate() # Load the files res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") @@ -308,7 +308,7 @@ def test_restart_si_celi(run_in_tmpdir): # Perform simulation dt = [0.75] power = 1.0 - SI_CELI_Integrator(op, dt, power).integrate() + SICELIIntegrator(op, dt, power).integrate() # Load the files prev_res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") @@ -318,7 +318,7 @@ def test_restart_si_celi(run_in_tmpdir): op.output_dir = output_dir # Perform restarts simulation - SI_CELI_Integrator(op, dt, power).integrate() + SICELIIntegrator(op, dt, power).integrate() # Load the files res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") @@ -348,7 +348,7 @@ def test_restart_si_leqi(run_in_tmpdir): dt = [0.75] power = 1.0 nstages = 10 - SI_LEQI_Integrator(op, dt, power, nstages).integrate() + SILEQIIntegrator(op, dt, power, nstages).integrate() # Load the files prev_res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") @@ -358,7 +358,7 @@ def test_restart_si_leqi(run_in_tmpdir): op.output_dir = output_dir # Perform restarts simulation - SI_LEQI_Integrator(op, dt, power, nstages).integrate() + SILEQIIntegrator(op, dt, power, nstages).integrate() # Load the files res = openmc.deplete.ResultsList(op.output_dir / "depletion_results.h5") diff --git a/tests/unit_tests/test_deplete_si_celi.py b/tests/unit_tests/test_deplete_si_celi.py index 954c9d8907..0a0103510b 100644 --- a/tests/unit_tests/test_deplete_si_celi.py +++ b/tests/unit_tests/test_deplete_si_celi.py @@ -4,7 +4,7 @@ These tests integrate a simple test problem described in dummy_geometry.py. """ from pytest import approx -from openmc.deplete import SI_CELI_Integrator, ResultsList +from openmc.deplete import SICELIIntegrator, ResultsList from tests import dummy_operator @@ -18,7 +18,7 @@ def test_si_celi(run_in_tmpdir): # Perform simulation using the si_celi algorithm dt = [0.75, 0.75] power = 1.0 - SI_CELI_Integrator(op, dt, power).integrate() + SICELIIntegrator(op, dt, power).integrate() # Load the files res = ResultsList(op.output_dir / "depletion_results.h5") diff --git a/tests/unit_tests/test_deplete_si_leqi.py b/tests/unit_tests/test_deplete_si_leqi.py index c26c06d035..41215c22f4 100644 --- a/tests/unit_tests/test_deplete_si_leqi.py +++ b/tests/unit_tests/test_deplete_si_leqi.py @@ -4,7 +4,7 @@ These tests integrate a simple test problem described in dummy_geometry.py. """ from pytest import approx -from openmc.deplete import SI_LEQI_Integrator, ResultsList +from openmc.deplete import SILEQIIntegrator, ResultsList from tests import dummy_operator @@ -18,7 +18,7 @@ def test_si_leqi(run_in_tmpdir): # Perform simulation using the si_leqi algorithm dt = [0.75, 0.75] power = 1.0 - SI_LEQI_Integrator(op, dt, power, 10).integrate() + SILEQIIntegrator(op, dt, power, 10).integrate() # Load the files res = ResultsList(op.output_dir / "depletion_results.h5") From 18c4d5f4327828721a1b21c9bec0da24de8d5a3b Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Wed, 7 Aug 2019 09:01:15 -0500 Subject: [PATCH 036/137] Document init parameters and attributes for Integrators --- docs/source/pythonapi/deplete.rst | 2 +- openmc/deplete/integrator/abc.py | 111 +++++++++++++++---------- openmc/deplete/integrator/cecm.py | 31 +++++++ openmc/deplete/integrator/celi.py | 31 +++++++ openmc/deplete/integrator/cf4.py | 31 +++++++ openmc/deplete/integrator/epc_rk4.py | 34 +++++++- openmc/deplete/integrator/leqi.py | 31 +++++++ openmc/deplete/integrator/predictor.py | 13 ++- openmc/deplete/integrator/si_celi.py | 38 ++++++++- openmc/deplete/integrator/si_leqi.py | 36 ++++++++ 10 files changed, 308 insertions(+), 50 deletions(-) diff --git a/docs/source/pythonapi/deplete.rst b/docs/source/pythonapi/deplete.rst index 29a3b5b9a0..f2964c5c89 100644 --- a/docs/source/pythonapi/deplete.rst +++ b/docs/source/pythonapi/deplete.rst @@ -17,9 +17,9 @@ transport-depletion coupling algorithms `_. :template: myclassinherit.rst integrator.PredictorIntegrator - integrator.CF4Integrator integrator.CECMIntegrator integrator.CELIIntegrator + integrator.CF4Integrator integrator.EPCRK4Integrator integrator.LEQIIntegrator integrator.SICELIIntegrator diff --git a/openmc/deplete/integrator/abc.py b/openmc/deplete/integrator/abc.py index 03064d8bdb..53db536aa4 100644 --- a/openmc/deplete/integrator/abc.py +++ b/openmc/deplete/integrator/abc.py @@ -13,30 +13,39 @@ from openmc.deplete import Results, OperatorResult 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): - """ - 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. - """ # Check number of stages previously used if operator.prev_res is not None: res = operator.prev_res[-1] @@ -175,33 +184,45 @@ class SIIntegrator(Integrator): 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): - """ - 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 - """ check_type("n_steps", n_steps, Integral) check_greater_than("n_steps", n_steps, 0) super().__init__(operator, timesteps, power, power_density) diff --git a/openmc/deplete/integrator/cecm.py b/openmc/deplete/integrator/cecm.py index 647acfe0cc..aea4b7b399 100644 --- a/openmc/deplete/integrator/cecm.py +++ b/openmc/deplete/integrator/cecm.py @@ -21,6 +21,37 @@ class CECMIntegrator(Integrator): 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 diff --git a/openmc/deplete/integrator/celi.py b/openmc/deplete/integrator/celi.py index 6935f9f6d6..9aa9830b2c 100644 --- a/openmc/deplete/integrator/celi.py +++ b/openmc/deplete/integrator/celi.py @@ -22,6 +22,37 @@ class CELIIntegrator(Integrator): 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 diff --git a/openmc/deplete/integrator/cf4.py b/openmc/deplete/integrator/cf4.py index a88a6788c8..49ae16483a 100644 --- a/openmc/deplete/integrator/cf4.py +++ b/openmc/deplete/integrator/cf4.py @@ -27,6 +27,37 @@ class CF4Integrator(Integrator): 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 diff --git a/openmc/deplete/integrator/epc_rk4.py b/openmc/deplete/integrator/epc_rk4.py index d5fba639f7..c8247bf427 100644 --- a/openmc/deplete/integrator/epc_rk4.py +++ b/openmc/deplete/integrator/epc_rk4.py @@ -8,8 +8,7 @@ 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: + Runge-Kutta 4 method. This algorithm is mathematically defined as: .. math:: \begin{aligned} @@ -22,6 +21,37 @@ class EPCRK4Integrator(Integrator): 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 diff --git a/openmc/deplete/integrator/leqi.py b/openmc/deplete/integrator/leqi.py index 551514078b..86c1ab2dfa 100644 --- a/openmc/deplete/integrator/leqi.py +++ b/openmc/deplete/integrator/leqi.py @@ -36,6 +36,37 @@ class LEQIIntegrator(Integrator): \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 diff --git a/openmc/deplete/integrator/predictor.py b/openmc/deplete/integrator/predictor.py index f57bac79c1..44b1516aa0 100644 --- a/openmc/deplete/integrator/predictor.py +++ b/openmc/deplete/integrator/predictor.py @@ -20,7 +20,7 @@ class PredictorIntegrator(Integrator): Parameters ---------- operator : openmc.deplete.TransportOperator - The operator object to simulate on. + Operator to perform transport simulations timesteps : iterable of float Array of timesteps in units of [s]. Note that values are not cumulative. @@ -36,6 +36,17 @@ class PredictorIntegrator(Integrator): 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 diff --git a/openmc/deplete/integrator/si_celi.py b/openmc/deplete/integrator/si_celi.py index 69f7287baf..4b4ccc1e69 100644 --- a/openmc/deplete/integrator/si_celi.py +++ b/openmc/deplete/integrator/si_celi.py @@ -15,8 +15,44 @@ class SICELIIntegrator(SIIntegrator): using the `fourth order commutator-free integrator `_. - Detailed algorithm can be found in section 3.2 in `colin josey's thesis + 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 diff --git a/openmc/deplete/integrator/si_leqi.py b/openmc/deplete/integrator/si_leqi.py index 4a39207caf..1e48c039e7 100644 --- a/openmc/deplete/integrator/si_leqi.py +++ b/openmc/deplete/integrator/si_leqi.py @@ -18,6 +18,42 @@ class SILEQIIntegrator(SIIntegrator): 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 From 5adc3b5816fe33adc17f8656fe3a731196f3ca7f Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Wed, 7 Aug 2019 09:13:14 -0500 Subject: [PATCH 037/137] Document special and abstract methods for Integrators --- docs/source/_templates/myintegrator.rst | 9 +++++++++ docs/source/pythonapi/deplete.rst | 15 +++++++++++++-- openmc/deplete/integrator/abc.py | 4 +++- 3 files changed, 25 insertions(+), 3 deletions(-) create mode 100644 docs/source/_templates/myintegrator.rst 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/pythonapi/deplete.rst b/docs/source/pythonapi/deplete.rst index f2964c5c89..dc95c78217 100644 --- a/docs/source/pythonapi/deplete.rst +++ b/docs/source/pythonapi/deplete.rst @@ -14,7 +14,7 @@ transport-depletion coupling algorithms `_. .. autosummary:: :toctree: generated :nosignatures: - :template: myclassinherit.rst + :template: myintegrator.rst integrator.PredictorIntegrator integrator.CECMIntegrator @@ -95,7 +95,18 @@ The following classes are abstract classes that can be used to extend the EnergyHelper 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:: diff --git a/openmc/deplete/integrator/abc.py b/openmc/deplete/integrator/abc.py index 53db536aa4..cceb6ccc30 100644 --- a/openmc/deplete/integrator/abc.py +++ b/openmc/deplete/integrator/abc.py @@ -118,9 +118,11 @@ class Integrator(ABC): """ 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): @@ -180,7 +182,7 @@ class Integrator(ABC): class SIIntegrator(Integrator): - """Abstract for the Stochastic Implicit Euler integrators + """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 From 0bc7800a92c8b1f8bc8997219efecf40c39a239f Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Wed, 7 Aug 2019 10:51:19 -0500 Subject: [PATCH 038/137] Move openmc.deplete.integrator into openmc.deplete Much of the previous API is intact, with the major change being CRAM functions are imported from openmc.deplete.cram in the test_deplete_cram. The integrator abstract classes are placed in openmc/deplete/abc.py with all concrete classes going in to openmc/deplete/integrators.py Documentation updated accordingly --- docs/source/pythonapi/deplete.rst | 20 +- openmc/deplete/__init__.py | 2 +- openmc/deplete/_matrix_funcs.py | 79 +++ openmc/deplete/abc.py | 287 +++++++- openmc/deplete/{integrator => }/cram.py | 4 +- openmc/deplete/integrator/__init__.py | 17 - openmc/deplete/integrator/abc.py | 277 -------- openmc/deplete/integrator/cecm.py | 92 --- openmc/deplete/integrator/celi.py | 110 ---- openmc/deplete/integrator/cf4.py | 140 ---- openmc/deplete/integrator/epc_rk4.py | 119 ---- openmc/deplete/integrator/leqi.py | 173 ----- openmc/deplete/integrator/predictor.py | 81 --- openmc/deplete/integrator/si_celi.py | 109 --- openmc/deplete/integrator/si_leqi.py | 129 ---- openmc/deplete/integrators.py | 836 ++++++++++++++++++++++++ tests/unit_tests/test_deplete_cram.py | 2 +- 17 files changed, 1210 insertions(+), 1267 deletions(-) create mode 100644 openmc/deplete/_matrix_funcs.py rename openmc/deplete/{integrator => }/cram.py (99%) delete mode 100644 openmc/deplete/integrator/__init__.py delete mode 100644 openmc/deplete/integrator/abc.py delete mode 100644 openmc/deplete/integrator/cecm.py delete mode 100644 openmc/deplete/integrator/celi.py delete mode 100644 openmc/deplete/integrator/cf4.py delete mode 100644 openmc/deplete/integrator/epc_rk4.py delete mode 100644 openmc/deplete/integrator/leqi.py delete mode 100644 openmc/deplete/integrator/predictor.py delete mode 100644 openmc/deplete/integrator/si_celi.py delete mode 100644 openmc/deplete/integrator/si_leqi.py create mode 100644 openmc/deplete/integrators.py diff --git a/docs/source/pythonapi/deplete.rst b/docs/source/pythonapi/deplete.rst index dc95c78217..a72504ac92 100644 --- a/docs/source/pythonapi/deplete.rst +++ b/docs/source/pythonapi/deplete.rst @@ -16,14 +16,14 @@ transport-depletion coupling algorithms `_. :nosignatures: :template: myintegrator.rst - integrator.PredictorIntegrator - integrator.CECMIntegrator - integrator.CELIIntegrator - integrator.CF4Integrator - integrator.EPCRK4Integrator - integrator.LEQIIntegrator - integrator.SICELIIntegrator - integrator.SILEQIIntegrator + PredictorIntegrator + CECMIntegrator + CELIIntegrator + CF4Integrator + EPCRK4Integrator + LEQIIntegrator + SICELIIntegrator + SILEQIIntegrator Each of these functions expects a "transport operator" to be passed. An operator specific to OpenMC is available using the following class: @@ -114,5 +114,5 @@ as follows: :nosignatures: :template: myfunction.rst - integrator.CRAM16 - integrator.CRAM48 + cram.CRAM16 + cram.CRAM48 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..1cacbd2c06 --- /dev/null +++ b/openmc/deplete/_matrix_funcs.py @@ -0,0 +1,79 @@ +"""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 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 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 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) diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py index 8e77a8ff0d..0f1203aeb8 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -5,17 +5,20 @@ 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 uncertainties import ufloat from openmc.data import DataLibrary, JOULE_PER_EV from openmc.checkvalue import check_type, check_greater_than +from .results import Results from .chain import Chain from .results_list import ResultsList @@ -44,8 +47,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 @@ -104,7 +107,7 @@ class TransportOperator(ABC): self.prev_res = None else: check_type("previous results", prev_results, ResultsList) - self.prev_results = prev_res + self.prev_results = prev_results @property def dilute_initial(self): @@ -179,7 +182,8 @@ 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. """ @@ -266,7 +270,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 ------- @@ -357,3 +362,271 @@ class EnergyHelper(ABC): def nuclides(self, nuclides): check_type("nuclides", nuclides, list, str) self._nuclides = nuclides + + +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/integrator/cram.py b/openmc/deplete/cram.py similarity index 99% rename from openmc/deplete/integrator/cram.py rename to openmc/deplete/cram.py index c57ff53132..41a4e4c710 100644 --- a/openmc/deplete/integrator/cram.py +++ b/openmc/deplete/cram.py @@ -11,7 +11,9 @@ 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, matrix_func=None): diff --git a/openmc/deplete/integrator/__init__.py b/openmc/deplete/integrator/__init__.py deleted file mode 100644 index 961afefb6c..0000000000 --- a/openmc/deplete/integrator/__init__.py +++ /dev/null @@ -1,17 +0,0 @@ -""" -Integrator -=========== - -The integrator subcomponents. -""" - -from .abc import Integrator -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/abc.py b/openmc/deplete/integrator/abc.py deleted file mode 100644 index cceb6ccc30..0000000000 --- a/openmc/deplete/integrator/abc.py +++ /dev/null @@ -1,277 +0,0 @@ -from copy import deepcopy -from abc import ABC, abstractmethod -from collections.abc import Iterable -from numbers import Integral - -from uncertainties import ufloat - -from openmc.checkvalue import check_type, check_greater_than -from openmc.capi import statepoint_write -from openmc.deplete import Results, OperatorResult - - -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/integrator/cecm.py b/openmc/deplete/integrator/cecm.py deleted file mode 100644 index aea4b7b399..0000000000 --- a/openmc/deplete/integrator/cecm.py +++ /dev/null @@ -1,92 +0,0 @@ -"""The CE/CM integrator.""" - -from .abc import Integrator -from .cram import timed_deplete - - -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=-1): - """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] diff --git a/openmc/deplete/integrator/celi.py b/openmc/deplete/integrator/celi.py deleted file mode 100644 index 9aa9830b2c..0000000000 --- a/openmc/deplete/integrator/celi.py +++ /dev/null @@ -1,110 +0,0 @@ -"""The CE/LI CFQ4 integrator.""" - -from .cram import timed_deplete -from .abc import Integrator - - -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=-1): - """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 - 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] - - -# 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]) diff --git a/openmc/deplete/integrator/cf4.py b/openmc/deplete/integrator/cf4.py deleted file mode 100644 index 49ae16483a..0000000000 --- a/openmc/deplete/integrator/cf4.py +++ /dev/null @@ -1,140 +0,0 @@ -"""The CF4 integrator.""" - -import copy -from collections.abc import Iterable - -from .cram import timed_deplete -from .abc import Integrator -from ..results import Results - - -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): - """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 - 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 - """ - # 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]) - - -# 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]) diff --git a/openmc/deplete/integrator/epc_rk4.py b/openmc/deplete/integrator/epc_rk4.py deleted file mode 100644 index c8247bf427..0000000000 --- a/openmc/deplete/integrator/epc_rk4.py +++ /dev/null @@ -1,119 +0,0 @@ -"""The EPC-RK4 integrator.""" - -from .cram import timed_deplete -from .abc import Integrator - - -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): - """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, 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]) - - -# 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]) diff --git a/openmc/deplete/integrator/leqi.py b/openmc/deplete/integrator/leqi.py deleted file mode 100644 index 86c1ab2dfa..0000000000 --- a/openmc/deplete/integrator/leqi.py +++ /dev/null @@ -1,173 +0,0 @@ -"""The LE/QI CFQ4 integrator.""" - -import copy -from itertools import repeat - -from .abc import Integrator -from .celi import CELIIntegrator -from .cram import timed_deplete - - -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]) - - -# 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 diff --git a/openmc/deplete/integrator/predictor.py b/openmc/deplete/integrator/predictor.py deleted file mode 100644 index 44b1516aa0..0000000000 --- a/openmc/deplete/integrator/predictor.py +++ /dev/null @@ -1,81 +0,0 @@ -"""First-order predictor algorithm.""" - -from .abc import Integrator -from .cram import timed_deplete - - -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], [] diff --git a/openmc/deplete/integrator/si_celi.py b/openmc/deplete/integrator/si_celi.py deleted file mode 100644 index 4b4ccc1e69..0000000000 --- a/openmc/deplete/integrator/si_celi.py +++ /dev/null @@ -1,109 +0,0 @@ -"""The SI-CE/LI CFQ4 integrator.""" - -import copy - -from .abc import SIIntegrator -from .cram import timed_deplete -from .celi import _celi_f1, _celi_f2 -from ..abc import OperatorResult - - -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): - """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 - Current depletion step index. Unused - - 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] diff --git a/openmc/deplete/integrator/si_leqi.py b/openmc/deplete/integrator/si_leqi.py deleted file mode 100644 index 1e48c039e7..0000000000 --- a/openmc/deplete/integrator/si_leqi.py +++ /dev/null @@ -1,129 +0,0 @@ -"""The SI-LE/QI CFQ4 integrator.""" - -import copy -from itertools import repeat - -from .abc import SIIntegrator -from .si_celi import SICELIIntegrator -from .leqi import _leqi_f1, _leqi_f2, _leqi_f3, _leqi_f4 -from .cram import timed_deplete -from ..abc import OperatorResult - - -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/integrators.py b/openmc/deplete/integrators.py new file mode 100644 index 0000000000..7637f4fe10 --- /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=-1): + """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): + """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 + 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 + """ + # 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=-1): + """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 + 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): + """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, 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): + """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 + Current depletion step index. Unused + + 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/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(): From 2514918591fc54077173b2c8230245eea4c58df8 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Wed, 7 Aug 2019 11:08:28 -0500 Subject: [PATCH 039/137] Consistently document unused depletion index The LEQI-type integrators are the only ones that need the current depletion index. All other integrators now display the same _i=None for this index in the call signature, and denote the variable as optional and unused in the docstring --- openmc/deplete/integrators.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/openmc/deplete/integrators.py b/openmc/deplete/integrators.py index 7637f4fe10..67106aa3e1 100644 --- a/openmc/deplete/integrators.py +++ b/openmc/deplete/integrators.py @@ -142,7 +142,7 @@ class CECMIntegrator(Integrator): """ _num_stages = 2 - def __call__(self, conc, rates, dt, power, _i=-1): + def __call__(self, conc, rates, dt, power, _i=None): """Integrate using CE/CM Parameters @@ -232,7 +232,7 @@ class CF4Integrator(Integrator): """ _num_stages = 4 - def __call__(self, bos_conc, bos_rates, dt, power, i): + def __call__(self, bos_conc, bos_rates, dt, power, _i=None): """Perform the integration across one time step Parameters @@ -245,8 +245,8 @@ class CF4Integrator(Integrator): Time in [s] for the entire depletion interval power : float Power of the system in [W] - i : int - Current depletion step index + _i : int, optional + Current depletion step index. Not used Returns ------- @@ -339,7 +339,7 @@ class CELIIntegrator(Integrator): """ _num_stages = 2 - def __call__(self, bos_conc, rates, dt, power, _i=-1): + def __call__(self, bos_conc, rates, dt, power, _i=None): """Perform the integration across one time step Parameters @@ -352,7 +352,7 @@ class CELIIntegrator(Integrator): Time in [s] for the entire depletion interval power : float Power of the system in [W] - _i : int + _i : int, optional Current iteration count. Not used Returns @@ -433,7 +433,7 @@ class EPCRK4Integrator(Integrator): """ _num_stages = 4 - def __call__(self, conc, rates, dt, power, _i): + def __call__(self, conc, rates, dt, power, _i=None): """Perform the integration across one time step Parameters @@ -446,7 +446,7 @@ class EPCRK4Integrator(Integrator): Time in [s] for the entire depletion interval power : float Power of the system in [W] - i : int + _i : int, optional Current depletion step index, unused. Returns @@ -663,7 +663,7 @@ class SICELIIntegrator(SIIntegrator): """ _num_stages = 2 - def __call__(self, bos_conc, bos_rates, dt, power, _i): + def __call__(self, bos_conc, bos_rates, dt, power, _i=None): """Perform the integration across one time step Parameters @@ -676,8 +676,8 @@ class SICELIIntegrator(SIIntegrator): Time in [s] for the entire depletion interval power : float Power of the system in [W] - _i : int - Current depletion step index. Unused + _i : int, optional + Current depletion step index. Not used Returns ------- From 4043e02fc78a5f43fc85f62ab5e0b9252e50b4e5 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Wed, 7 Aug 2019 11:20:34 -0500 Subject: [PATCH 040/137] Apply suggestions from code review Co-Authored-By: Paul Romano --- openmc/deplete/results.py | 2 +- openmc/deplete/results_list.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/openmc/deplete/results.py b/openmc/deplete/results.py index e05fd6bf94..59a25d716c 100644 --- a/openmc/deplete/results.py +++ b/openmc/deplete/results.py @@ -165,7 +165,7 @@ class Results(object): Returns ------- Results - - New results object + New results object """ new = Results() new.volume = {lm: self.volume[lm] for lm in local_materials} diff --git a/openmc/deplete/results_list.py b/openmc/deplete/results_list.py index 8a0efcdbf1..3b5166ed8a 100644 --- a/openmc/deplete/results_list.py +++ b/openmc/deplete/results_list.py @@ -24,7 +24,7 @@ class ResultsList(list): Returns ------- new : ResultsList - New instance of depletion results + New instance of depletion results """ with h5py.File(str(filename), "r") as fh: check_filetype_version(fh, 'depletion results', _VERSION_RESULTS[0]) From 144aa3a2e39221e7829a1d0ca1e9c42c245a9e7b Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Wed, 7 Aug 2019 11:32:20 -0500 Subject: [PATCH 041/137] Document purpose for ranges in Results.distribute --- openmc/deplete/results.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/openmc/deplete/results.py b/openmc/deplete/results.py index 59a25d716c..19911ba7f6 100644 --- a/openmc/deplete/results.py +++ b/openmc/deplete/results.py @@ -161,6 +161,9 @@ class Results(object): 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 ------- From f3e7c7c623d2aba1a524b98a89467d1002b17fc1 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Wed, 24 Jul 2019 11:58:54 -0500 Subject: [PATCH 042/137] Support for passing fission yields to Chain.form_matrix In support of using energy dependent fission yields, this commit allows a dictionary of fission yields to be passed into Chain.form_matrix. The dictionary is expected to be of the form {source_name: {target_name: yield_fraction}} where source_name and target_name are string GND names for fissionable nuclide and fission yield products, respectively. Currently, the fission yield dictionary does not have to be passed, defaulting to using the thermal yield values. This is done to make testing easier, and for back compatability. The goal of this feature, however, is to pass material [and thus spectrum] specific yields into this method. The test test_deplete_chain::test_form_matrix has been modified to pass the exact fission yield dictionary into Chain.form_matrix. The resulting matrix is compared against the matrix when no yields are passed. --- openmc/deplete/chain.py | 25 ++++++++++++++++++++++--- tests/unit_tests/test_deplete_chain.py | 7 +++++++ 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/openmc/deplete/chain.py b/openmc/deplete/chain.py index 1f16d9cafa..865f533ceb 100644 --- a/openmc/deplete/chain.py +++ b/openmc/deplete/chain.py @@ -129,6 +129,7 @@ class Chain(object): self.nuclides = [] self.reactions = [] self.nuclide_dict = OrderedDict() + self._default_fsn_yields = None def __contains__(self, nuclide): return nuclide in self.nuclide_dict @@ -374,13 +375,27 @@ class Chain(object): clean_indentation(root_elem) tree.write(str(filename), encoding='utf-8') - def form_matrix(self, rates): + def _build_default_yields(self): + """Return dictionary {str: {str: float}}""" + # Take lowest energy for back compatability + # Should be removed by end of this feature + out = {} + for nuc in self.nuclides: + if len(nuc.yield_data) == 0: + continue + _energy, yield_data = sorted(nuc.yield_data.items())[0] + out[nuc.name] = {prod: frac for prod, frac in yield_data} + 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 : dictionary of tuple to float, optional + Option to use a custom set of fission yields Returns ------- @@ -391,6 +406,11 @@ class Chain(object): matrix = defaultdict(float) reactions = set() + if fission_yields is None: + if self._default_fsn_yields is None: + self._default_fsn_yields = self._build_default_yields() + fission_yields = self._default_fsn_yields + for i, nuc in enumerate(self.nuclides): if nuc.n_decay_modes != 0: @@ -438,8 +458,7 @@ class Chain(object): # 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] diff --git a/tests/unit_tests/test_deplete_chain.py b/tests/unit_tests/test_deplete_chain.py index 33b13b596d..df82eab1b0 100644 --- a/tests/unit_tests/test_deplete_chain.py +++ b/tests/unit_tests/test_deplete_chain.py @@ -3,6 +3,7 @@ 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 @@ -220,6 +221,12 @@ 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. """ From 72bf884d9bdfe62af065642207fe7fc6b79df9c5 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Fri, 2 Aug 2019 17:00:38 -0500 Subject: [PATCH 043/137] Pass fission yields to depletion matrix_func Takes a single set of fission yields and passes them as an additional argument to matrix_func: >>> A = matrix_func(chain, rates, fission_yields) Applied to cf4, epc_rk4, celi, and leqi functions. Assumes that fission yields will not change during a depletion event. This change is probably overshadowed by how much the reaction rates may change, but still worth pointing out. --- openmc/deplete/_matrix_funcs.py | 83 +++++++++++++++++---------------- openmc/deplete/chain.py | 11 ++--- openmc/deplete/cram.py | 21 +++++++-- tests/dummy_operator.py | 69 ++++++++++++++------------- 4 files changed, 99 insertions(+), 85 deletions(-) diff --git a/openmc/deplete/_matrix_funcs.py b/openmc/deplete/_matrix_funcs.py index 1cacbd2c06..a606d739f7 100644 --- a/openmc/deplete/_matrix_funcs.py +++ b/openmc/deplete/_matrix_funcs.py @@ -1,77 +1,78 @@ """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_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): - return (1 / 12 * chain.form_matrix(rates[0]) - + 5 / 12 * chain.form_matrix(rates[1])) +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): - return 1 / 2 * chain.form_matrix(rates) +def cf4_f1(chain, rates, fission_yields=None): + return 1 / 2 * chain.form_matrix(rates, fission_yields) -def cf4_f2(chain, rates): - return -1 / 2 * chain.form_matrix(rates[0]) + chain.form_matrix(rates[1]) +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): - 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_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): - 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_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): - return 1 / 2 * chain.form_matrix(rates) +def rk4_f1(chain, rates, fission_yields=None): + return 1 / 2 * chain.form_matrix(rates, fission_yields) -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 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): - f1 = chain.form_matrix(inputs[0]) - f2 = chain.form_matrix(inputs[1]) +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): - f1 = chain.form_matrix(inputs[0]) - f2 = chain.form_matrix(inputs[1]) +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): - f1 = chain.form_matrix(inputs[0]) - f2 = chain.form_matrix(inputs[1]) - f3 = chain.form_matrix(inputs[2]) +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): - f1 = chain.form_matrix(inputs[0]) - f2 = chain.form_matrix(inputs[1]) - f3 = chain.form_matrix(inputs[2]) +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) diff --git a/openmc/deplete/chain.py b/openmc/deplete/chain.py index 865f533ceb..62158255a7 100644 --- a/openmc/deplete/chain.py +++ b/openmc/deplete/chain.py @@ -129,7 +129,6 @@ class Chain(object): self.nuclides = [] self.reactions = [] self.nuclide_dict = OrderedDict() - self._default_fsn_yields = None def __contains__(self, nuclide): return nuclide in self.nuclide_dict @@ -375,7 +374,7 @@ class Chain(object): clean_indentation(root_elem) tree.write(str(filename), encoding='utf-8') - def _build_default_yields(self): + def get_thermal_fission_yields(self): """Return dictionary {str: {str: float}}""" # Take lowest energy for back compatability # Should be removed by end of this feature @@ -383,8 +382,8 @@ class Chain(object): for nuc in self.nuclides: if len(nuc.yield_data) == 0: continue - _energy, yield_data = sorted(nuc.yield_data.items())[0] - out[nuc.name] = {prod: frac for prod, frac in yield_data} + 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): @@ -407,9 +406,7 @@ class Chain(object): reactions = set() if fission_yields is None: - if self._default_fsn_yields is None: - self._default_fsn_yields = self._build_default_yields() - fission_yields = self._default_fsn_yields + fission_yields = self.get_thermal_fission_yields() for i, nuc in enumerate(self.nuclides): diff --git a/openmc/deplete/cram.py b/openmc/deplete/cram.py index 41a4e4c710..d0c868004c 100644 --- a/openmc/deplete/cram.py +++ b/openmc/deplete/cram.py @@ -30,7 +30,9 @@ def deplete(chain, x, rates, dt, matrix_func=None): dt : float Time in [s] to deplete for maxtrix_func : Callable, optional - Function of two variables: ``chain`` and ``rates``. + 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`. @@ -40,9 +42,15 @@ def deplete(chain, x, rates, dt, matrix_func=None): Updated atom number vectors for each material """ + if not hasattr(chain, "fission_yields"): + fission_yields = repeat(chain.get_thermal_fission_yields()) + else: + fission_yields = chain.fission_yields + # Use multiprocessing pool to distribute work with Pool() as pool: - iters = zip(repeat(chain), x, rates, repeat(dt), repeat(matrix_func)) + iters = zip(repeat(chain), x, rates, repeat(dt), + fission_yields, repeat(matrix_func)) x_result = list(pool.starmap(_cram_wrapper, iters)) return x_result @@ -67,7 +75,7 @@ def timed_deplete(*args, **kwargs): return time.time() - start, results -def _cram_wrapper(chain, n0, rates, dt, matrix_func=None): +def _cram_wrapper(chain, n0, rates, dt, fission_yields, matrix_func=None): """Wraps depletion matrix creation / CRAM solve for multiprocess execution Parameters @@ -82,6 +90,9 @@ def _cram_wrapper(chain, n0, rates, dt, matrix_func=None): Time to integrate to. maxtrix_func : function, optional Function to form the depletion matrix + fission_yields : dict + Single-energy fission yields of the form + ``{parent: {product: fission_yield}}`` Returns ------- @@ -90,9 +101,9 @@ def _cram_wrapper(chain, n0, rates, dt, matrix_func=None): """ if matrix_func is None: - A = chain.form_matrix(rates) + A = chain.form_matrix(rates, fission_yields) else: - A = matrix_func(chain, rates) + A = matrix_func(chain, rates, fission_yields) return CRAM48(A, n0, dt) diff --git a/tests/dummy_operator.py b/tests/dummy_operator.py index c23abef631..acfdc5b583 100644 --- a/tests/dummy_operator.py +++ b/tests/dummy_operator.py @@ -6,6 +6,42 @@ from openmc.deplete.reaction_rates import ReactionRates from openmc.deplete.abc import TransportOperator, OperatorResult +class TestChain(object): + + @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): """This is a dummy operator class with no statistical uncertainty. @@ -21,6 +57,7 @@ class DummyOperator(TransportOperator): """ def __init__(self, previous_results=None): self.prev_res = previous_results + self.chain = TestChain() def __call__(self, vec, power, print_out=False): """Evaluates F(y) @@ -52,38 +89,6 @@ class DummyOperator(TransportOperator): # Create a fake rates object return OperatorResult(ufloat(0.0, 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]])) - @property def volume(self): """ From 8cd71be8641790d0d7a96e0359b6eecd165f8176 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Fri, 2 Aug 2019 17:48:06 -0500 Subject: [PATCH 044/137] Store depletion fission yields with common yield matrix Add two new classes for working with fission yield data on the Chain. The primary class is FissionYieldDistribution. This class stores distributions for one nuclide with potentially many energies, with the assumption that the products don't change __too__ much across energy. Looking at the CASL chain and one generated by ENDF data, this appears to be the case. Most distributions are "full" in the sense that energies produce the same products. There are some cases where one or two products may not exist for a given energy. The FissionYieldDistribution retains a dictionary-like behavior, e.g. d[0.0253]["Xe135"] is a valid command and returns the yield for Xe-135 at a "thermal" spectrum, due to yields provided at 0.0253 eV. This is done with a helper class, _FissionYield, implemented first to support this behavior, but also to allow vector-operations in combining fission yields. These _FissionYield objects store the same product vector and a view into the underlying yield_matrix for a single energy. To support simple weighting of yields, the __mull__ and __iadd__ methods are implemented. A copy method is provided to remove the chance of modifying the original yield data. test_deplete_chain and test_deplete_nuclide have been modified in order to utilize these classes, without ruining the validity of the tests. Tests for the view / copy methods and vector operations on _FissionYield objects are included. --- openmc/deplete/nuclide.py | 201 +++++++++++++++++++++-- tests/unit_tests/test_deplete_chain.py | 6 +- tests/unit_tests/test_deplete_nuclide.py | 48 +++++- 3 files changed, 232 insertions(+), 23 deletions(-) diff --git a/openmc/deplete/nuclide.py b/openmc/deplete/nuclide.py index 88d5ac6eae..80ec648802 100644 --- a/openmc/deplete/nuclide.py +++ b/openmc/deplete/nuclide.py @@ -3,12 +3,19 @@ Contains the per-nuclide components of a depletion chain. """ +from numbers import Real from collections import namedtuple +from collections.abc import Iterable, Mapping + try: import lxml.etree as ET except ImportError: import xml.etree.ElementTree as ET +from numpy import asarray, fromiter, empty + +from openmc.checkvalue import check_type, check_length + DecayTuple = namedtuple('DecayTuple', 'type target branching_ratio') DecayTuple.__doc__ = """\ @@ -83,7 +90,7 @@ class Nuclide(object): 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 + Energies at which fission product yields exist """ @@ -165,12 +172,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_data = FissionYieldDistribution.from_xml_element(fpy_elem) nuc.yield_energies = list(sorted(nuc.yield_data.keys())) return nuc @@ -211,14 +213,181 @@ 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 + + +class FissionYieldDistribution(Mapping): + """Class for storing energy-dependent fission yields for a single nuclide + + Parameters + ---------- + ordered_energies : iterable of real + Energies for which fission yield data exist + orderded_products : iterable of str + Fission products produced by this parent at all energies + group_fission_yields : numpy.ndarray or iterable of iterable of float + Array of shape ``(n_energy, n_products)`` where + ``group_fission_yields[g][j]`` is the yield of + ``ordered_products[j]`` due to a fission in energy region ``g``. + + Attributes + ---------- + energies : tuple + Energies for which fission yields exist. Converted for + indexing + products : tuple + Fission products produced at all energies. Converted + for indexing + 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`, :meth:`from_dict` + """ + + def __init__(self, ordered_energies, ordered_products, group_fission_yields): + check_type("energies", ordered_energies, Iterable, Real) + self.energies = tuple(ordered_energies) + check_type("products", ordered_products, Iterable, str) + self.products = tuple(ordered_products) + yield_matrix = asarray(group_fission_yields, dtype=float) + if yield_matrix.shape != (len(self.energies), len(self.products)): + raise ValueError( + "Shape of yield matrix inconsistent. " + "Should be ({}, {}), is {}".format( + len(energy_map), len(ordered_products), yield_matrix.shape)) + 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) + + @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 + """ + 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() + yield_mapobj = map(float, yield_elem.find("data").text.split()) + # Get a map of products to their corresponding yield + yields[energy] = dict(zip(products, yield_mapobj)) + + return cls.from_dict(yields) + + @classmethod + def from_dict(cls, fission_yields): + """Construct a distribution from a dictionary of yields + + Parameters + ----------- + fission_yields : dict + Dictionary ``{energy: {product: yield}}`` + + Returns + ------- + FissionYieldDistribution + """ + # mapping {energy: {product: value}} + energies = tuple(sorted(fission_yields)) + + # Get a consistent set of products to produce a matrix of yields + shared_prod = set() + for prod_set in map(set, fission_yields.values()): + shared_prod |= prod_set + ordered_prod = tuple(sorted(shared_prod)) + + yield_matrix = empty((len(energies), len(shared_prod))) + + for g_index, energy in enumerate(sorted(energies)): + prod_map = fission_yields[energy] + for prod_ix, product in enumerate(ordered_prod): + yield_val = prod_map.get(product) + if yield_val is None: + yield_matrix[g_index, prod_ix] = 0.0 + else: + yield_matrix[g_index, prod_ix] = yield_val + + return cls(energies, ordered_prod, yield_matrix) + + 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 + + Abstracted to support nested dictionary-like behavior for + :class:`FissionYieldDistribution`, and allowing math operations + on a single vector of yields + + Parameters + ---------- + products : tuple of str + Products for this specific distribution + yields : numpy.ndarray + View into associated :attr:`FissionYieldDistribution.yield_matrix` + """ + + def __init__(self, products, yields): + self.products = products + self.yields = yields + + def __getitem__(self, product): + if product not in self.products: + raise KeyError(product) + return self.yields[self.products.index(product)] + + def __len__(self): + return len(self.products) + + def __iter__(self): + return iter(self.products) + + def __iadd__(self, other): + """Increment value from other fission yield""" + self.yields += other.yields + return self + + def __mul__(self, value): + return _FissionYield(self.products, self.yields * value) + + def copy(self): + """Return an identical yield object, with unique yields""" + return _FissionYield(self.products, self.yields.copy()) diff --git a/tests/unit_tests/test_deplete_chain.py b/tests/unit_tests/test_deplete_chain.py index df82eab1b0..d31933c34a 100644 --- a/tests/unit_tests/test_deplete_chain.py +++ b/tests/unit_tests/test_deplete_chain.py @@ -131,7 +131,8 @@ def test_from_xml(simple_chain): # Yield tests 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): @@ -163,7 +164,8 @@ def test_export_to_xml(run_in_tmpdir): 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.from_dict({ + 0.0253: {"A": 0.0292737, "B": 0.002566345}}) chain = Chain() chain.nuclides = [A, B, C] diff --git a/tests/unit_tests/test_deplete_nuclide.py b/tests/unit_tests/test_deplete_nuclide.py index f2a101d2a9..07f2edb85c 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 + from openmc.deplete import nuclide @@ -69,11 +71,10 @@ def test_from_xml(): nuclide.ReactionTuple('(n,gamma)', 'U236', 6545200.0, 1.0), nuclide.ReactionTuple('fission', None, 193405400.0, 1.0), ] + expected_yield_data = nuclide.FissionYieldDistribution.from_dict({ + 0.0253: {"Xe138": 0.0481413, "Zr100": 0.0497641, "Te134": 0.062155}}) assert u235.yield_energies == [0.0253] - assert u235.yield_data == { - 0.0253: [('Te134', 0.062155), ('Zr100', 0.0497641), - ('Xe138', 0.0481413)] - } + assert u235.yield_data == expected_yield_data def test_to_xml_element(): @@ -91,7 +92,8 @@ def test_to_xml_element(): 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.from_dict( + {0.0253: {"A": 0.0292737, "B": 0.002566345}}) element = C.to_xml_element() assert element.get("half_life") == "0.123" @@ -114,3 +116,39 @@ 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.from_dict(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_matrix = 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_matrix) + + # Test the operations / special methods for fission yield + orig_yield_obj = yield_dist[0.0253] + # __getitem__ return yields as a view into yield matrix + assert orig_yield_obj.yields.base is yield_dist.yield_matrix + copied_yield = orig_yield_obj.copy() + # copied yields own their own memory -> not a view + assert copied_yield.yields.base is None + + # Fission yield feature uses scaled and incremented + mod_yields = orig_yield_obj * 2 + assert numpy.array_equal(orig_yield_obj.yields * 2, mod_yields.yields) + mod_yields += orig_yield_obj + assert numpy.array_equal(orig_yield_obj.yields * 3, mod_yields.yields) + From 599d473888be6e443cac4465987078a58eec9c19 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Fri, 2 Aug 2019 17:51:33 -0500 Subject: [PATCH 045/137] Add FissionYieldHelper for energy-dependent fission yields Operator now has a new helper for tallying fission rates and computing fission yields using energy dependency. The FissionYieldHelper creates fission rate tallies across burnable materials with an energy filter. The energy filter corresponds to each energy group for which there are fission yields. During the operator's unpacking of tallies, this helper computes a relative fission contribution by normalizing fission rates, such that the sum of all group fission rates in each material and each nuclide is unity. A single fission yield for each parent nuclide is computed by weighting the energy-dependent fission yields by these relative contributions. A nested dictionary {parent: {product: yield}} is added to a library, and then attached to the depletion chain at the end of the unpacking. These libraries are used during the depletion process instead of the only-thermal yields from before. A test was added that creates a proxy helper that doesn't use the C-API, but unpacks the "fission tally" and computes the fission yield library in an identical manner as the actual class. Since the proxy helper is a subclass of the one used in real transport, the functionality is consistent. --- openmc/deplete/chain.py | 3 - openmc/deplete/helpers.py | 193 ++++++++++++++++++++++- openmc/deplete/operator.py | 22 ++- tests/unit_tests/test_deplete_helpers.py | 99 ++++++++++++ 4 files changed, 309 insertions(+), 8 deletions(-) create mode 100644 tests/unit_tests/test_deplete_helpers.py diff --git a/openmc/deplete/chain.py b/openmc/deplete/chain.py index 62158255a7..31b074bddb 100644 --- a/openmc/deplete/chain.py +++ b/openmc/deplete/chain.py @@ -452,9 +452,6 @@ 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.. for product, y in fission_yields[nuc.name].items(): yield_val = y * path_rate if yield_val != 0.0: diff --git a/openmc/deplete/helpers.py b/openmc/deplete/helpers.py index 66722c00a2..07c8e20242 100644 --- a/openmc/deplete/helpers.py +++ b/openmc/deplete/helpers.py @@ -1,11 +1,13 @@ """ Class for normalizing fission energy deposition """ +from collections import defaultdict from itertools import product -from numpy import dot, zeros +from numpy import dot, zeros, newaxis, divide, asarray -from openmc.capi import Tally, MaterialFilter +from openmc.capi import ( + Tally, MaterialFilter, EnergyFilter) from .abc import ReactionRateHelper, EnergyHelper # ------------------------------------- @@ -142,3 +144,190 @@ 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 FissionYieldHelper(object): + """Class for using energy-dependent fission yields in depletion chain + + Creates a tally across all burnable materials to score the fission + rate in nuclides with yield data. An energy filter is used to + compute this rates in a group structure corresponding to the + fission yield data. This tally data is used to compute the + relative number of fission events in each energy region, + which serve as the weights for each energy-dependent fission + yield distribution. + + Parameters + ---------- + chain_nuclides : iterable of openmc.deplete.nuclide + Nuclides tracked in the depletion chain. Not necessary + that all have yield data. + n_bmats : int + Number of burnable materials tracked in the problem + + Attributes + ---------- + energy_bounds : tuple of float + Sorted energy bounds from the tally filter + results : numpy.ndarray + Array of tally results for this process with shape + ``(n_local_mat, n_energy, n_nucs)`` + libraries : list of dict + List of fission yield dictionaries of the form + ``{parent: {product: yield}}``. Populated in + :meth:`compute_yields` and reset during + :meth:`unpack`. + """ + + def __init__(self, chain_nuclides, n_bmats): + self.libraries = [] + self._chain_nuclides = {} + self._chain_set = set() + self._tally_map = {} + # TODO Support user-requested minimum energy? + yield_energies = {0.0} + + # Get nuclides with fission yield data, names + # and all energy points + # Names are provided from operator tally nuclides + for nuc in chain_nuclides: + if len(nuc.yield_data) == 0: + continue + self._chain_nuclides[nuc.name] = nuc + self._chain_set.add(nuc.name) + yield_energies.update(nuc.yield_energies) + + # Create energy grid + self._energy_bounds = tuple(sorted(yield_energies)) + self.n_bmats = n_bmats + + self._reaction_tally = None + self.results = None + self.local_indexes = None + + @property + def energy_bounds(self): + return self._energy_bounds + + def generate_tallies(self, materials, mat_indexes): + """Construct the fission rate tally + + Parameters + ---------- + materials : iterable of C-API materials + Materials to be used in :class:`openmc.capi.MaterialFilter` + mat_indexes : iterable of int + Indexes for materials in ``materials`` tracked on this + process + """ + # Tally group-wise fission reaction rates + self._reaction_tally = Tally() + self._reaction_tally.scores = ['fission'] + + # Tally energy-weighted group-wise fission reaction rate + # Used to evaluated linear interpolation between fission yield points + self._weighted_reaction_tally = Tally() + self._weighted_reaction_tally.scores = ['fission'] + + filters = [ + MaterialFilter(materials), EnergyFilter(self._energy_bounds)] + + self._reaction_tally.filters = filters + self.local_indexes = asarray(mat_indexes) + + def set_fissionable_nuclides(self, nuclides): + """List of string of nuclides with data to be tallied + + Parameters + ---------- + nuclides : iterable of str + nuclides with non-zero densities that are candidates + for the fission tally. Not necessary that all are nuclides + with fission yields, but at least one must be + + Returns + ------- + nuclides : tuple of str + Nuclides ordered as they appear in the tally and in + the nuclide column of :attr:`results` + + Raises + ------ + ValueError + If no nuclides in ``nuclides`` are tracked on this + object + """ + # Set of all nuclides with positive density + # and fission yield data + nuc_set = self._chain_set & set(nuclides) + if len(nuc_set) == 0: + raise ValueError( + "No overlap between chain nuclides with fission yields and " + "requested tally nuclides") + nuclides = tuple(sorted(nuc_set)) + self._tally_index = [self._chain_nuclides[n] for n in nuclides] + self._reaction_tally.nuclides = nuclides + return nuclides + + def unpack(self): + """Unpack fission rate tallies to produce :attr:`results` + + Resets :attr:`libraries` under the assumption this is called + during the :class:`openmc.deplete.Operator` unpackign process + """ + # clear old libraries + self.libraries = [] + + # get view into tally results + # new shape: [material, energy, parent nuclide] + result_view = self._reaction_tally.results[..., 1].reshape( + self.n_bmats, len(self._energy_bounds) - 1, + len(self._reaction_tally.nuclides)) + + # Get results specific to this process + self.results = result_view[self.local_indexes, ...] + + # scale fission yields proportional to total fission rate + fsn_rate = self.results.sum(axis=1) + # TODO Guard against divide by zero + self.results /= fsn_rate[:, newaxis, :] + + def compute_yields(self, local_mat_index): + """Compute single fission yields using :attr:`results` + + Produces a new library in :attr:`self.libraries` + + 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` + """ + tally_results = self.results[local_mat_index] + + # Dictionary {parent_nuclide : [product, yield_vector]} + initial_library = {} + for i_energy, energy in enumerate(self._energy_bounds[1:]): + for i_nuc, fiss_frac in enumerate(tally_results[i_energy]): + parent = self._tally_index[i_nuc] + yield_data = parent.yield_data.get(energy) + if yield_data is None: + continue + if parent not in initial_library: + initial_library[parent] = yield_data * fiss_frac + continue + initial_library[parent] += yield_data * fiss_frac + + # convert to dictionary that can be passed to Chain.form_matrix + # {parent: {product: yield}} + library = {} + for k, yield_obj in initial_library.items(): + library[k.name] = dict(zip(yield_obj.products, yield_obj.yields)) + + self.libraries.append(library) diff --git a/openmc/deplete/operator.py b/openmc/deplete/operator.py index f8288841f7..0c8c72987b 100644 --- a/openmc/deplete/operator.py +++ b/openmc/deplete/operator.py @@ -25,7 +25,8 @@ from .abc import TransportOperator, OperatorResult from .atom_number import AtomNumber from .reaction_rates import ReactionRates from .results_list import ResultsList -from .helpers import DirectReactionRateHelper, ChainFissionHelper +from .helpers import ( + DirectReactionRateHelper, ChainFissionHelper, FissionYieldHelper) def _distribute(items): @@ -177,6 +178,8 @@ class Operator(TransportOperator): self._rate_helper = DirectReactionRateHelper( self.reaction_rates.n_nuc, self.reaction_rates.n_react) self._energy_helper = ChainFissionHelper() + self._fsn_yield_helper = FissionYieldHelper( + self.chain.nuclides, len(self.burnable_mats)) def __call__(self, vec, power): """Runs a simulation. @@ -204,8 +207,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._fsn_yield_helper.set_fissionable_nuclides(nuclides) # Run OpenMC openmc.capi.reset() @@ -409,6 +414,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._fsn_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_[:])) @@ -554,6 +563,7 @@ class Operator(TransportOperator): # Keep track of energy produced from all reactions in eV per source # particle self._energy_helper.reset() + self._fsn_yield_helper.unpack() # Create arrays to store fission Q values, reaction rates, and nuclide # numbers, zeroed out in material iteration @@ -576,6 +586,9 @@ class Operator(TransportOperator): tally_rates = self._rate_helper.get_material_rates( mat_index, nuc_ind, react_ind) + # Compute fission yields for this material + self._fsn_yield_helper.compute_yields(i) + # Accumulate energy from fission self._energy_helper.update(tally_rates[:, fission_ind], mat_index) @@ -589,6 +602,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 = self._fsn_yield_helper.libraries + return OperatorResult(k_combined, rates) def _get_nuclides_with_data(self): diff --git a/tests/unit_tests/test_deplete_helpers.py b/tests/unit_tests/test_deplete_helpers.py new file mode 100644 index 0000000000..5db979098b --- /dev/null +++ b/tests/unit_tests/test_deplete_helpers.py @@ -0,0 +1,99 @@ +"""Test the Operator helpers""" + +from unittest.mock import Mock + +import pytest +import numpy +from numpy.testing import assert_array_equal + +from openmc.deplete.nuclide import Nuclide, FissionYieldDistribution +from openmc.deplete.helpers import FissionYieldHelper + + +class FissionYieldHelperProxy(FissionYieldHelper): + + def __init__(self, chain_nuclides, n_bmats): + super().__init__(chain_nuclides, n_bmats) + self._reaction_tally = Mock() + self.local_indexes = numpy.array([0]) + + def generate_tallies(self, *args, **kwargs): + # Avoid calls to the C-API + pass + + +def test_fission_yield_helper(): + """Test the collection of fission yield data using approximated tallies + """ + u5yield_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}} + u235 = Nuclide() + u235.name = "U235" + u235.yield_energies = (0.0253, 1.40e7) + u235.yield_data = FissionYieldDistribution.from_dict(u5yield_dict) + + u8yield_dict = {5.00e5: {"Xe135": 1.12e-3, "Gd155": 1.32e-12}} + u238 = Nuclide() + u238.name = "U238" + u238.yield_energies = (5.00e5, ) + u238.yield_data = FissionYieldDistribution.from_dict(u8yield_dict) + + xe135 = Nuclide() + xe135.name = "Xe135" + + n_bmats = 2 + helper = FissionYieldHelperProxy([u235, u238, xe135], n_bmats) + + assert helper.energy_bounds == (0, 0.0253, 5.00e5, 1.40e7) + + # test that tally must be created with nuclides with yields + with pytest.raises(ValueError, match="No overlap"): + helper.set_fissionable_nuclides(["Xe135", ]) + + act_nucs = helper.set_fissionable_nuclides(["U235", "U238", "Xe135"]) + assert act_nucs == ("U235", "U238") + + # Emulate getting tally data from transport run + # Test as if this Helper is responsible for one of two materials + # Tally results ordered [n_mat * n_ene, n_fiss_nuc, 3] + + tally_res = numpy.zeros((3 * n_bmats, 2, 3)) + u5_fiss_rates = numpy.array([1.0, 1.5, 2.0]) + u8_fiss_rates = numpy.array([0.0, 1.0, 1.0]) + tally_res[:3, 0, 1] = u5_fiss_rates + tally_res[:3, 1, 1] = u8_fiss_rates + + helper._reaction_tally.results = tally_res + helper.unpack() # compute yield fractions + + # Compare fraction fission rate from helper.reset + exp_results = numpy.empty((1, 3, 2)) + # Fraction of fission events in each energy range + u5_vec = u5_fiss_rates / u5_fiss_rates.sum() + u8_vec = u8_fiss_rates / u8_fiss_rates.sum() + exp_results[..., 0] = u5_vec + exp_results[..., 1] = u8_vec + assert_array_equal(helper.results, exp_results) + + # Compute and compare the library of fission yields + exp_lib = { + "U235": { + "Xe135": (u5yield_dict[0.0253]["Xe135"] * u5_vec[0] + + u5yield_dict[1.40e7]["Xe135"] * u5_vec[2]), + "Gd155": (u5yield_dict[0.0253]["Gd155"] * u5_vec[0] + + u5yield_dict[1.40e7]["Gd155"] * u5_vec[2]), + "Sm149": u5yield_dict[0.0253]["Sm149"] * u5_vec[0], + }, + "U238": { + "Xe135": u8yield_dict[5.00e5]["Xe135"] * u8_vec[1], + "Gd155": u8yield_dict[5.00e5]["Gd155"] * u8_vec[1], + } + } + + assert len(helper.libraries) == 0 + helper.compute_yields(0) + assert len(helper.libraries) == 1 + act_library = helper.libraries[0] + for parent, sub_yields in exp_lib.items(): + assert act_library[parent] == pytest.approx(sub_yields) From 27f2c6b15fe951104d48fda726e4ddc8764b2abb Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Mon, 5 Aug 2019 09:28:47 -0500 Subject: [PATCH 046/137] Guard against divide by zero in fission yield helper Create an empty matrix to fit energy-dependent fission rates for [materials, energies, nuclides]. Use the nonzero method on total fission rate to find indicies along the first and last axis for non-zero total fission rate. Populate corresponding fractional fission rates by dividing group fission rates by total fission rate, using the indices for materials and nuclides with non-zero total fission rate. Use numpy.where to find where total fission rate is zero, and directly set these locations to zero in the final result matrix. Also clean up some lint in openmc.deplete.nuclide related to old variable names and unused imports. --- openmc/deplete/helpers.py | 20 +++++++++++++------- openmc/deplete/nuclide.py | 7 ++++--- tests/unit_tests/test_deplete_nuclide.py | 1 - 3 files changed, 17 insertions(+), 11 deletions(-) diff --git a/openmc/deplete/helpers.py b/openmc/deplete/helpers.py index 07c8e20242..43a835908c 100644 --- a/openmc/deplete/helpers.py +++ b/openmc/deplete/helpers.py @@ -1,10 +1,9 @@ """ Class for normalizing fission energy deposition """ -from collections import defaultdict from itertools import product -from numpy import dot, zeros, newaxis, divide, asarray +from numpy import dot, zeros, newaxis, asarray, empty_like, where from openmc.capi import ( Tally, MaterialFilter, EnergyFilter) @@ -290,12 +289,19 @@ class FissionYieldHelper(object): len(self._reaction_tally.nuclides)) # Get results specific to this process - self.results = result_view[self.local_indexes, ...] + fission_rates = result_view[self.local_indexes] + self.results = empty_like(fission_rates) - # scale fission yields proportional to total fission rate - fsn_rate = self.results.sum(axis=1) - # TODO Guard against divide by zero - self.results /= fsn_rate[:, newaxis, :] + # scale group fission rates proportional to total fission rate + fission_total = fission_rates.sum(axis=1) + nz_mat, nz_nuc = fission_total.nonzero() + self.results[nz_mat, :, nz_nuc] = ( + fission_rates[nz_mat, :, nz_nuc] + / fission_total[nz_mat, newaxis, nz_nuc]) + + # directly set values to zero where total fission rate is zero + z_mat, z_nuc = where(fission_total == 0.0) + self.results[z_mat, :, z_nuc] = 0.0 def compute_yields(self, local_mat_index): """Compute single fission yields using :attr:`results` diff --git a/openmc/deplete/nuclide.py b/openmc/deplete/nuclide.py index 80ec648802..8a0a9dc642 100644 --- a/openmc/deplete/nuclide.py +++ b/openmc/deplete/nuclide.py @@ -12,9 +12,9 @@ try: except ImportError: import xml.etree.ElementTree as ET -from numpy import asarray, fromiter, empty +from numpy import asarray, empty -from openmc.checkvalue import check_type, check_length +from openmc.checkvalue import check_type DecayTuple = namedtuple('DecayTuple', 'type target branching_ratio') @@ -260,7 +260,8 @@ class FissionYieldDistribution(Mapping): raise ValueError( "Shape of yield matrix inconsistent. " "Should be ({}, {}), is {}".format( - len(energy_map), len(ordered_products), yield_matrix.shape)) + len(ordered_energies), len(ordered_products), + yield_matrix.shape)) self.yield_matrix = yield_matrix def __len__(self): diff --git a/tests/unit_tests/test_deplete_nuclide.py b/tests/unit_tests/test_deplete_nuclide.py index 07f2edb85c..2a3993921f 100644 --- a/tests/unit_tests/test_deplete_nuclide.py +++ b/tests/unit_tests/test_deplete_nuclide.py @@ -151,4 +151,3 @@ def test_fission_yield_distribution(): assert numpy.array_equal(orig_yield_obj.yields * 2, mod_yields.yields) mod_yields += orig_yield_obj assert numpy.array_equal(orig_yield_obj.yields * 3, mod_yields.yields) - From c732336536669e5232fef19b0261fb2d941e0ef7 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Tue, 6 Aug 2019 13:38:13 -0500 Subject: [PATCH 047/137] Avert bad numpy slices unpacking fission yields with no materials For the case where there are more processes than burnable materials, e.g. single pin cell or assembly with one burnable material, then the local_indexes attribute on some FissionYieldHelpers will be an empty array. This causes errors trying to use this as an indexer into the tally results. Instead, simply return from the unpacking if this is the case. The compute_yields function is called inside the Operator's unpacking of it's local materials, so that __should__ not be in a position to call compute_yields for a process with no burnable materials. --- openmc/deplete/helpers.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/openmc/deplete/helpers.py b/openmc/deplete/helpers.py index 43a835908c..8f35fac433 100644 --- a/openmc/deplete/helpers.py +++ b/openmc/deplete/helpers.py @@ -277,8 +277,14 @@ class FissionYieldHelper(object): """Unpack fission rate tallies to produce :attr:`results` Resets :attr:`libraries` under the assumption this is called - during the :class:`openmc.deplete.Operator` unpackign process + during the :class:`openmc.deplete.Operator` unpacking process """ + # if this process is not responsible for depleting anything + # [more processes than burnable materials] + # don't do anything + if self.local_indexes.size == 0: + return + # clear old libraries self.libraries = [] From 5fb8ec2d7aeb0af1d408898b6f57b620132bf39e Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Thu, 8 Aug 2019 08:30:23 -0500 Subject: [PATCH 048/137] Document and test the default Chain fission yields --- openmc/deplete/chain.py | 26 +++++++++++++++++++++----- tests/unit_tests/test_deplete_chain.py | 8 ++++++++ 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/openmc/deplete/chain.py b/openmc/deplete/chain.py index 31b074bddb..2354158562 100644 --- a/openmc/deplete/chain.py +++ b/openmc/deplete/chain.py @@ -375,9 +375,19 @@ class Chain(object): tree.write(str(filename), encoding='utf-8') def get_thermal_fission_yields(self): - """Return dictionary {str: {str: float}}""" - # Take lowest energy for back compatability - # Should be removed by end of this feature + """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 len(nuc.yield_data) == 0: @@ -393,14 +403,20 @@ class Chain(object): ---------- rates : numpy.ndarray 2D array indexed by (nuclide, reaction) - fission_yields : dictionary of tuple to float, optional - Option to use a custom set of fission yields + 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() diff --git a/tests/unit_tests/test_deplete_chain.py b/tests/unit_tests/test_deplete_chain.py index d31933c34a..546fa99c3e 100644 --- a/tests/unit_tests/test_deplete_chain.py +++ b/tests/unit_tests/test_deplete_chain.py @@ -230,6 +230,7 @@ def test_form_matrix(simple_chain): 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. """ chain = Chain() @@ -338,3 +339,10 @@ def test_capture_branch_failures(simple_chain): br = {"C": {"A": 1.0, "B": 1.0}} with pytest.raises(ValueError, match="C ratios"): simple_chain.set_capture_branches(br) + + +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}} From 3f42fc8505a37af535cf5786e14c38891da3a034 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Thu, 8 Aug 2019 08:50:32 -0500 Subject: [PATCH 049/137] Remove FissionYieldHelper.libraries; Return from compute_yields Previously the yields were appended into the libraries attribute. Now, the yields are returned directly and not appended at all. It is up to the caller to place these yields in the correct location. The Operator maintains a list of of the libraries that is updated through the unpacking method, and then set onto the Chain after working through all local materials --- openmc/deplete/helpers.py | 26 ++++++++---------------- openmc/deplete/operator.py | 7 +++++-- tests/unit_tests/test_deplete_helpers.py | 5 +---- 3 files changed, 15 insertions(+), 23 deletions(-) diff --git a/openmc/deplete/helpers.py b/openmc/deplete/helpers.py index 8f35fac433..ac81d0ac7a 100644 --- a/openmc/deplete/helpers.py +++ b/openmc/deplete/helpers.py @@ -163,7 +163,7 @@ class FissionYieldHelper(object): Parameters ---------- - chain_nuclides : iterable of openmc.deplete.nuclide + chain_nuclides : iterable of openmc.deplete.Nuclide Nuclides tracked in the depletion chain. Not necessary that all have yield data. n_bmats : int @@ -176,15 +176,9 @@ class FissionYieldHelper(object): results : numpy.ndarray Array of tally results for this process with shape ``(n_local_mat, n_energy, n_nucs)`` - libraries : list of dict - List of fission yield dictionaries of the form - ``{parent: {product: yield}}``. Populated in - :meth:`compute_yields` and reset during - :meth:`unpack`. """ def __init__(self, chain_nuclides, n_bmats): - self.libraries = [] self._chain_nuclides = {} self._chain_set = set() self._tally_map = {} @@ -245,7 +239,7 @@ class FissionYieldHelper(object): Parameters ---------- nuclides : iterable of str - nuclides with non-zero densities that are candidates + Nuclides with non-zero densities that are candidates for the fission tally. Not necessary that all are nuclides with fission yields, but at least one must be @@ -275,9 +269,6 @@ class FissionYieldHelper(object): def unpack(self): """Unpack fission rate tallies to produce :attr:`results` - - Resets :attr:`libraries` under the assumption this is called - during the :class:`openmc.deplete.Operator` unpacking process """ # if this process is not responsible for depleting anything # [more processes than burnable materials] @@ -285,9 +276,6 @@ class FissionYieldHelper(object): if self.local_indexes.size == 0: return - # clear old libraries - self.libraries = [] - # get view into tally results # new shape: [material, energy, parent nuclide] result_view = self._reaction_tally.results[..., 1].reshape( @@ -312,7 +300,7 @@ class FissionYieldHelper(object): def compute_yields(self, local_mat_index): """Compute single fission yields using :attr:`results` - Produces a new library in :attr:`self.libraries` + Produces a new library in :attr:`libraries` Parameters ---------- @@ -320,6 +308,11 @@ class FissionYieldHelper(object): 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}}`` """ tally_results = self.results[local_mat_index] @@ -337,9 +330,8 @@ class FissionYieldHelper(object): initial_library[parent] += yield_data * fiss_frac # convert to dictionary that can be passed to Chain.form_matrix - # {parent: {product: yield}} library = {} for k, yield_obj in initial_library.items(): library[k.name] = dict(zip(yield_obj.products, yield_obj.yields)) - self.libraries.append(library) + return library diff --git a/openmc/deplete/operator.py b/openmc/deplete/operator.py index 0c8c72987b..d99c96f185 100644 --- a/openmc/deplete/operator.py +++ b/openmc/deplete/operator.py @@ -565,6 +565,9 @@ class Operator(TransportOperator): self._energy_helper.reset() self._fsn_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 number = np.empty(rates.n_nuc) @@ -587,7 +590,7 @@ class Operator(TransportOperator): mat_index, nuc_ind, react_ind) # Compute fission yields for this material - self._fsn_yield_helper.compute_yields(i) + fission_yields.append(self._fsn_yield_helper.compute_yields(i)) # Accumulate energy from fission self._energy_helper.update(tally_rates[:, fission_ind], mat_index) @@ -603,7 +606,7 @@ class Operator(TransportOperator): rates *= power / energy # Store new fission yields on the chain - self.chain.fission_yields = self._fsn_yield_helper.libraries + self.chain.fission_yields = fission_yields return OperatorResult(k_combined, rates) diff --git a/tests/unit_tests/test_deplete_helpers.py b/tests/unit_tests/test_deplete_helpers.py index 5db979098b..15ae1550ed 100644 --- a/tests/unit_tests/test_deplete_helpers.py +++ b/tests/unit_tests/test_deplete_helpers.py @@ -91,9 +91,6 @@ def test_fission_yield_helper(): } } - assert len(helper.libraries) == 0 - helper.compute_yields(0) - assert len(helper.libraries) == 1 - act_library = helper.libraries[0] + act_library = helper.compute_yields(0) for parent, sub_yields in exp_lib.items(): assert act_library[parent] == pytest.approx(sub_yields) From d10b83e06454b573e19e77375e46cb2a2040b423 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Thu, 8 Aug 2019 08:58:37 -0500 Subject: [PATCH 050/137] Document FissionYieldDistribution Pretty internal, but people may be curious --- docs/source/pythonapi/deplete.rst | 2 ++ openmc/deplete/nuclide.py | 14 +++++++++++--- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/docs/source/pythonapi/deplete.rst b/docs/source/pythonapi/deplete.rst index a72504ac92..243dcad430 100644 --- a/docs/source/pythonapi/deplete.rst +++ b/docs/source/pythonapi/deplete.rst @@ -65,6 +65,7 @@ for a depletion chain: DecayTuple Nuclide ReactionTuple + FissionYieldDistribution The following classes are used during a depletion simulation and store auxiliary data, such as number densities and reaction rates for each material. @@ -77,6 +78,7 @@ data, such as number densities and reaction rates for each material. AtomNumber ChainFissionHelper DirectReactionRateHelper + FissionYieldHelper OperatorResult ReactionRates Results diff --git a/openmc/deplete/nuclide.py b/openmc/deplete/nuclide.py index 8a0a9dc642..23f14331e2 100644 --- a/openmc/deplete/nuclide.py +++ b/openmc/deplete/nuclide.py @@ -221,9 +221,17 @@ class Nuclide(object): class FissionYieldDistribution(Mapping): """Class for storing energy-dependent fission yields for a single nuclide + Can be used as a dictionary mapping energies and products to fission + yields:: + + >>> fydist = FissionYieldDistribution.from_dict({ + ... {0.0253: {"Xe135": 0.021}}) + >>> fydist[0.0253]["Xe135"] + 0.021 + Parameters ---------- - ordered_energies : iterable of real + ordered_energies : iterable of float Energies for which fission yield data exist orderded_products : iterable of str Fission products produced by this parent at all energies @@ -247,7 +255,7 @@ class FissionYieldDistribution(Mapping): See Also -------- - :meth:`from_xml`, :meth:`from_dict` + :meth:`from_xml_element`, :meth:`from_dict` """ def __init__(self, ordered_energies, ordered_products, group_fission_yields): @@ -354,7 +362,7 @@ class FissionYieldDistribution(Mapping): class _FissionYield(Mapping): """Mapping for fission yields of a parent at a specific energy - Abstracted to support nested dictionary-like behavior for + Separated to support nested dictionary-like behavior for :class:`FissionYieldDistribution`, and allowing math operations on a single vector of yields From c2137187a4b0eb5c6bfaf2278a80c403e6c9bc4b Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Tue, 6 Aug 2019 11:48:57 -0500 Subject: [PATCH 051/137] Add Te129 to CASL chain Closes: #1307 This serves as a path way for production in I129 without impacting transport because 1) Te129 has no ground state cross section data, due to 2) Te129 decays to I129 with a half life of about an hour Running scripts/openmc-make-depletion-chain-casl now has the following changes: 1) Te129 is present 2) Te129_m1 decays to Te129 with branching ratio of 2/3 using ENDFB 7.1 data [pulled using tools/ci/download-xs.sh] --- scripts/casl_chain.py | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/casl_chain.py b/scripts/casl_chain.py index 86389e3ced..2477a5e892 100755 --- a/scripts/casl_chain.py +++ b/scripts/casl_chain.py @@ -187,6 +187,7 @@ CASL_CHAIN = { 'Sb127': (False, 3, 2, None), 'Te127': (False, 3, -1, None), 'Te127_m1': (False, 3, -1, None), + 'Te129': (False, 3, 2, None), 'Te129_m1': (False, 3, 2, None), 'Te132': (False, 3, 2, None), 'I127': (True, 3, 1, None), From ec7af8f376c0c8830bcb878c59b10a9c375297b5 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Thu, 8 Aug 2019 15:27:57 -0500 Subject: [PATCH 052/137] Add validation for deplete.Nuclide branching ratios, fission yields Method Nuclide.validate traverses over decay mode and reaction lists, as well as all fission yield data to check for inconsistencies. The following checks are performed: 1) For all non-fission reactions and decay modes, do the sum of branching ratios equal about 1? 2) For fission reactions, do the sum of fission yield fractions equal about 2? Users are allowed to supply three control arguments: - strict: bool that controls if errors are raised [True] or warnings [False] - quiet: bool that controls if warnings are printed [False] or supressed [True] - tolerance: float that provides some wiggle room on the comparisons ``y - tol <= x <= y + tol`` The method returns a boolean if 1) no inconsistencies were found and strict evaluates to True, 2) strict evaluates to False regardless of quiet. If ``strict`` evaluates to False and ``quiet`` evaluates to True, the method will return at the first inconsistency. No type checking is done because this will potentially be called by ``openmc.deplete.Chain`` for many nuclides as the primary entry point. Type and value checking will be done there. A unit test was added that creates a valid nuclide, and then strategically invalidates it to check for the various exceptions and error messages. --- openmc/deplete/nuclide.py | 101 ++++++++++++++++++++++- tests/unit_tests/test_deplete_nuclide.py | 91 ++++++++++++++++++++ 2 files changed, 191 insertions(+), 1 deletion(-) diff --git a/openmc/deplete/nuclide.py b/openmc/deplete/nuclide.py index 88d5ac6eae..9c07a91a9a 100644 --- a/openmc/deplete/nuclide.py +++ b/openmc/deplete/nuclide.py @@ -3,7 +3,9 @@ Contains the per-nuclide components of a depletion chain. """ -from collections import namedtuple +from collections import namedtuple, defaultdict +from operator import attrgetter, itemgetter +from warnings import warn try: import lxml.etree as ET except ImportError: @@ -222,3 +224,100 @@ class Nuclide(object): data_elem.text = ' '.join(str(x[1]) for x in self.yield_data[E]) 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, + do the sum of branching ratios equal about 1? + 2) for fission reactions, do the sum of fission yield + fractions equal about 2? + + Parameters + ---------- + strict : bool, optional + Raise exceptions at the first inconsistency if true. + Otherwise mark a warning + quiet : bool, optional + Flag to supress 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 + """ + + branch_getter = attrgetter("branching_ratio") + msg_func = ("Nuclide {name} has {prop} that sum to {actual} " + "instead of {expected} +/- {tol:7.4e}").format + type_map = defaultdict(set) + valid = True + + # check decay modes + if len(self.decay_modes) > 0: + sum_br = sum(map(branch_getter, 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 len(self.reactions) > 0: + type_map.clear() + for reaction in self.reactions: + type_map[reaction.type].add(reaction) + for rxn_type, reactions in type_map.items(): + sum_rxn = sum(map(branch_getter, 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 len(self.yield_data) > 0: + yield_getter = itemgetter(1) + for energy, yield_list in self.yield_data.items(): + sum_yield = sum(map(yield_getter, yield_list)) + 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 diff --git a/tests/unit_tests/test_deplete_nuclide.py b/tests/unit_tests/test_deplete_nuclide.py index f2a101d2a9..a93f6bb4c9 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 pytest + from openmc.deplete import nuclide @@ -114,3 +116,92 @@ 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_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) + fission_yields = nuc.yield_data[1e6].pop() + + 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] From 6da0b3ec5758a74abddc30ce25512646caf9362f Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Thu, 8 Aug 2019 16:42:14 -0500 Subject: [PATCH 053/137] Add openmc.deplete.Chain.validate method Closes #1308 by providing a way to validate the contents of the depletion chain. This method iterates over all the nuclides and calls their validation method with the same input arguments, (strict, quiet, and tolerance). For the case where strict == False, quiet == False, and a nuclide fails the validation, the method returns early rather than continuing to iterate over all other nuclides. Type and value checking are also done on the tolerance argument. Two tests were added to test_deplete_chain.py The more interesting test works with the simple chain and various manipulations to check the robustness of the method. The other just checks the type and value checking on tolerance. --- openmc/deplete/chain.py | 53 +++++++++++++++++++++++++- openmc/deplete/nuclide.py | 4 ++ tests/unit_tests/test_deplete_chain.py | 52 +++++++++++++++++++++++++ 3 files changed, 108 insertions(+), 1 deletion(-) diff --git a/openmc/deplete/chain.py b/openmc/deplete/chain.py index 1f16d9cafa..d2ccf227c4 100644 --- a/openmc/deplete/chain.py +++ b/openmc/deplete/chain.py @@ -10,9 +10,10 @@ import math import re from collections import OrderedDict, defaultdict from collections.abc import Mapping +from numbers import Real from warnings import warn -from openmc.checkvalue import check_type, check_less_than +from openmc.checkvalue import check_type, check_less_than, check_greater_than from openmc.data import gnd_name, zam # Try to use lxml if it is available. It preserves the order of attributes and @@ -613,3 +614,53 @@ class Chain(object): new_ratios[ground_tgt] = ground_br parent.reactions.append(ReactionTuple( "(n,gamma)", ground_tgt, capt_Q, ground_br)) + + 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, do the sum of branching + ratios equal about 1? + 2) For fission reactions, do the sum of fission yield + fractions equal about 2? + + Parameters + ---------- + strict : bool, optional + Raise exceptions at the first inconsistency if true. + Otherwise mark a warning + quiet : bool, optional + Flag to supress 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 &= stat + return valid diff --git a/openmc/deplete/nuclide.py b/openmc/deplete/nuclide.py index 9c07a91a9a..63503cbfb4 100644 --- a/openmc/deplete/nuclide.py +++ b/openmc/deplete/nuclide.py @@ -260,6 +260,10 @@ class Nuclide(object): ValueError If ``strict`` evaluates to ``True`` and an inconistency was found + + See Also + -------- + openmc.deplete.Chain.validate """ branch_getter = attrgetter("branching_ratio") diff --git a/tests/unit_tests/test_deplete_chain.py b/tests/unit_tests/test_deplete_chain.py index 33b13b596d..294e56637c 100644 --- a/tests/unit_tests/test_deplete_chain.py +++ b/tests/unit_tests/test_deplete_chain.py @@ -329,3 +329,55 @@ def test_capture_branch_failures(simple_chain): br = {"C": {"A": 1.0, "B": 1.0}} with pytest.raises(ValueError, match="C ratios"): simple_chain.set_capture_branches(br) + + +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) From 58313c4ed8c6c6008f38ccdc8598507834c375aa Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Thu, 8 Aug 2019 16:43:32 -0500 Subject: [PATCH 054/137] Check for downloaded ENDF data in openmc-make-depletion-chain Mirror to openmc-make-depletion-chain-casl, but uses Path objects --- scripts/openmc-make-depletion-chain | 32 +++++++++++++++++++++-------- 1 file changed, 23 insertions(+), 9 deletions(-) diff --git a/scripts/openmc-make-depletion-chain b/scripts/openmc-make-depletion-chain index 7c08f984e9..8627e62ec6 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,29 @@ 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 zip( + (decay_files, neutron_files, nfy_files), + ("decay", "neutron", "nfy")): + if len(flist) == 0: + 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') From 3b348116d1a4030b34c08a3e1086897f5d59a757 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Thu, 8 Aug 2019 16:50:34 -0500 Subject: [PATCH 055/137] Document Te-129 addition in scripts/casl_chain.py --- scripts/casl_chain.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/scripts/casl_chain.py b/scripts/casl_chain.py index 2477a5e892..cecf88b1dd 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). +# Te-129 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 From 066885ddec08641df638aad322810e48a3844dca Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Thu, 8 Aug 2019 16:53:12 -0500 Subject: [PATCH 056/137] Document openmc-make-depletion-chain[-casl] scripts --- docs/source/usersguide/scripts.rst | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/docs/source/usersguide/scripts.rst b/docs/source/usersguide/scripts.rst index 76c7c2f222..d64632683e 100644 --- a/docs/source/usersguide/scripts.rst +++ b/docs/source/usersguide/scripts.rst @@ -106,6 +106,36 @@ 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 7.1 nuclear data. If the :envvar:`OPENMC_ENDF_DATA` variable +is not set, and ``"neutron"``, ``"decay"``, ``"nfy"`` directories +to not exist, then ENDF/B 7.1 293 K data will be downloaded. + +.. _scripts_depletion_chain_casl: + +------------------------------------ +``openmc-make-depletion-chain-casl`` +------------------------------------ + +This script generates a depletion chain called ``chain_casl.xml`` +using a ENDF/B 7.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 it's 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 7.1 293 K data will be downloaded. + .. _scripts_stopping: ------------------------------- From 726811394781a2b3b88440a3e4b36172aa596139 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Fri, 9 Aug 2019 17:43:34 -0500 Subject: [PATCH 057/137] Apply suggestions from code review Co-Authored-By: Paul Romano --- openmc/deplete/nuclide.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openmc/deplete/nuclide.py b/openmc/deplete/nuclide.py index 23f14331e2..7353cb9023 100644 --- a/openmc/deplete/nuclide.py +++ b/openmc/deplete/nuclide.py @@ -331,7 +331,7 @@ class FissionYieldDistribution(Mapping): yield_matrix = empty((len(energies), len(shared_prod))) - for g_index, energy in enumerate(sorted(energies)): + 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) From fe2a87e7597427f2ecc1c03e885d76223771053a Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Tue, 13 Aug 2019 08:32:18 -0500 Subject: [PATCH 058/137] Add a few papers in publications list --- docs/source/publications.rst | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) 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.*, From fc2c80628b93ecb4667f98740085692f30134168 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Tue, 13 Aug 2019 08:36:09 -0500 Subject: [PATCH 059/137] Remove list of developers, update copyright year in a few places --- docs/source/developers.rst | 28 ---------------------------- docs/source/index.rst | 1 - docs/source/license.rst | 2 +- man/man1/openmc.1 | 2 +- 4 files changed, 2 insertions(+), 31 deletions(-) delete mode 100644 docs/source/developers.rst 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/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 From fab859982f867dc64f73678d38832cb84d435bc9 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Mon, 12 Aug 2019 15:15:26 -0500 Subject: [PATCH 060/137] Document openmc.deplete.nuclide.FissionYield --- docs/source/pythonapi/deplete.rst | 1 + openmc/deplete/nuclide.py | 81 +++++++++++++++++++++---------- 2 files changed, 56 insertions(+), 26 deletions(-) diff --git a/docs/source/pythonapi/deplete.rst b/docs/source/pythonapi/deplete.rst index 243dcad430..d53db063e4 100644 --- a/docs/source/pythonapi/deplete.rst +++ b/docs/source/pythonapi/deplete.rst @@ -66,6 +66,7 @@ for a depletion chain: 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. diff --git a/openmc/deplete/nuclide.py b/openmc/deplete/nuclide.py index 7353cb9023..86a10d4294 100644 --- a/openmc/deplete/nuclide.py +++ b/openmc/deplete/nuclide.py @@ -231,13 +231,15 @@ class FissionYieldDistribution(Mapping): Parameters ---------- - ordered_energies : iterable of float - Energies for which fission yield data exist - orderded_products : iterable of str - Fission products produced by this parent at all energies - group_fission_yields : numpy.ndarray or iterable of iterable of float + energies : iterable of float + Energies for which fission yield data exist. Must be ordered + by increasing energy + products : iterable of str + Fission products produced by this parent at all energies. + Must be ordered alphabetically + yield_matrix : numpy.ndarray or iterable of iterable of float Array of shape ``(n_energy, n_products)`` where - ``group_fission_yields[g][j]`` is the yield of + ``yield_matrix[g][j]`` is the yield of ``ordered_products[j]`` due to a fission in energy region ``g``. Attributes @@ -255,20 +257,21 @@ class FissionYieldDistribution(Mapping): See Also -------- - :meth:`from_xml_element`, :meth:`from_dict` + * :meth:`from_xml_element`, :meth:`from_dict` - Construction methods + * :class:`FissionYield` - Class used for storing yields at a given energy """ - def __init__(self, ordered_energies, ordered_products, group_fission_yields): - check_type("energies", ordered_energies, Iterable, Real) - self.energies = tuple(ordered_energies) - check_type("products", ordered_products, Iterable, str) - self.products = tuple(ordered_products) - yield_matrix = asarray(group_fission_yields, dtype=float) + def __init__(self, energies, products, yield_matrix): + check_type("energies", energies, Iterable, Real) + self.energies = tuple(energies) + check_type("products", products, Iterable, str) + self.products = tuple(products) + yield_matrix = asarray(yield_matrix, dtype=float) if yield_matrix.shape != (len(self.energies), len(self.products)): raise ValueError( "Shape of yield matrix inconsistent. " "Should be ({}, {}), is {}".format( - len(ordered_energies), len(ordered_products), + len(energies), len(products), yield_matrix.shape)) self.yield_matrix = yield_matrix @@ -278,7 +281,7 @@ class FissionYieldDistribution(Mapping): def __getitem__(self, energy): if energy not in self.energies: raise KeyError(energy) - return _FissionYield( + return FissionYield( self.products, self.yield_matrix[self.energies.index(energy)]) def __iter__(self): @@ -321,13 +324,13 @@ class FissionYieldDistribution(Mapping): FissionYieldDistribution """ # mapping {energy: {product: value}} - energies = tuple(sorted(fission_yields)) + energies = sorted(fission_yields) # Get a consistent set of products to produce a matrix of yields shared_prod = set() for prod_set in map(set, fission_yields.values()): shared_prod |= prod_set - ordered_prod = tuple(sorted(shared_prod)) + ordered_prod = sorted(shared_prod) yield_matrix = empty((len(energies), len(shared_prod))) @@ -335,10 +338,8 @@ class FissionYieldDistribution(Mapping): prod_map = fission_yields[energy] for prod_ix, product in enumerate(ordered_prod): yield_val = prod_map.get(product) - if yield_val is None: - yield_matrix[g_index, prod_ix] = 0.0 - else: - yield_matrix[g_index, prod_ix] = yield_val + yield_matrix[g_index, prod_ix] = ( + 0.0 if yield_val is None else yield_val) return cls(energies, ordered_prod, yield_matrix) @@ -359,19 +360,47 @@ class FissionYieldDistribution(Mapping): data_elem.text = " ".join(map(str, yield_obj.yields)) -class _FissionYield(Mapping): +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 + on a single vector of yields. Can in turn be used like a + dictionary to fetch fission 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 - View into associated :attr:`FissionYieldDistribution.yield_matrix` + 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 """ def __init__(self, products, yields): @@ -395,8 +424,8 @@ class _FissionYield(Mapping): return self def __mul__(self, value): - return _FissionYield(self.products, self.yields * value) + return FissionYield(self.products, self.yields * value) def copy(self): """Return an identical yield object, with unique yields""" - return _FissionYield(self.products, self.yields.copy()) + return FissionYield(self.products, self.yields.copy()) From 6d4d25acb2a53df609df76eb0a79ebb5e77b4804 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Tue, 13 Aug 2019 15:16:41 -0500 Subject: [PATCH 061/137] Implement add, imul, rmul for openmc.deplete.FissionYield Addition methods __add__ and __iadd__ expect the other argument to a FissionYield object and will add the yields from one to the other [in place for __iadd__]. Multiplication methods __imul__, __mul__, and __rmul__ expect the other argument to be a scalar and will scale the fission yields by this value [in place for __imul__] Also added a __repr__ method that returns a dictionary-like representation --- openmc/deplete/nuclide.py | 27 +++++++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/openmc/deplete/nuclide.py b/openmc/deplete/nuclide.py index 86a10d4294..615761cde9 100644 --- a/openmc/deplete/nuclide.py +++ b/openmc/deplete/nuclide.py @@ -367,6 +367,8 @@ class FissionYield(Mapping): :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. @@ -401,6 +403,8 @@ class FissionYield(Mapping): 0.002 >>> (new + fy_vector)["Sm149"] 0.0009 + >>> dict(new) + {"Xe135": 0.002, "I129": 0.001, "Sm149": 0.0003} """ def __init__(self, products, yields): @@ -418,13 +422,32 @@ class FissionYield(Mapping): def __iter__(self): return iter(self.products) + def __add__(self, other): + new = self.copy() + new += other + return new + def __iadd__(self, other): """Increment value from other fission yield""" self.yields += other.yields return self - def __mul__(self, value): - return FissionYield(self.products, self.yields * value) + def __imul__(self, scalar): + self.yields *= scalar + return self + + def __mul__(self, scalar): + new = self.copy() + new *= scalar + return new + + def __rmul__(self, scalar): + new = self.copy() + new *= scalar + return new + + def __repr__(self): + return "<{}: {}>".format(self.__class__.__name__, repr(dict(self))) def copy(self): """Return an identical yield object, with unique yields""" From 2cfbb21858283e78bfbfeab2ac8fc8fb5b6ec91a Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Tue, 13 Aug 2019 15:23:19 -0500 Subject: [PATCH 062/137] Provide abstract FissionYieldHelper class API used by the Operator: - generate_tallies - weighted_yields [abstract] - update_nuclides_from_operator - unpack generate_tallies and unpack are empty methods, provided so that a consistent API can be found on helpers with tallies. Sorts nuclides into two dictionaries: those with a single set of fission yields, and those with multiple sets. The former can is exposed through the constant_yields property, returning a copy of the fission yield dictionary {parent: {product: yield}} The Operator now looks for/uses the weighted_yields and update_nuclides_from_operator methods instead of the old compute_yields and set_fissionable_nuclides methods --- docs/source/pythonapi/deplete.rst | 8 +-- openmc/deplete/abc.py | 102 ++++++++++++++++++++++++++++++ openmc/deplete/operator.py | 4 +- 3 files changed, 108 insertions(+), 6 deletions(-) diff --git a/docs/source/pythonapi/deplete.rst b/docs/source/pythonapi/deplete.rst index d53db063e4..260838439c 100644 --- a/docs/source/pythonapi/deplete.rst +++ b/docs/source/pythonapi/deplete.rst @@ -6,7 +6,7 @@ .. 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 `_. @@ -25,7 +25,7 @@ transport-depletion coupling algorithms `_. 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:: @@ -79,7 +79,6 @@ data, such as number densities and reaction rates for each material. AtomNumber ChainFissionHelper DirectReactionRateHelper - FissionYieldHelper OperatorResult ReactionRates Results @@ -94,8 +93,9 @@ The following classes are abstract classes that can be used to extend the :nosignatures: :template: myclass.rst - ReactionRateHelper EnergyHelper + FissionYieldHelper + ReactionRateHelper TransportOperator Custom integrators can be developed by subclassing from the following abstract diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py index 0f1203aeb8..356e9186df 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -364,6 +364,108 @@ class EnergyHelper(ABC): 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. Not necessary + that all have yield data. + + Attributes + ---------- + n_bmats : int + Number of burnable materials tracked in the problem + 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 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): + out = {} + for key, sub in self._constant_yields.items(): + out[key] = sub.copy() + return out + + @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 + Indexes for materials in ``materials`` tracked on this + process + """ + + def update_nuclides_from_operator(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 : tuple 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 tuple(sorted(self._chain_set & set(nuclides))) + + class Integrator(ABC): """Abstract class for solving the time-integration for depletion diff --git a/openmc/deplete/operator.py b/openmc/deplete/operator.py index d99c96f185..15f85aa4b2 100644 --- a/openmc/deplete/operator.py +++ b/openmc/deplete/operator.py @@ -210,7 +210,7 @@ class Operator(TransportOperator): nuclides = self._get_tally_nuclides() self._rate_helper.nuclides = nuclides self._energy_helper.nuclides = nuclides - self._fsn_yield_helper.set_fissionable_nuclides(nuclides) + self._fsn_yield_helper.update_nuclides_from_operator(nuclides) # Run OpenMC openmc.capi.reset() @@ -590,7 +590,7 @@ class Operator(TransportOperator): mat_index, nuc_ind, react_ind) # Compute fission yields for this material - fission_yields.append(self._fsn_yield_helper.compute_yields(i)) + fission_yields.append(self._fsn_yield_helper.weighted_yields(i)) # Accumulate energy from fission self._energy_helper.update(tally_rates[:, fission_ind], mat_index) From d8b9f5db54d28c6dc16330cd056e47eed90bedec Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Tue, 13 Aug 2019 15:39:13 -0500 Subject: [PATCH 063/137] Add ConstantFissionYieldHelper concrete class Given a requested energy, will take fission yields from that energy on all nuclides and hold them as constant throughout the simulation. If the requested energy is not found for a specific nuclide, then the closest energy is used. The default is to take the thermal 0.0253 eV yields. This is now the helper attached to the Operator. The user can select what energy of yields they would like to use. --- docs/source/pythonapi/deplete.rst | 1 + openmc/deplete/helpers.py | 173 +++++------------- openmc/deplete/operator.py | 12 +- .../unit_tests/test_deplete_fission_yields.py | 42 +++++ tests/unit_tests/test_deplete_helpers.py | 96 ---------- 5 files changed, 99 insertions(+), 225 deletions(-) create mode 100644 tests/unit_tests/test_deplete_fission_yields.py delete mode 100644 tests/unit_tests/test_deplete_helpers.py diff --git a/docs/source/pythonapi/deplete.rst b/docs/source/pythonapi/deplete.rst index 260838439c..8e8cf755b8 100644 --- a/docs/source/pythonapi/deplete.rst +++ b/docs/source/pythonapi/deplete.rst @@ -78,6 +78,7 @@ data, such as number densities and reaction rates for each material. AtomNumber ChainFissionHelper + ConstantFissionYieldHelper DirectReactionRateHelper OperatorResult ReactionRates diff --git a/openmc/deplete/helpers.py b/openmc/deplete/helpers.py index ac81d0ac7a..44ed9293d0 100644 --- a/openmc/deplete/helpers.py +++ b/openmc/deplete/helpers.py @@ -2,12 +2,19 @@ Class for normalizing fission energy deposition """ from itertools import product +from numbers import Real from numpy import dot, zeros, newaxis, asarray, empty_like, where +from openmc.checkvalue import check_type, check_greater_than from openmc.capi import ( Tally, MaterialFilter, EnergyFilter) -from .abc import ReactionRateHelper, EnergyHelper +from .abc import ( + ReactionRateHelper, EnergyHelper, FissionYieldHelper) + +__all__ = ( + "DirectReactionRateHelper", "ChainFissionHelper", + "ConstantFissionYieldHelper") # ------------------------------------- # Helpers for generating reaction rates @@ -150,152 +157,68 @@ class ChainFissionHelper(EnergyHelper): # ------------------------------------ -class FissionYieldHelper(object): - """Class for using energy-dependent fission yields in depletion chain - - Creates a tally across all burnable materials to score the fission - rate in nuclides with yield data. An energy filter is used to - compute this rates in a group structure corresponding to the - fission yield data. This tally data is used to compute the - relative number of fission events in each energy region, - which serve as the weights for each energy-dependent fission - yield distribution. +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. Not necessary that all have yield data. - n_bmats : int - Number of burnable materials tracked in the problem + 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 ---------- - energy_bounds : tuple of float - Sorted energy bounds from the tally filter - results : numpy.ndarray - Array of tally results for this process with shape - ``(n_local_mat, n_energy, n_nucs)`` + 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, n_bmats): - self._chain_nuclides = {} - self._chain_set = set() - self._tally_map = {} - # TODO Support user-requested minimum energy? - yield_energies = {0.0} - - # Get nuclides with fission yield data, names - # and all energy points - # Names are provided from operator tally nuclides - for nuc in chain_nuclides: - if len(nuc.yield_data) == 0: + 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 - self._chain_nuclides[nuc.name] = nuc - self._chain_set.add(nuc.name) - yield_energies.update(nuc.yield_energies) - - # Create energy grid - self._energy_bounds = tuple(sorted(yield_energies)) - self.n_bmats = n_bmats - - self._reaction_tally = None - self.results = None - self.local_indexes = None + # Specific energy not found, use closest energy + distances = [abs(energy - ene) for ene in nuc.yield_energies] + min_index = min( + range(len(nuc.yield_energies)), key=distances.__getitem__) + self._constant_yields[name] = ( + nuc.yield_data[nuc.yield_energies[min_index]]) @property - def energy_bounds(self): - return self._energy_bounds + def energy(self): + return self._energy - def generate_tallies(self, materials, mat_indexes): - """Construct the fission rate tally + def weighted_yields(self, _local_mat_index=None): + """Return fission yields for all nuclides requested Parameters ---------- - materials : iterable of C-API materials - Materials to be used in :class:`openmc.capi.MaterialFilter` - mat_indexes : iterable of int - Indexes for materials in ``materials`` tracked on this - process - """ - # Tally group-wise fission reaction rates - self._reaction_tally = Tally() - self._reaction_tally.scores = ['fission'] - - # Tally energy-weighted group-wise fission reaction rate - # Used to evaluated linear interpolation between fission yield points - self._weighted_reaction_tally = Tally() - self._weighted_reaction_tally.scores = ['fission'] - - filters = [ - MaterialFilter(materials), EnergyFilter(self._energy_bounds)] - - self._reaction_tally.filters = filters - self.local_indexes = asarray(mat_indexes) - - def set_fissionable_nuclides(self, nuclides): - """List of string of nuclides with data to be tallied - - Parameters - ---------- - nuclides : iterable of str - Nuclides with non-zero densities that are candidates - for the fission tally. Not necessary that all are nuclides - with fission yields, but at least one must be + _local_mat_index : int, optional + Current material index. Not used since all yields are + constant Returns ------- - nuclides : tuple of str - Nuclides ordered as they appear in the tally and in - the nuclide column of :attr:`results` - - Raises - ------ - ValueError - If no nuclides in ``nuclides`` are tracked on this - object + library : dict + Dictionary of ``{parent: {product: fyield}}`` """ - # Set of all nuclides with positive density - # and fission yield data - nuc_set = self._chain_set & set(nuclides) - if len(nuc_set) == 0: - raise ValueError( - "No overlap between chain nuclides with fission yields and " - "requested tally nuclides") - nuclides = tuple(sorted(nuc_set)) - self._tally_index = [self._chain_nuclides[n] for n in nuclides] - self._reaction_tally.nuclides = nuclides - return nuclides - - def unpack(self): - """Unpack fission rate tallies to produce :attr:`results` - """ - # if this process is not responsible for depleting anything - # [more processes than burnable materials] - # don't do anything - if self.local_indexes.size == 0: - return - - # get view into tally results - # new shape: [material, energy, parent nuclide] - result_view = self._reaction_tally.results[..., 1].reshape( - self.n_bmats, len(self._energy_bounds) - 1, - len(self._reaction_tally.nuclides)) - - # Get results specific to this process - fission_rates = result_view[self.local_indexes] - self.results = empty_like(fission_rates) - - # scale group fission rates proportional to total fission rate - fission_total = fission_rates.sum(axis=1) - nz_mat, nz_nuc = fission_total.nonzero() - self.results[nz_mat, :, nz_nuc] = ( - fission_rates[nz_mat, :, nz_nuc] - / fission_total[nz_mat, newaxis, nz_nuc]) - - # directly set values to zero where total fission rate is zero - z_mat, z_nuc = where(fission_total == 0.0) - self.results[z_mat, :, z_nuc] = 0.0 + return self.constant_yields def compute_yields(self, local_mat_index): """Compute single fission yields using :attr:`results` diff --git a/openmc/deplete/operator.py b/openmc/deplete/operator.py index 15f85aa4b2..660539af42 100644 --- a/openmc/deplete/operator.py +++ b/openmc/deplete/operator.py @@ -26,7 +26,7 @@ from .atom_number import AtomNumber from .reaction_rates import ReactionRates from .results_list import ResultsList from .helpers import ( - DirectReactionRateHelper, ChainFissionHelper, FissionYieldHelper) + DirectReactionRateHelper, ChainFissionHelper, ConstantFissionYieldHelper) def _distribute(items): @@ -85,6 +85,10 @@ 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_energy : float, optional + Energy [eV] to pull fission product yields from. Passed to the + :class:`openmc.deplete.ConstantFissionYieldHelper`. Default: + 0.0253 eV Attributes ---------- @@ -123,7 +127,7 @@ class Operator(TransportOperator): """ def __init__(self, geometry, settings, chain_file=None, prev_results=None, diff_burnable_mats=False, fission_q=None, - dilute_initial=1.0e3): + dilute_initial=1.0e3, fission_yield_energy=0.0253): super().__init__(chain_file, fission_q, dilute_initial, prev_results) self.round_number = False self.prev_res = None @@ -178,8 +182,8 @@ class Operator(TransportOperator): self._rate_helper = DirectReactionRateHelper( self.reaction_rates.n_nuc, self.reaction_rates.n_react) self._energy_helper = ChainFissionHelper() - self._fsn_yield_helper = FissionYieldHelper( - self.chain.nuclides, len(self.burnable_mats)) + self._fsn_yield_helper = ConstantFissionYieldHelper( + self.chain.nuclides, energy=fission_yield_energy) def __call__(self, vec, power): """Runs a simulation. 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..b9eaf9d2fb --- /dev/null +++ b/tests/unit_tests/test_deplete_fission_yields.py @@ -0,0 +1,42 @@ +"""Test the FissionYieldHelpers""" + +from collections import namedtuple + +import pytest + +from openmc.deplete.nuclide import Nuclide, FissionYieldDistribution +from openmc.deplete.helpers import ConstantFissionYieldHelper + + +@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.name = "U235" + u235.yield_energies = (0.0253, 5.0e5, 1.40e7) + u235.yield_data = FissionYieldDistribution.from_dict(u5yield_dict) + + u8yield_dict = {5.00e5: {"Xe135": 1.12e-3, "Gd155": 1.32e-12}} + u238 = Nuclide() + u238.name = "U238" + u238.yield_energies = (5.00e5, ) + u238.yield_data = FissionYieldDistribution.from_dict(u8yield_dict) + + xe135 = Nuclide() + xe135.name = "Xe135" + + NuclideBundle = namedtuple("NuclideBundle", "u235 u238 xe135") + return NuclideBundle(u235, u238, xe135) +@pytest.mark.parametrize("input_energy, u5_yield_energy", ( + (0.0253, 0.0253), (0.01, 0.0253), (4e5, 5e5))) +def test_constant_helper(nuclide_bundle, input_energy, u5_yield_energy): + helper = ConstantFissionYieldHelper( + nuclide_bundle, energy=input_energy) + assert helper.energy == input_energy + assert helper.constant_yields == { + "U235": nuclide_bundle.u235.yield_data[u5_yield_energy], + "U238": nuclide_bundle.u238.yield_data[5.00e5]} # only epithermal + assert helper.constant_yields == helper.weighted_yields(1) diff --git a/tests/unit_tests/test_deplete_helpers.py b/tests/unit_tests/test_deplete_helpers.py deleted file mode 100644 index 15ae1550ed..0000000000 --- a/tests/unit_tests/test_deplete_helpers.py +++ /dev/null @@ -1,96 +0,0 @@ -"""Test the Operator helpers""" - -from unittest.mock import Mock - -import pytest -import numpy -from numpy.testing import assert_array_equal - -from openmc.deplete.nuclide import Nuclide, FissionYieldDistribution -from openmc.deplete.helpers import FissionYieldHelper - - -class FissionYieldHelperProxy(FissionYieldHelper): - - def __init__(self, chain_nuclides, n_bmats): - super().__init__(chain_nuclides, n_bmats) - self._reaction_tally = Mock() - self.local_indexes = numpy.array([0]) - - def generate_tallies(self, *args, **kwargs): - # Avoid calls to the C-API - pass - - -def test_fission_yield_helper(): - """Test the collection of fission yield data using approximated tallies - """ - u5yield_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}} - u235 = Nuclide() - u235.name = "U235" - u235.yield_energies = (0.0253, 1.40e7) - u235.yield_data = FissionYieldDistribution.from_dict(u5yield_dict) - - u8yield_dict = {5.00e5: {"Xe135": 1.12e-3, "Gd155": 1.32e-12}} - u238 = Nuclide() - u238.name = "U238" - u238.yield_energies = (5.00e5, ) - u238.yield_data = FissionYieldDistribution.from_dict(u8yield_dict) - - xe135 = Nuclide() - xe135.name = "Xe135" - - n_bmats = 2 - helper = FissionYieldHelperProxy([u235, u238, xe135], n_bmats) - - assert helper.energy_bounds == (0, 0.0253, 5.00e5, 1.40e7) - - # test that tally must be created with nuclides with yields - with pytest.raises(ValueError, match="No overlap"): - helper.set_fissionable_nuclides(["Xe135", ]) - - act_nucs = helper.set_fissionable_nuclides(["U235", "U238", "Xe135"]) - assert act_nucs == ("U235", "U238") - - # Emulate getting tally data from transport run - # Test as if this Helper is responsible for one of two materials - # Tally results ordered [n_mat * n_ene, n_fiss_nuc, 3] - - tally_res = numpy.zeros((3 * n_bmats, 2, 3)) - u5_fiss_rates = numpy.array([1.0, 1.5, 2.0]) - u8_fiss_rates = numpy.array([0.0, 1.0, 1.0]) - tally_res[:3, 0, 1] = u5_fiss_rates - tally_res[:3, 1, 1] = u8_fiss_rates - - helper._reaction_tally.results = tally_res - helper.unpack() # compute yield fractions - - # Compare fraction fission rate from helper.reset - exp_results = numpy.empty((1, 3, 2)) - # Fraction of fission events in each energy range - u5_vec = u5_fiss_rates / u5_fiss_rates.sum() - u8_vec = u8_fiss_rates / u8_fiss_rates.sum() - exp_results[..., 0] = u5_vec - exp_results[..., 1] = u8_vec - assert_array_equal(helper.results, exp_results) - - # Compute and compare the library of fission yields - exp_lib = { - "U235": { - "Xe135": (u5yield_dict[0.0253]["Xe135"] * u5_vec[0] - + u5yield_dict[1.40e7]["Xe135"] * u5_vec[2]), - "Gd155": (u5yield_dict[0.0253]["Gd155"] * u5_vec[0] - + u5yield_dict[1.40e7]["Gd155"] * u5_vec[2]), - "Sm149": u5yield_dict[0.0253]["Sm149"] * u5_vec[0], - }, - "U238": { - "Xe135": u8yield_dict[5.00e5]["Xe135"] * u8_vec[1], - "Gd155": u8yield_dict[5.00e5]["Gd155"] * u8_vec[1], - } - } - - act_library = helper.compute_yields(0) - for parent, sub_yields in exp_lib.items(): - assert act_library[parent] == pytest.approx(sub_yields) From ca435b0bda717d1e4764a2907c42369019034d3b Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Tue, 13 Aug 2019 16:10:55 -0500 Subject: [PATCH 064/137] Add TalliedFissionYieldHelper abstract helper Designed to be subclassed by helpers that aim to compute effective fission yields with tallied data. Constructs a tally in all burnable materials, scoring the fission rate in all nuclides that have non-zero density (as reported by the Operator) and have multiple sets of yields. For nuclides with non-zero densities and a single set of yields, those yields are assumed to be constant across incident neutron energies. The unpack method is now abstract, as each subclass should have its own data layout, some with different energy structures perhaps?? Maybe some weighting functions? --- docs/source/pythonapi/deplete.rst | 1 + openmc/deplete/abc.py | 92 ++++++++++++++++++++++++++++++- 2 files changed, 92 insertions(+), 1 deletion(-) diff --git a/docs/source/pythonapi/deplete.rst b/docs/source/pythonapi/deplete.rst index 8e8cf755b8..7efc8c6393 100644 --- a/docs/source/pythonapi/deplete.rst +++ b/docs/source/pythonapi/deplete.rst @@ -97,6 +97,7 @@ The following classes are abstract classes that can be used to extend the EnergyHelper FissionYieldHelper ReactionRateHelper + TalliedFissionYieldHelper TransportOperator Custom integrators can be developed by subclassing from the following abstract diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py index 356e9186df..3e1dbde599 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -13,10 +13,11 @@ from copy import deepcopy from warnings import warn 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 @@ -466,6 +467,95 @@ class FissionYieldHelper(ABC): return tuple(sorted(self._chain_set & set(nuclides))) +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_nuclides_from_operator`. 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. + n_bmats : int + Number of burnable materials tracked in the problem. + + Attributes + ---------- + n_bmats : int + Number of burnable materials tracked in the problem + 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}}`` + """ + + _upper_energy = 20.0e6 # upper energy for tallies + + def __init__(self, chain_nuclides, n_bmats): + super().__init__(chain_nuclides) + self.n_bmats = n_bmats + self._local_indexes = None + self._fission_rate_tally = None + self._tally_index = {} + + 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 + Indexes for materials in ``materials`` tracked on this + process + """ + 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_nuclides_from_operator(self, nuclides): + """Tally nuclides with non-zero density and multiple yields + + Parameters + ---------- + nuclides : iterable of str + Nuclides with non-zero densities from the + :class:`openmc.deplete.Operator` + + Returns + ------- + nuclides : tuple of str + Union of nuclides that the :class:`openmc.deplete.Operator` + says have non-zero densities at this stage and those that + have multiple sets of yield data. Sorted by nuclide name + """ + overlap = set(self._chain_nuclides).intersection(set(nuclides)) + if len(overlap) == 0: + # tally no nuclides, but keep the Tally alive + self._fission_rate_tally.nuclides = None + self._tally_index = [] + return tuple() + nuclides = tuple(sorted(overlap)) + self._tally_index = [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 From f82dc83473cd27c80820077beb5b013444187aaf Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Tue, 13 Aug 2019 16:38:24 -0500 Subject: [PATCH 065/137] Add FissionYieldCutoffHelper for weighting yields by a cutoff energy This helper performs the following tasks to weight fission yields: 1) Determine a set of "fast" and "thermal" yields based on a user specified cutoff energy, 2) Tally fission rates above and below the cutoff, and 3) Compute the effective yield by weighting the fast and thermal yields by the fraction of fast and thermal fissions for all nuclides in all burnable materials. The user is allowed to specify the cutoff energy and energies preferred for the fast and thermal yields. By default, the cutoff is 112 eV, chosen as it is the logarithmic mean of 0.0253 eV and 500 keV. These two values are the default energies for thermal and fast yields. Tests are added to check for failure in constructing this helper (thermal energy > cutoff, non-real values passed, etc.), selection of yields given a range of user data, and a proxy class that emulates a variety of thermal and fast splits to check the eventual computed yield libraries. --- docs/source/pythonapi/deplete.rst | 1 + openmc/deplete/helpers.py | 189 +++++++++++++++--- .../unit_tests/test_deplete_fission_yields.py | 143 +++++++++++++ 3 files changed, 306 insertions(+), 27 deletions(-) diff --git a/docs/source/pythonapi/deplete.rst b/docs/source/pythonapi/deplete.rst index 7efc8c6393..44f8b70e05 100644 --- a/docs/source/pythonapi/deplete.rst +++ b/docs/source/pythonapi/deplete.rst @@ -80,6 +80,7 @@ data, such as number densities and reaction rates for each material. ChainFissionHelper ConstantFissionYieldHelper DirectReactionRateHelper + FissionYieldCutoffHelper OperatorResult ReactionRates Results diff --git a/openmc/deplete/helpers.py b/openmc/deplete/helpers.py index 44ed9293d0..37d7359e83 100644 --- a/openmc/deplete/helpers.py +++ b/openmc/deplete/helpers.py @@ -3,18 +3,20 @@ Class for normalizing fission energy deposition """ from itertools import product from numbers import Real +import operator -from numpy import dot, zeros, newaxis, asarray, empty_like, where +from numpy import dot, zeros, newaxis from openmc.checkvalue import check_type, check_greater_than from openmc.capi import ( Tally, MaterialFilter, EnergyFilter) from .abc import ( - ReactionRateHelper, EnergyHelper, FissionYieldHelper) + ReactionRateHelper, EnergyHelper, FissionYieldHelper, + TalliedFissionYieldHelper) __all__ = ( "DirectReactionRateHelper", "ChainFissionHelper", - "ConstantFissionYieldHelper") + "ConstantFissionYieldHelper", "FissionYieldCutoffHelper") # ------------------------------------- # Helpers for generating reaction rates @@ -220,41 +222,174 @@ class ConstantFissionYieldHelper(FissionYieldHelper): """ return self.constant_yields - def compute_yields(self, local_mat_index): - """Compute single fission yields using :attr:`results` - Produces a new library in :attr:`libraries` +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. Not necessary + that all have yield data. + n_bmats : int + 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 + 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) + super().__init__(chain_nuclides, n_bmats) + self._cutoff = cutoff + self._thermal_yields = {} + self._fast_yields = {} + for name, nuc in self._chain_nuclides.items(): + yields = nuc.yield_data + energies = nuc.yield_energies + thermal = yields.get(thermal_energy) + if thermal is None: + # find first index >= cutoff + ix = self._find_fallback_energy( + name, energies, cutoff, True) + thermal = yields[energies[ix - 1]] + fast = yields.get(fast_energy) + if fast is None: + # find first index <= cutoff + rev_ix = self._find_fallback_energy( + name, list(reversed(energies)), cutoff, False) + fast = yields[energies[-rev_ix]] + self._thermal_yields[name] = thermal + self._fast_yields[name] = fast + + @staticmethod + def _find_fallback_energy(name, energies, cutoff, check_under): + cutoff_func = operator.ge if check_under else operator.le + found = False + for ix, ene in enumerate(energies): + if cutoff_func(ene, cutoff): + found = True + break + if found and ix != 0: + return ix + domain = "thermal" if check_under else "fast" + raise ValueError("Could not find {} yields for {} " + "with cutoff {} eV".format(domain, name, cutoff)) + + 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 + Indexes for materials in ``materials`` tracked on this + process + """ + super().generate_tallies(materials, mat_indexes) + energy_filter = EnergyFilter() + energy_filter.bins = (0.0, self._cutoff, self._upper_energy) + self._fission_rate_tally.filters.append(energy_filter) + + def unpack(self): + """Obtain fast and thermal fission fractions from tally""" + fission_rates = self._fission_rate_tally.results[..., 1].reshape( + self.n_bmats, 2, len(self._tally_index)) + 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``s 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``'s 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 material tracked on this process that - exists in :attr:`local_mat_index` and fits within - the first axis in :attr:`results` + Index for specific burnable material. Effective + yields will be produced using + ``self.results[local_mat_index]`` Returns ------- library : dict Dictionary of ``{parent: {product: fyield}}`` """ - tally_results = self.results[local_mat_index] + rates = self.results[local_mat_index] + yields = self.constant_yields + # iterate over thermal then fast yields, prefer __mul__ to __rmul__ + for therm_frac, nuc in zip(rates[0], self._tally_index): + yields[nuc.name] = self._thermal_yields[nuc.name] * therm_frac - # Dictionary {parent_nuclide : [product, yield_vector]} - initial_library = {} - for i_energy, energy in enumerate(self._energy_bounds[1:]): - for i_nuc, fiss_frac in enumerate(tally_results[i_energy]): - parent = self._tally_index[i_nuc] - yield_data = parent.yield_data.get(energy) - if yield_data is None: - continue - if parent not in initial_library: - initial_library[parent] = yield_data * fiss_frac - continue - initial_library[parent] += yield_data * fiss_frac + for fast_frac, nuc in zip(rates[1], self._tally_index): + yields[nuc.name] += self._fast_yields[nuc.name] * fast_frac + return yields - # convert to dictionary that can be passed to Chain.form_matrix - library = {} - for k, yield_obj in initial_library.items(): - library[k.name] = dict(zip(yield_obj.products, yield_obj.yields)) + @property + def thermal_yields(self): + out = {} + for key, sub in self._thermal_yields.items(): + out[key] = sub.copy() + return out - return library + @property + def fast_yields(self): + out = {} + for key, sub in self._fast_yields.items(): + out[key] = sub.copy() + return out diff --git a/tests/unit_tests/test_deplete_fission_yields.py b/tests/unit_tests/test_deplete_fission_yields.py index b9eaf9d2fb..f3911d80d0 100644 --- a/tests/unit_tests/test_deplete_fission_yields.py +++ b/tests/unit_tests/test_deplete_fission_yields.py @@ -1,5 +1,148 @@ """Test the FissionYieldHelpers""" +from collections import namedtuple +from unittest.mock import Mock + +import pytest +import numpy +from numpy.testing import assert_array_equal + +from openmc.capi import Material +from openmc.deplete.nuclide import Nuclide, FissionYieldDistribution +from openmc.deplete.helpers import ( + FissionYieldCutoffHelper, ConstantFissionYieldHelper) + + +@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.name = "U235" + u235.yield_energies = (0.0253, 5.0e5, 1.40e7) + u235.yield_data = FissionYieldDistribution.from_dict(u5yield_dict) + + u8yield_dict = {5.00e5: {"Xe135": 1.12e-3, "Gd155": 1.32e-12}} + u238 = Nuclide() + u238.name = "U238" + u238.yield_energies = (5.00e5, ) + u238.yield_data = FissionYieldDistribution.from_dict(u8yield_dict) + + xe135 = Nuclide() + xe135.name = "Xe135" + + NuclideBundle = namedtuple("NuclideBundle", "u235 u238 xe135") + return NuclideBundle(u235, u238, xe135) +@pytest.mark.parametrize("input_energy, u5_yield_energy", ( + (0.0253, 0.0253), (0.01, 0.0253), (4e5, 5e5))) +def test_constant_helper(nuclide_bundle, input_energy, u5_yield_energy): + helper = ConstantFissionYieldHelper( + nuclide_bundle, energy=input_energy) + assert helper.energy == input_energy + assert helper.constant_yields == { + "U235": nuclide_bundle.u235.yield_data[u5_yield_energy], + "U238": nuclide_bundle.u238.yield_data[5.00e5]} # only epithermal + assert helper.constant_yields == helper.weighted_yields(1) + + +# --------------------------------- +# Test the FissionYieldCutoffHelper +# --------------------------------- + + +def test_cutoff_construction(nuclide_bundle): + # defaults + helper = FissionYieldCutoffHelper( + nuclide_bundle, 1) + assert helper.constant_yields == { + "U238": nuclide_bundle.u238.yield_data[5.0e5]} + assert helper.thermal_yields == { + "U235": nuclide_bundle.u235.yield_data[0.0253]} + assert helper.fast_yields == { + "U235": nuclide_bundle.u235.yield_data[5e5]} + # use 14 MeV yields + helper = FissionYieldCutoffHelper( + nuclide_bundle, 1, fast_energy=14e6) + assert helper.constant_yields == { + "U238": nuclide_bundle.u238.yield_data[5.0e5]} + assert helper.thermal_yields == { + "U235": nuclide_bundle.u235.yield_data[0.0253]} + assert helper.fast_yields == { + "U235": nuclide_bundle.u235.yield_data[14e6]} + # specify missing thermal yields -> use 0.0253 + helper = FissionYieldCutoffHelper( + nuclide_bundle, 1, thermal_energy=1) + assert helper.thermal_yields == { + "U235": nuclide_bundle.u235.yield_data[0.0253]} + assert helper.fast_yields == { + "U235": nuclide_bundle.u235.yield_data[5e5]} + # request missing fast yields -> use epithermal + helper = FissionYieldCutoffHelper( + nuclide_bundle, 1, fast_energy=1e4) + assert helper.thermal_yields == { + "U235": nuclide_bundle.u235.yield_data[0.0253]} + assert helper.fast_yields == { + "U235": nuclide_bundle.u235.yield_data[5e5]} + # test failures in cutoff: super low, super high + with pytest.raises(ValueError, match="thermal yields"): + FissionYieldCutoffHelper( + nuclide_bundle, 1, thermal_energy=0.001, cutoff=0.002) + with pytest.raises(ValueError, match="fast yields"): + FissionYieldCutoffHelper( + nuclide_bundle, 1, cutoff=15e6, fast_energy=17e6) + + +@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}) + + +class CutoffProxy(FissionYieldCutoffHelper): + """Proxy that supplies a set of tallies""" + + def generate_tallies(self, materials, mat_indexes): + self._fission_rate_tally = Mock() + self._local_indexes = numpy.asarray(mat_indexes) + + +# emulate some split between fast and thermal U235 fissions +@pytest.mark.parametrize("therm_frac", (0.5, 0.2, 0.8)) +def test_cutoff_helper(nuclide_bundle, therm_frac): + materials = ["1", "2"] # TODO Use real C API materials? + n_bmats = len(materials) + local_mats = [0] + proxy = CutoffProxy(nuclide_bundle, n_bmats) + proxy.generate_tallies(materials, local_mats) + non_zero_nucs = [n.name for n in nuclide_bundle] + tally_nucs = proxy.update_nuclides_from_operator(non_zero_nucs) + assert tally_nucs == ("U235", ) + # Emulate building tallies + # material x energy, tallied_nuclides, 3 + proxy_flux = 1e6 + tally_data = numpy.empty((n_bmats * 2, 1, 3)) + tally_data[0, 0, 1] = therm_frac * proxy_flux + tally_data[1, 0, 1] = (1 - therm_frac) * proxy_flux + proxy._fission_rate_tally.results = tally_data + + proxy.unpack() + # expected results of shape (n_mats, 2, n_tnucs) + expected_results = numpy.empty((1, 2, 1)) + expected_results[:, 0] = therm_frac + expected_results[:, 1] = (1 - therm_frac) + assert proxy.results == pytest.approx(expected_results) + + actual_yields = proxy.weighted_yields(0) + assert actual_yields["U238"] == nuclide_bundle.u238.yield_data[5e5] + assert actual_yields["U235"] == ( + proxy.thermal_yields["U235"] * therm_frac + + proxy.fast_yields["U235"] * (1 - therm_frac)) +"""Test the FissionYieldHelpers""" + from collections import namedtuple import pytest From 6b87ada75274e3d9cca3b49715f474981744c3e9 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Tue, 13 Aug 2019 16:59:25 -0500 Subject: [PATCH 066/137] Apply suggestions from code review Co-Authored-By: Paul Romano --- docs/source/usersguide/scripts.rst | 10 +++++----- openmc/deplete/chain.py | 10 +++++----- openmc/deplete/nuclide.py | 20 ++++++++++---------- scripts/casl_chain.py | 2 +- scripts/openmc-make-depletion-chain | 2 +- 5 files changed, 22 insertions(+), 22 deletions(-) diff --git a/docs/source/usersguide/scripts.rst b/docs/source/usersguide/scripts.rst index d64632683e..c97e7f1bd5 100644 --- a/docs/source/usersguide/scripts.rst +++ b/docs/source/usersguide/scripts.rst @@ -114,9 +114,9 @@ so it should not be necessary in practice to generate it yourself. ------------------------------- This script generates a depletion chain file called ``chain_endfb71.xml`` -using ENDF/B 7.1 nuclear data. If the :envvar:`OPENMC_ENDF_DATA` variable +using ENDF/B-VII.1 nuclear data. If the :envvar:`OPENMC_ENDF_DATA` variable is not set, and ``"neutron"``, ``"decay"``, ``"nfy"`` directories -to not exist, then ENDF/B 7.1 293 K data will be downloaded. +do not exist, then ENDF/B-VII.1 data will be downloaded. .. _scripts_depletion_chain_casl: @@ -125,16 +125,16 @@ to not exist, then ENDF/B 7.1 293 K data will be downloaded. ------------------------------------ This script generates a depletion chain called ``chain_casl.xml`` -using a ENDF/B 7.1 nuclear data for a simplified chain. +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 it's link to +``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 7.1 293 K data will be downloaded. +to not exist, then ENDF/B-VII.1 data will be downloaded. .. _scripts_stopping: diff --git a/openmc/deplete/chain.py b/openmc/deplete/chain.py index d2ccf227c4..bfbfcc2bef 100644 --- a/openmc/deplete/chain.py +++ b/openmc/deplete/chain.py @@ -620,9 +620,9 @@ class Chain(object): The following checks are performed for all nuclides present: - 1) For all non-fission reactions, do the sum of branching - ratios equal about 1? - 2) For fission reactions, do the sum of fission yield + 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 2? Parameters @@ -631,7 +631,7 @@ class Chain(object): Raise exceptions at the first inconsistency if true. Otherwise mark a warning quiet : bool, optional - Flag to supress warnings and return immediately at + Flag to suppress warnings and return immediately at the first inconsistency. Used only if ``strict`` does not evaluate to ``True``. tolerance : float, optional @@ -662,5 +662,5 @@ class Chain(object): stat = self[name].validate(strict, quiet, tolerance) if quiet and not stat: return stat - valid &= stat + valid = valid and stat return valid diff --git a/openmc/deplete/nuclide.py b/openmc/deplete/nuclide.py index 63503cbfb4..95af6d03de 100644 --- a/openmc/deplete/nuclide.py +++ b/openmc/deplete/nuclide.py @@ -231,9 +231,9 @@ class Nuclide(object): The following checks are performed: 1) for all non-fission reactions and decay modes, - do the sum of branching ratios equal about 1? - 2) for fission reactions, do the sum of fission yield - fractions equal about 2? + does the sum of branching ratios equal about one? + 2) for fission reactions, does the sum of fission yield + fractions equal about two? Parameters ---------- @@ -241,7 +241,7 @@ class Nuclide(object): Raise exceptions at the first inconsistency if true. Otherwise mark a warning quiet : bool, optional - Flag to supress warnings and return immediately at + Flag to suppress warnings and return immediately at the first inconsistency. Used only if ``strict`` does not evaluate to ``True``. tolerance : float, optional @@ -273,8 +273,8 @@ class Nuclide(object): valid = True # check decay modes - if len(self.decay_modes) > 0: - sum_br = sum(map(branch_getter, self.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( @@ -287,12 +287,12 @@ class Nuclide(object): warn(msg) valid = False - if len(self.reactions) > 0: + if self.reactions: type_map.clear() for reaction in self.reactions: type_map[reaction.type].add(reaction) for rxn_type, reactions in type_map.items(): - sum_rxn = sum(map(branch_getter, reactions)) + sum_rxn = sum(rx.branching_ratio for rx in reactions) stat = 1.0 - tolerance <= sum_rxn <= 1.0 + tolerance if stat: continue @@ -306,10 +306,10 @@ class Nuclide(object): warn(msg) valid = False - if len(self.yield_data) > 0: + if self.yield_data > 0: yield_getter = itemgetter(1) for energy, yield_list in self.yield_data.items(): - sum_yield = sum(map(yield_getter, yield_list)) + sum_yield = sum(y[1] for y in yield_list) stat = 2.0 - tolerance <= sum_yield <= 2.0 + tolerance if stat: continue diff --git a/scripts/casl_chain.py b/scripts/casl_chain.py index cecf88b1dd..61f5da9bc3 100755 --- a/scripts/casl_chain.py +++ b/scripts/casl_chain.py @@ -6,7 +6,7 @@ # Note 32 of the 255 nuclides appeare twice as they are both activation # nuclides (category 1) and fission product nuclides (category 3). -# Te-129 has been added due to it's link to I129 production. +# Te129 has been added due to it's link to I129 production. CASL_CHAIN = { # Nuclide: (Stable, CAT, IFPY, Special yield treatment) diff --git a/scripts/openmc-make-depletion-chain b/scripts/openmc-make-depletion-chain index 8627e62ec6..5ad0bc66a1 100755 --- a/scripts/openmc-make-depletion-chain +++ b/scripts/openmc-make-depletion-chain @@ -36,7 +36,7 @@ def main(): for flist, ftype in zip( (decay_files, neutron_files, nfy_files), ("decay", "neutron", "nfy")): - if len(flist) == 0: + 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) From ebf2f25ab1d7e55e5cef3089380af4ef0a7f7b2a Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Tue, 13 Aug 2019 16:57:27 -0500 Subject: [PATCH 067/137] Make number of burnable materials optional for TalliedFissionYieldHelper Must be supplied prior to generating tallies. This makes the Operator's job a little easier when picking the fission yield mode. --- openmc/deplete/abc.py | 22 +++++++++++++++++++--- openmc/deplete/helpers.py | 5 +++-- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py index 3e1dbde599..7f6de6d62e 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -480,13 +480,14 @@ class TalliedFissionYieldHelper(FissionYieldHelper): chain_nuclides : iterable of openmc.deplete.Nuclide Nuclides tracked in the depletion chain. Not necessary that all have yield data. - n_bmats : int + n_bmats : int, optional Number of burnable materials tracked in the problem. Attributes ---------- n_bmats : int - Number of burnable materials tracked in the problem + Number of burnable materials tracked in the problem. + Must be set prior to generating tallies 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}}`` @@ -494,13 +495,26 @@ class TalliedFissionYieldHelper(FissionYieldHelper): _upper_energy = 20.0e6 # upper energy for tallies - def __init__(self, chain_nuclides, n_bmats): + def __init__(self, chain_nuclides, n_bmats=None): super().__init__(chain_nuclides) self.n_bmats = n_bmats self._local_indexes = None self._fission_rate_tally = None self._tally_index = {} + @property + def n_bmats(self): + return self._n_bmats + + @n_bmats.setter + def n_bmats(self, value): + if value is None: + self._n_bmats = None + return + check_type("n_bmats", value, Integral) + check_greater_than("n_bmats", value, 0) + sef._n_bmats = value + def generate_tallies(self, materials, mat_indexes): """Construct the fission rate tally @@ -512,6 +526,8 @@ class TalliedFissionYieldHelper(FissionYieldHelper): Indexes for materials in ``materials`` tracked on this process """ + if self._n_bmat is None: + raise AttributeError("Number of burnable materials is not set") self._local_indexes = asarray(mat_indexes) # Tally group-wise fission reaction rates diff --git a/openmc/deplete/helpers.py b/openmc/deplete/helpers.py index 37d7359e83..2dae7c0362 100644 --- a/openmc/deplete/helpers.py +++ b/openmc/deplete/helpers.py @@ -238,7 +238,7 @@ class FissionYieldCutoffHelper(TalliedFissionYieldHelper): chain_nuclides : iterable of openmc.deplete.Nuclide Nuclides tracked in the depletion chain. Not necessary that all have yield data. - n_bmats : int + 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 @@ -254,7 +254,8 @@ class FissionYieldCutoffHelper(TalliedFissionYieldHelper): Attributes ---------- n_bmats : int - Number of burnable materials tracked in the problem + 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 From 1d7f79e341c96a8c563abe38702b3a7e66c3c7ac Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Tue, 13 Aug 2019 17:12:31 -0500 Subject: [PATCH 068/137] Determine fission yield helper based on Operator arguments A single argument, fission_yield_mode, is used to determine what type of FissionYield to use on the Operator. The options are * "constant": ConstantFissionYieldHelper [default] * "cutoff": FissionYieldCutoffHelper An additional argument, fission_yield_opts, can be supplied as a dictionary of keyword arguments to pass to the new helper. This allows the user to have control over how the private helper is created. --- openmc/deplete/helpers.py | 4 ++-- openmc/deplete/operator.py | 48 +++++++++++++++++++++++++++++++------- 2 files changed, 42 insertions(+), 10 deletions(-) diff --git a/openmc/deplete/helpers.py b/openmc/deplete/helpers.py index 2dae7c0362..d6051c6dd3 100644 --- a/openmc/deplete/helpers.py +++ b/openmc/deplete/helpers.py @@ -350,12 +350,12 @@ class FissionYieldCutoffHelper(TalliedFissionYieldHelper): 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``s fission events + 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``'s fissions are considered thermal, then + 70% of ``A`` fissions are considered thermal, then the effective fission product yield distributions for ``A`` is ``0.7 * T + 0.3 * F`` diff --git a/openmc/deplete/operator.py b/openmc/deplete/operator.py index 660539af42..11fded8e44 100644 --- a/openmc/deplete/operator.py +++ b/openmc/deplete/operator.py @@ -26,7 +26,8 @@ from .atom_number import AtomNumber from .reaction_rates import ReactionRates from .results_list import ResultsList from .helpers import ( - DirectReactionRateHelper, ChainFissionHelper, ConstantFissionYieldHelper) + DirectReactionRateHelper, ChainFissionHelper, ConstantFissionYieldHelper, + FissionYieldCutoffHelper) def _distribute(items): @@ -85,10 +86,23 @@ 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_energy : float, optional - Energy [eV] to pull fission product yields from. Passed to the - :class:`openmc.deplete.ConstantFissionYieldHelper`. Default: - 0.0253 eV + fission_yield_mode : str, {"constant", "cutoff"} + Key indicating what fission product yield scheme to use. The + key determines what fission energy helper is used:: + + * "constant": :class:`openmc.deplete.ConstantFissionYieldHelper` + * "cutoff": :class:`openmc.deplete.FissionYieldCutoffHelper` + + The documentation on these classes describe their methodology + and differences. ``"constant"`` will treat fission yields as + constant with respect to energy, while ``"cutoff"`` will + compute effective yields based on the number of fission events + above and below a cutoff. 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 ---------- @@ -125,9 +139,19 @@ class Operator(TransportOperator): diff_burnable_mats : bool Whether to differentiate burnable materials with multiple instances """ + _fission_helpers_ = { + "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, fission_yield_energy=0.0253): + 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 @@ -182,8 +206,16 @@ class Operator(TransportOperator): self._rate_helper = DirectReactionRateHelper( self.reaction_rates.n_nuc, self.reaction_rates.n_react) self._energy_helper = ChainFissionHelper() - self._fsn_yield_helper = ConstantFissionYieldHelper( - self.chain.nuclides, energy=fission_yield_energy) + + # Select and create fission yield helper + fission_helper = self._fission_helpers_[fission_yield_mode] + if fission_yield_opts is None: + self._fsn_yield_helper = fission_helper(self.chain.nuclides) + else: + self._fsn_yield_helper = fission_yield_mode( + self.chain.nuclides, **fission_yield_opts) + if hasattr(self._fsn_yield_helper, "n_bmats"): + self._fsn_yield_helper.n_bmats = len(self.burnable_mats) def __call__(self, vec, power): """Runs a simulation. From 46ce147a81c963f23150c9bf4b1d77baf520eb67 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Tue, 13 Aug 2019 17:47:34 -0500 Subject: [PATCH 069/137] Revert ebf2f25ab and add from_operator class method TalliedFissionYieldHelper instances must be passed the number of burnable materials at construction again. To make things easier for the Operator, a from_operator class method has been added to the FissionYieldHelper and TalliedFissionYieldHelper abstract classes, as well as more explicit versions on the ConstantFissionYieldHelper and FissionYieldCutoffHelper. All have the call signature cls.from_operator(operator, **kwargs) The concrete classes have explicitely declared what the allowed key word arguments are, while the abstract classes forward kwargs directly onto __init__. All explicit keyword arguments should have a counterpart in the __init__ method. --- openmc/deplete/abc.py | 51 ++++++++++++++++++++++++-------------- openmc/deplete/helpers.py | 48 +++++++++++++++++++++++++++++++++++ openmc/deplete/operator.py | 11 +++----- 3 files changed, 85 insertions(+), 25 deletions(-) diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py index 7f6de6d62e..5efda87029 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -466,6 +466,20 @@ class FissionYieldHelper(ABC): """ return tuple(sorted(self._chain_set & set(nuclides))) + @classmethod + def from_operator(cls, operator, **kwargs): + """Create a new instance by pulling data from the operator + + Parameters + ---------- + operator : openmc.deplete.TransportOperator + Operator with a depletion chain + kwargs: optional + Optional keyword arguments to be passed to the underlying + ``__init__`` method + """ + return cls(operator.chain.nuclides, **kwargs) + class TalliedFissionYieldHelper(FissionYieldHelper): """Abstract class for computing fission yields with tallies @@ -480,14 +494,13 @@ class TalliedFissionYieldHelper(FissionYieldHelper): chain_nuclides : iterable of openmc.deplete.Nuclide Nuclides tracked in the depletion chain. Not necessary that all have yield data. - n_bmats : int, optional + n_bmats : int Number of burnable materials tracked in the problem. Attributes ---------- n_bmats : int Number of burnable materials tracked in the problem. - Must be set prior to generating tallies 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}}`` @@ -495,26 +508,13 @@ class TalliedFissionYieldHelper(FissionYieldHelper): _upper_energy = 20.0e6 # upper energy for tallies - def __init__(self, chain_nuclides, n_bmats=None): + def __init__(self, chain_nuclides, n_bmats): super().__init__(chain_nuclides) self.n_bmats = n_bmats self._local_indexes = None self._fission_rate_tally = None self._tally_index = {} - @property - def n_bmats(self): - return self._n_bmats - - @n_bmats.setter - def n_bmats(self, value): - if value is None: - self._n_bmats = None - return - check_type("n_bmats", value, Integral) - check_greater_than("n_bmats", value, 0) - sef._n_bmats = value - def generate_tallies(self, materials, mat_indexes): """Construct the fission rate tally @@ -526,8 +526,6 @@ class TalliedFissionYieldHelper(FissionYieldHelper): Indexes for materials in ``materials`` tracked on this process """ - if self._n_bmat is None: - raise AttributeError("Number of burnable materials is not set") self._local_indexes = asarray(mat_indexes) # Tally group-wise fission reaction rates @@ -571,6 +569,23 @@ class TalliedFissionYieldHelper(FissionYieldHelper): tally data. """ + @classmethod + def from_operator(cls, operator, **kwargs): + """Create a new instance by pulling data from the operator + + Require the more concrete :class:`openmc.deplete.Operator` + because this needs knowledge of burnable materials + + Parameters + ---------- + operator : openmc.deplete.TransportOperator + Operator with a depletion chain + kwargs: optional + Optional keyword arguments to be passed to the underlying + ``__init__`` method + """ + return cls(operator.chain.nuclides, len(operator.burnable_mats), + **kwargs) class Integrator(ABC): """Abstract class for solving the time-integration for depletion diff --git a/openmc/deplete/helpers.py b/openmc/deplete/helpers.py index d6051c6dd3..b66c9ba5e7 100644 --- a/openmc/deplete/helpers.py +++ b/openmc/deplete/helpers.py @@ -202,6 +202,24 @@ class ConstantFissionYieldHelper(FissionYieldHelper): self._constant_yields[name] = ( nuc.yield_data[nuc.yield_energies[min_index]]) + @classmethod + def from_operator(cls, operator, energy=0.0253): + """Return a new ConstantFissionYieldHelper using operator data + + Parameters + ---------- + operator : openmc.deplete.TransportOperator + operator with a depletion chain + energy : float, optional + Energy for default fission yield libraries for nuclides with + multiple sets of yield data + + Returns + ------- + ConstantFissionYieldHelper + """ + return cls(operator.chain.nuclides, energy=energy) + @property def energy(self): return self._energy @@ -302,6 +320,36 @@ class FissionYieldCutoffHelper(TalliedFissionYieldHelper): self._thermal_yields[name] = thermal self._fast_yields[name] = fast + @classmethod + def from_operator(cls, operator, cutoff=112.0, + thermal_energy=0.0253, fast_energy=500e3): + """Construct a helper from an operator + + All keyword arguments are identical to their counterpart + in the main ``__init__`` method + + Parameters + ---------- + operator : openmc.deplete.Operator + Operator with a chain and burnable materials + cutoff : float, optional + Cutoff energy for tallying fast and thermal fissions + thermal_energy : float, optional + Energy to use when pulling thermal fission yields from + nuclides with multiple sets of yields + fast_energy : float, optional + Energy to use when pulling fast fission yields from + nuclides with multiple sets of yields + + Returns + ------- + FissionYieldCutoffHelper + + """ + return cls(operator.chain.nuclides, len(operator.burnable_mats), + cutoff=cutoff, thermal_energy=thermal_energy, + fast_energy=fast_energy) + @staticmethod def _find_fallback_energy(name, energies, cutoff, check_under): cutoff_func = operator.ge if check_under else operator.le diff --git a/openmc/deplete/operator.py b/openmc/deplete/operator.py index 11fded8e44..e967dc85d1 100644 --- a/openmc/deplete/operator.py +++ b/openmc/deplete/operator.py @@ -209,13 +209,10 @@ class Operator(TransportOperator): # Select and create fission yield helper fission_helper = self._fission_helpers_[fission_yield_mode] - if fission_yield_opts is None: - self._fsn_yield_helper = fission_helper(self.chain.nuclides) - else: - self._fsn_yield_helper = fission_yield_mode( - self.chain.nuclides, **fission_yield_opts) - if hasattr(self._fsn_yield_helper, "n_bmats"): - self._fsn_yield_helper.n_bmats = len(self.burnable_mats) + fission_yield_opts = ( + {} if fission_yield_opts is None else fission_yield_opts) + self._fsn_yield_helper = fission_helper.from_operator( + self, **fission_yield_opts) def __call__(self, vec, power): """Runs a simulation. From 0944503464aec68046a9407dd19078286d937fdf Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Wed, 14 Aug 2019 09:47:36 -0500 Subject: [PATCH 070/137] Document Chain.fission_yields attribute The user is allowed to pass a single dictionary of yields to be applied to all materials. Otherwise, each element in the iterable should correspond to a material ordered by the TransportOperator. Sparse checking is performed on the supplied input. Validation is possible, but could become a burden for potentially many burnable materials representing a lengthy list of nested dictionaries to dig through and validate. --- openmc/deplete/chain.py | 31 ++++++++++++++++++++++++-- tests/unit_tests/test_deplete_chain.py | 13 +++++++++++ 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/openmc/deplete/chain.py b/openmc/deplete/chain.py index 2354158562..a204e4821c 100644 --- a/openmc/deplete/chain.py +++ b/openmc/deplete/chain.py @@ -9,7 +9,8 @@ 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 @@ -122,13 +123,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: Real}}``, 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 @@ -642,3 +652,20 @@ class Chain(object): new_ratios[ground_tgt] = ground_br parent.reactions.append(ReactionTuple( "(n,gamma)", ground_tgt, capt_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 None: + self._fission_yields = None + return + if isinstance(yields, Mapping): + self._fission_yields = [yields] + return + check_type("fission_yields", yields, Iterable, Mapping) + self._fission_yields = yields diff --git a/tests/unit_tests/test_deplete_chain.py b/tests/unit_tests/test_deplete_chain.py index 546fa99c3e..2e5ad09832 100644 --- a/tests/unit_tests/test_deplete_chain.py +++ b/tests/unit_tests/test_deplete_chain.py @@ -346,3 +346,16 @@ def test_simple_fission_yields(simple_chain): """ 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 From c1d66bc0226b721620e1aad78b684074acb0c080 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Wed, 14 Aug 2019 10:49:46 -0500 Subject: [PATCH 071/137] Ensure # fission yields == # burnable materials in cram.deplete If the fission yields are a single entry, then itertools.repeat is used to apply the fission yields to all burnable materials. Otherwise, check that the number of fission yield libraries is equal to the number of materials to be burned. --- openmc/deplete/cram.py | 12 ++++++++---- openmc/deplete/nuclide.py | 21 +++++++++++---------- tests/unit_tests/test_deplete_chain.py | 9 ++++++++- 3 files changed, 27 insertions(+), 15 deletions(-) diff --git a/openmc/deplete/cram.py b/openmc/deplete/cram.py index d0c868004c..184c411bf3 100644 --- a/openmc/deplete/cram.py +++ b/openmc/deplete/cram.py @@ -42,10 +42,14 @@ def deplete(chain, x, rates, dt, matrix_func=None): Updated atom number vectors for each material """ - if not hasattr(chain, "fission_yields"): - fission_yields = repeat(chain.get_thermal_fission_yields()) - else: - fission_yields = chain.fission_yields + 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))) # Use multiprocessing pool to distribute work with Pool() as pool: diff --git a/openmc/deplete/nuclide.py b/openmc/deplete/nuclide.py index 615761cde9..5217831f49 100644 --- a/openmc/deplete/nuclide.py +++ b/openmc/deplete/nuclide.py @@ -86,9 +86,9 @@ 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_data : dict of float to :class:`openmc.deplete.FissionYieldDistribution` + Fission product yields at tabulated energies for this nuclide. Can be + treated as a nested dictionary ``{energy: {product: yield}}`` yield_energies : list of float Energies at which fission product yields exist @@ -300,15 +300,15 @@ class FissionYieldDistribution(Mapping): ------- FissionYieldDistribution """ - yields = {} + 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() - yield_mapobj = map(float, yield_elem.find("data").text.split()) + yields = map(float, yield_elem.find("data").text.split()) # Get a map of products to their corresponding yield - yields[energy] = dict(zip(products, yield_mapobj)) + all_yields[energy] = dict(zip(products, yields)) - return cls.from_dict(yields) + return cls.from_dict(all_yields) @classmethod def from_dict(cls, fission_yields): @@ -327,9 +327,7 @@ class FissionYieldDistribution(Mapping): energies = sorted(fission_yields) # Get a consistent set of products to produce a matrix of yields - shared_prod = set() - for prod_set in map(set, fission_yields.values()): - shared_prod |= prod_set + 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))) @@ -422,6 +420,9 @@ class FissionYield(Mapping): def __iter__(self): return iter(self.products) + def items(self): + return zip(self.products, self.yields) + def __add__(self, other): new = self.copy() new += other diff --git a/tests/unit_tests/test_deplete_chain.py b/tests/unit_tests/test_deplete_chain.py index 2e5ad09832..dcb5127cba 100644 --- a/tests/unit_tests/test_deplete_chain.py +++ b/tests/unit_tests/test_deplete_chain.py @@ -7,7 +7,7 @@ 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 @@ -359,3 +359,10 @@ def test_fission_yield_attribute(simple_chain): 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) From 6556abc2a869edd970c4adec247f95521df6828f Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Wed, 14 Aug 2019 10:53:49 -0500 Subject: [PATCH 072/137] Add empty fission yields to tests.TestChain This object is used in unit testing the depletion schemes, where the depletion matrix is pre-defined and there is no need for fission yields. However, the depletion interface expects the chain to have a fission_yields attribute. --- tests/dummy_operator.py | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/tests/dummy_operator.py b/tests/dummy_operator.py index acfdc5b583..cf76f86b59 100644 --- a/tests/dummy_operator.py +++ b/tests/dummy_operator.py @@ -7,6 +7,13 @@ from openmc.deplete.abc import TransportOperator, OperatorResult 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(): From 0931b58269f1e0e324c81048503680764411f7d4 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Wed, 14 Aug 2019 15:15:47 -0500 Subject: [PATCH 073/137] Fix wonky layout in test_deplete_fission_yields --- .../unit_tests/test_deplete_fission_yields.py | 89 +++++-------------- 1 file changed, 20 insertions(+), 69 deletions(-) diff --git a/tests/unit_tests/test_deplete_fission_yields.py b/tests/unit_tests/test_deplete_fission_yields.py index f3911d80d0..bf0bc6b8fc 100644 --- a/tests/unit_tests/test_deplete_fission_yields.py +++ b/tests/unit_tests/test_deplete_fission_yields.py @@ -5,9 +5,7 @@ from unittest.mock import Mock import pytest import numpy -from numpy.testing import assert_array_equal -from openmc.capi import Material from openmc.deplete.nuclide import Nuclide, FissionYieldDistribution from openmc.deplete.helpers import ( FissionYieldCutoffHelper, ConstantFissionYieldHelper) @@ -27,7 +25,7 @@ def nuclide_bundle(): u8yield_dict = {5.00e5: {"Xe135": 1.12e-3, "Gd155": 1.32e-12}} u238 = Nuclide() u238.name = "U238" - u238.yield_energies = (5.00e5, ) + u238.yield_energies = (5.00e5,) u238.yield_data = FissionYieldDistribution.from_dict(u8yield_dict) xe135 = Nuclide() @@ -35,11 +33,13 @@ def nuclide_bundle(): NuclideBundle = namedtuple("NuclideBundle", "u235 u238 xe135") return NuclideBundle(u235, u238, xe135) -@pytest.mark.parametrize("input_energy, u5_yield_energy", ( - (0.0253, 0.0253), (0.01, 0.0253), (4e5, 5e5))) + + +@pytest.mark.parametrize( + "input_energy, u5_yield_energy", + ((0.0253, 0.0253), (0.01, 0.0253), (4e5, 5e5))) def test_constant_helper(nuclide_bundle, input_energy, u5_yield_energy): - helper = ConstantFissionYieldHelper( - nuclide_bundle, energy=input_energy) + helper = ConstantFissionYieldHelper(nuclide_bundle, energy=input_energy) assert helper.energy == input_energy assert helper.constant_yields == { "U235": nuclide_bundle.u235.yield_data[u5_yield_energy], @@ -54,37 +54,29 @@ def test_constant_helper(nuclide_bundle, input_energy, u5_yield_energy): def test_cutoff_construction(nuclide_bundle): # defaults - helper = FissionYieldCutoffHelper( - nuclide_bundle, 1) + helper = FissionYieldCutoffHelper(nuclide_bundle, 1) assert helper.constant_yields == { "U238": nuclide_bundle.u238.yield_data[5.0e5]} assert helper.thermal_yields == { "U235": nuclide_bundle.u235.yield_data[0.0253]} - assert helper.fast_yields == { - "U235": nuclide_bundle.u235.yield_data[5e5]} + assert helper.fast_yields == {"U235": nuclide_bundle.u235.yield_data[5e5]} # use 14 MeV yields - helper = FissionYieldCutoffHelper( - nuclide_bundle, 1, fast_energy=14e6) + helper = FissionYieldCutoffHelper(nuclide_bundle, 1, fast_energy=14e6) assert helper.constant_yields == { "U238": nuclide_bundle.u238.yield_data[5.0e5]} assert helper.thermal_yields == { "U235": nuclide_bundle.u235.yield_data[0.0253]} - assert helper.fast_yields == { - "U235": nuclide_bundle.u235.yield_data[14e6]} + assert helper.fast_yields == {"U235": nuclide_bundle.u235.yield_data[14e6]} # specify missing thermal yields -> use 0.0253 - helper = FissionYieldCutoffHelper( - nuclide_bundle, 1, thermal_energy=1) + helper = FissionYieldCutoffHelper(nuclide_bundle, 1, thermal_energy=1) assert helper.thermal_yields == { "U235": nuclide_bundle.u235.yield_data[0.0253]} - assert helper.fast_yields == { - "U235": nuclide_bundle.u235.yield_data[5e5]} + assert helper.fast_yields == {"U235": nuclide_bundle.u235.yield_data[5e5]} # request missing fast yields -> use epithermal - helper = FissionYieldCutoffHelper( - nuclide_bundle, 1, fast_energy=1e4) + helper = FissionYieldCutoffHelper(nuclide_bundle, 1, fast_energy=1e4) assert helper.thermal_yields == { "U235": nuclide_bundle.u235.yield_data[0.0253]} - assert helper.fast_yields == { - "U235": nuclide_bundle.u235.yield_data[5e5]} + assert helper.fast_yields == {"U235": nuclide_bundle.u235.yield_data[5e5]} # test failures in cutoff: super low, super high with pytest.raises(ValueError, match="thermal yields"): FissionYieldCutoffHelper( @@ -120,66 +112,25 @@ def test_cutoff_helper(nuclide_bundle, therm_frac): proxy.generate_tallies(materials, local_mats) non_zero_nucs = [n.name for n in nuclide_bundle] tally_nucs = proxy.update_nuclides_from_operator(non_zero_nucs) - assert tally_nucs == ("U235", ) + assert tally_nucs == ("U235",) # Emulate building tallies # material x energy, tallied_nuclides, 3 proxy_flux = 1e6 tally_data = numpy.empty((n_bmats * 2, 1, 3)) tally_data[0, 0, 1] = therm_frac * proxy_flux - tally_data[1, 0, 1] = (1 - therm_frac) * proxy_flux + tally_data[1, 0, 1] = (1 - therm_frac) * proxy_flux proxy._fission_rate_tally.results = tally_data proxy.unpack() # expected results of shape (n_mats, 2, n_tnucs) expected_results = numpy.empty((1, 2, 1)) expected_results[:, 0] = therm_frac - expected_results[:, 1] = (1 - therm_frac) + expected_results[:, 1] = 1 - therm_frac assert proxy.results == pytest.approx(expected_results) actual_yields = proxy.weighted_yields(0) assert actual_yields["U238"] == nuclide_bundle.u238.yield_data[5e5] assert actual_yields["U235"] == ( proxy.thermal_yields["U235"] * therm_frac - + proxy.fast_yields["U235"] * (1 - therm_frac)) -"""Test the FissionYieldHelpers""" - -from collections import namedtuple - -import pytest - -from openmc.deplete.nuclide import Nuclide, FissionYieldDistribution -from openmc.deplete.helpers import ConstantFissionYieldHelper - - -@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.name = "U235" - u235.yield_energies = (0.0253, 5.0e5, 1.40e7) - u235.yield_data = FissionYieldDistribution.from_dict(u5yield_dict) - - u8yield_dict = {5.00e5: {"Xe135": 1.12e-3, "Gd155": 1.32e-12}} - u238 = Nuclide() - u238.name = "U238" - u238.yield_energies = (5.00e5, ) - u238.yield_data = FissionYieldDistribution.from_dict(u8yield_dict) - - xe135 = Nuclide() - xe135.name = "Xe135" - - NuclideBundle = namedtuple("NuclideBundle", "u235 u238 xe135") - return NuclideBundle(u235, u238, xe135) -@pytest.mark.parametrize("input_energy, u5_yield_energy", ( - (0.0253, 0.0253), (0.01, 0.0253), (4e5, 5e5))) -def test_constant_helper(nuclide_bundle, input_energy, u5_yield_energy): - helper = ConstantFissionYieldHelper( - nuclide_bundle, energy=input_energy) - assert helper.energy == input_energy - assert helper.constant_yields == { - "U235": nuclide_bundle.u235.yield_data[u5_yield_energy], - "U238": nuclide_bundle.u238.yield_data[5.00e5]} # only epithermal - assert helper.constant_yields == helper.weighted_yields(1) + + proxy.fast_yields["U235"] * (1 - therm_frac) + ) From f2fa86fd5264432c89907c40109bbbb429746baf Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Wed, 14 Aug 2019 16:09:41 -0500 Subject: [PATCH 074/137] Delegate Nuclide yield properties to FissionYieldDistribution For consistency across the various fission yield helpers, the Nuclide object now sets the yield data to always be a FissionYieldDistribution or an empty dictionary. Nuclide.yield_energies is now a property that returns the energies from the FissionYieldDistribution. When setting the yield_data, the Nuclide enforces the object to be a Mapping instance. If the incoming yield_data is a FissionYieldDistribution, no conversions are made. Otherwise, a new distribution is created with the from_dict method. The Chain.from_xml method now creates the yield_data for nuclides using FissionYieldDistribution.from_dict Changes were made to some unit tests that attempted to set the yield_energies, which do not have a setter. --- openmc/deplete/chain.py | 14 +++++----- openmc/deplete/nuclide.py | 27 +++++++++++++++---- tests/unit_tests/test_deplete_chain.py | 3 +-- .../unit_tests/test_deplete_fission_yields.py | 2 -- tests/unit_tests/test_deplete_nuclide.py | 8 ++++-- 5 files changed, 37 insertions(+), 17 deletions(-) diff --git a/openmc/deplete/chain.py b/openmc/deplete/chain.py index a204e4821c..dd13a338be 100644 --- a/openmc/deplete/chain.py +++ b/openmc/deplete/chain.py @@ -15,6 +15,7 @@ from warnings import warn from openmc.checkvalue import check_type, check_less_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, @@ -276,11 +277,12 @@ 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(): @@ -294,10 +296,10 @@ 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.from_dict(yield_data)) # Display warnings if missing_daughter: diff --git a/openmc/deplete/nuclide.py b/openmc/deplete/nuclide.py index 5217831f49..649b6700f8 100644 --- a/openmc/deplete/nuclide.py +++ b/openmc/deplete/nuclide.py @@ -86,10 +86,10 @@ 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 :class:`openmc.deplete.FissionYieldDistribution` + 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 : list of float + yield_energies : tuple of float or None Energies at which fission product yields exist """ @@ -107,8 +107,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): @@ -118,6 +117,25 @@ class Nuclide(object): def n_reaction_paths(self): return len(self.reactions) + @property + def yield_data(self): + if self._yield_data is None: + return {} + return self._yield_data + + @yield_data.setter + def yield_data(self, fission_yields): + check_type("fission_yields", fission_yields, Mapping) + if isinstance(fission_yields, FissionYieldDistribution): + self._yield_data = fission_yields + self._yield_data = FissionYieldDistribution.from_dict(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. @@ -173,7 +191,6 @@ class Nuclide(object): fpy_elem = element.find('neutron_fission_yields') if fpy_elem is not None: nuc.yield_data = FissionYieldDistribution.from_xml_element(fpy_elem) - nuc.yield_energies = list(sorted(nuc.yield_data.keys())) return nuc diff --git a/tests/unit_tests/test_deplete_chain.py b/tests/unit_tests/test_deplete_chain.py index dcb5127cba..98ca023f00 100644 --- a/tests/unit_tests/test_deplete_chain.py +++ b/tests/unit_tests/test_deplete_chain.py @@ -129,7 +129,7 @@ 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].products == ("A", "B") assert (nuc.yield_data[0.0253].yields == [0.0292737, 0.002566345]).all() @@ -163,7 +163,6 @@ def test_export_to_xml(run_in_tmpdir): 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 = nuclide.FissionYieldDistribution.from_dict({ 0.0253: {"A": 0.0292737, "B": 0.002566345}}) diff --git a/tests/unit_tests/test_deplete_fission_yields.py b/tests/unit_tests/test_deplete_fission_yields.py index bf0bc6b8fc..2330742361 100644 --- a/tests/unit_tests/test_deplete_fission_yields.py +++ b/tests/unit_tests/test_deplete_fission_yields.py @@ -19,13 +19,11 @@ def nuclide_bundle(): 1.40e7: {"Xe135": 4.54e-3, "Gd155": 5.83e-8}} u235 = Nuclide() u235.name = "U235" - u235.yield_energies = (0.0253, 5.0e5, 1.40e7) u235.yield_data = FissionYieldDistribution.from_dict(u5yield_dict) u8yield_dict = {5.00e5: {"Xe135": 1.12e-3, "Gd155": 1.32e-12}} u238 = Nuclide() u238.name = "U238" - u238.yield_energies = (5.00e5,) u238.yield_data = FissionYieldDistribution.from_dict(u8yield_dict) xe135 = Nuclide() diff --git a/tests/unit_tests/test_deplete_nuclide.py b/tests/unit_tests/test_deplete_nuclide.py index 2a3993921f..ef5c3d1ca7 100644 --- a/tests/unit_tests/test_deplete_nuclide.py +++ b/tests/unit_tests/test_deplete_nuclide.py @@ -3,6 +3,7 @@ import xml.etree.ElementTree as ET import numpy +import pytest from openmc.deplete import nuclide @@ -73,8 +74,12 @@ def test_from_xml(): ] expected_yield_data = nuclide.FissionYieldDistribution.from_dict({ 0.0253: {"Xe138": 0.0481413, "Zr100": 0.0497641, "Te134": 0.062155}}) - assert u235.yield_energies == [0.0253] 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(): @@ -91,7 +96,6 @@ 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 = nuclide.FissionYieldDistribution.from_dict( {0.0253: {"A": 0.0292737, "B": 0.002566345}}) element = C.to_xml_element() From 454e6cc6b6739cd6f358951757354f654171ce80 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Wed, 14 Aug 2019 17:39:57 -0500 Subject: [PATCH 075/137] Add AveragedFissionYieldHelper operator helper Computes the effective fission yields for nuclides with multiple sets of yields by 1) Tallying fission rate and energy-weighted fission rate 2) Determining average energy at which fission events occur 3) Interpolating [lin-lin] between adjacent fission yields based on this average energy Uses a second tally with an EnergyFunctionFilter to tally E * sigma_f * phi. Added unit tests with the proxy-style class that doesn't use the C API to generate tallies but tests all other aspects of the implementation. Tests cover three possible average energies (below minimum supplied yield energy, above max supplied yield energy, and between two) The Operator uses fission_yield_mode="average" to select this helper. The helper also has a from_operator class method. --- docs/source/pythonapi/deplete.rst | 1 + openmc/deplete/abc.py | 10 +- openmc/deplete/helpers.py | 157 +++++++++++++++++- openmc/deplete/operator.py | 4 +- .../unit_tests/test_deplete_fission_yields.py | 69 ++++++-- 5 files changed, 217 insertions(+), 24 deletions(-) diff --git a/docs/source/pythonapi/deplete.rst b/docs/source/pythonapi/deplete.rst index 44f8b70e05..5699f3f2a8 100644 --- a/docs/source/pythonapi/deplete.rst +++ b/docs/source/pythonapi/deplete.rst @@ -77,6 +77,7 @@ data, such as number densities and reaction rates for each material. :template: myclass.rst AtomNumber + AveragedFissionYieldHelper ChainFissionHelper ConstantFissionYieldHelper DirectReactionRateHelper diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py index 5efda87029..d1dc2cdc4d 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -504,6 +504,8 @@ class TalliedFissionYieldHelper(FissionYieldHelper): 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 @@ -513,7 +515,8 @@ class TalliedFissionYieldHelper(FissionYieldHelper): self.n_bmats = n_bmats self._local_indexes = None self._fission_rate_tally = None - self._tally_index = {} + self._tally_nucs = [] + self.results = None def generate_tallies(self, materials, mat_indexes): """Construct the fission rate tally @@ -554,10 +557,10 @@ class TalliedFissionYieldHelper(FissionYieldHelper): if len(overlap) == 0: # tally no nuclides, but keep the Tally alive self._fission_rate_tally.nuclides = None - self._tally_index = [] + self._tally_nucs = [] return tuple() nuclides = tuple(sorted(overlap)) - self._tally_index = [self._chain_nuclides[n] for n in nuclides] + self._tally_nucs = [self._chain_nuclides[n] for n in nuclides] self._fission_rate_tally.nuclides = nuclides return nuclides @@ -587,6 +590,7 @@ class TalliedFissionYieldHelper(FissionYieldHelper): return cls(operator.chain.nuclides, len(operator.burnable_mats), **kwargs) + class Integrator(ABC): """Abstract class for solving the time-integration for depletion diff --git a/openmc/deplete/helpers.py b/openmc/deplete/helpers.py index b66c9ba5e7..19e51628d9 100644 --- a/openmc/deplete/helpers.py +++ b/openmc/deplete/helpers.py @@ -9,14 +9,15 @@ from numpy import dot, zeros, newaxis from openmc.checkvalue import check_type, check_greater_than from openmc.capi import ( - Tally, MaterialFilter, EnergyFilter) + Tally, MaterialFilter, EnergyFilter, EnergyFunctionFilter) from .abc import ( ReactionRateHelper, EnergyHelper, FissionYieldHelper, TalliedFissionYieldHelper) __all__ = ( "DirectReactionRateHelper", "ChainFissionHelper", - "ConstantFissionYieldHelper", "FissionYieldCutoffHelper") + "ConstantFissionYieldHelper", "FissionYieldCutoffHelper", + "AveragedFissionYieldHelper") # ------------------------------------- # Helpers for generating reaction rates @@ -386,7 +387,7 @@ class FissionYieldCutoffHelper(TalliedFissionYieldHelper): def unpack(self): """Obtain fast and thermal fission fractions from tally""" fission_rates = self._fission_rate_tally.results[..., 1].reshape( - self.n_bmats, 2, len(self._tally_index)) + 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() @@ -422,10 +423,10 @@ class FissionYieldCutoffHelper(TalliedFissionYieldHelper): rates = self.results[local_mat_index] yields = self.constant_yields # iterate over thermal then fast yields, prefer __mul__ to __rmul__ - for therm_frac, nuc in zip(rates[0], self._tally_index): + for therm_frac, nuc in zip(rates[0], self._tally_nucs): yields[nuc.name] = self._thermal_yields[nuc.name] * therm_frac - for fast_frac, nuc in zip(rates[1], self._tally_index): + for fast_frac, nuc in zip(rates[1], self._tally_nucs): yields[nuc.name] += self._fast_yields[nuc.name] * fast_frac return yields @@ -442,3 +443,149 @@ class FissionYieldCutoffHelper(TalliedFissionYieldHelper): for key, sub in self._fast_yields.items(): out[key] = sub.copy() return out + + +class AveragedFissionYieldHelper(TalliedFissionYieldHelper): + r"""Class that computes fission yields based on average fission energy + + Computes average energy at which fission events occured + reactions for all nuclides with multiple sets of fission yields + by + + .. 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, a geometric mean of the yield distributions is + used. + + Parameters + ---------- + chain_nuclides : iterable of openmc.deplete.Nuclide + Nuclides tracked in the depletion chain. Not necessary + that all have yield data. + n_bmats : int + Number of burnable materials tracked in the problem. + + Attributes + ---------- + n_bmats : int + Number of burnable materials tracked in the problem. + 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. + """ + + def __init__(self, chain_nuclides, n_bmats): + super().__init__(chain_nuclides, n_bmats) + 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 + Indexes for materials in ``materials`` tracked on this + process + """ + super().generate_tallies(materials, mat_indexes) + fission_tally = self._fission_rate_tally + + weighted_tally = Tally() + weighted_tally.filters = fission_tally.filters.copy() + weighted_tally.nuclides = fission_tally.nuclides + weighted_tally.scores = ['fission'] + + ene_bin = EnergyFilter() + ene_bin.bins = (0, self._upper_energy) + fission_tally.filters.append(ene_bin) + + ene_filter = EnergyFunctionFilter() + ene_filter.set_data((0, self._upper_energy), (0, self._upper_energy)) + weighted_tally.filters.append(ene_filter) + self._weighted_tally = weighted_tally + + def unpack(self): + """Unpack tallies and populate :attr:`results` with average energies""" + 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}}`` + """ + 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): + """Return a new helper with data from an operator + + Parameters + ---------- + operator : openmc.deplete.Operator + Operator with a depletion chain + + Returns + ------- + AveragedFissionYieldHelper + """ + return cls(operator.chain.nuclides, len(operator.burnable_mats)) diff --git a/openmc/deplete/operator.py b/openmc/deplete/operator.py index e967dc85d1..b7df9b8fc7 100644 --- a/openmc/deplete/operator.py +++ b/openmc/deplete/operator.py @@ -27,7 +27,7 @@ from .reaction_rates import ReactionRates from .results_list import ResultsList from .helpers import ( DirectReactionRateHelper, ChainFissionHelper, ConstantFissionYieldHelper, - FissionYieldCutoffHelper) + FissionYieldCutoffHelper, AveragedFissionYieldHelper) def _distribute(items): @@ -92,6 +92,7 @@ class Operator(TransportOperator): * "constant": :class:`openmc.deplete.ConstantFissionYieldHelper` * "cutoff": :class:`openmc.deplete.FissionYieldCutoffHelper` + * "average": :class:`openmc.deplete.AveragedFissionYieldHelper` The documentation on these classes describe their methodology and differences. ``"constant"`` will treat fission yields as @@ -140,6 +141,7 @@ class Operator(TransportOperator): Whether to differentiate burnable materials with multiple instances """ _fission_helpers_ = { + "average": AveragedFissionYieldHelper, "constant": ConstantFissionYieldHelper, "cutoff": FissionYieldCutoffHelper, } diff --git a/tests/unit_tests/test_deplete_fission_yields.py b/tests/unit_tests/test_deplete_fission_yields.py index 2330742361..0a87c4fb6c 100644 --- a/tests/unit_tests/test_deplete_fission_yields.py +++ b/tests/unit_tests/test_deplete_fission_yields.py @@ -8,7 +8,11 @@ import numpy from openmc.deplete.nuclide import Nuclide, FissionYieldDistribution from openmc.deplete.helpers import ( - FissionYieldCutoffHelper, ConstantFissionYieldHelper) + FissionYieldCutoffHelper, ConstantFissionYieldHelper, + AveragedFissionYieldHelper) + + +MATERIALS = ["1", "2"] @pytest.fixture(scope="module") @@ -45,11 +49,6 @@ def test_constant_helper(nuclide_bundle, input_energy, u5_yield_energy): assert helper.constant_yields == helper.weighted_yields(1) -# --------------------------------- -# Test the FissionYieldCutoffHelper -# --------------------------------- - - def test_cutoff_construction(nuclide_bundle): # defaults helper = FissionYieldCutoffHelper(nuclide_bundle, 1) @@ -92,22 +91,23 @@ def test_cutoff_failure(key): FissionYieldCutoffHelper(None, None, **{key: -1}) -class CutoffProxy(FissionYieldCutoffHelper): - """Proxy that supplies a set of tallies""" - +class ProxyMixin(object): + """Mixing that overloads the tally generation""" def generate_tallies(self, materials, mat_indexes): self._fission_rate_tally = Mock() self._local_indexes = numpy.asarray(mat_indexes) +class CutoffProxy(ProxyMixin, FissionYieldCutoffHelper): + """Proxy that supplies a set of tallies""" + + # emulate some split between fast and thermal U235 fissions @pytest.mark.parametrize("therm_frac", (0.5, 0.2, 0.8)) def test_cutoff_helper(nuclide_bundle, therm_frac): - materials = ["1", "2"] # TODO Use real C API materials? - n_bmats = len(materials) - local_mats = [0] + n_bmats = len(MATERIALS) proxy = CutoffProxy(nuclide_bundle, n_bmats) - proxy.generate_tallies(materials, local_mats) + proxy.generate_tallies(MATERIALS, [0]) non_zero_nucs = [n.name for n in nuclide_bundle] tally_nucs = proxy.update_nuclides_from_operator(non_zero_nucs) assert tally_nucs == ("U235",) @@ -130,5 +130,44 @@ def test_cutoff_helper(nuclide_bundle, therm_frac): assert actual_yields["U238"] == nuclide_bundle.u238.yield_data[5e5] assert actual_yields["U235"] == ( proxy.thermal_yields["U235"] * therm_frac - + proxy.fast_yields["U235"] * (1 - therm_frac) - ) + + proxy.fast_yields["U235"] * (1 - therm_frac)) + + +class AverageProxy(ProxyMixin, AveragedFissionYieldHelper): + """Proxy for generating mock set of tallies""" + def generate_tallies(self, materials, mat_indexes): + super().generate_tallies(materials, mat_indexes) + self._weighted_tally = Mock() + + +@pytest.mark.parametrize("avg_energy", (0.01, 100, 15e6)) +def test_averaged_helper(nuclide_bundle, avg_energy): + proxy = AverageProxy(nuclide_bundle, len(MATERIALS)) + proxy.generate_tallies(MATERIALS, [0]) + tallied_nucs = proxy.update_nuclides_from_operator( + [n.name for n in nuclide_bundle]) + assert tallied_nucs == ("U235", ) + # enforce some average energy + proxy_flux = 1e16 + fission_results = numpy.ones((len(MATERIALS), 1, 3)) * proxy_flux + weighted_results = fission_results * avg_energy + proxy._fission_rate_tally.results = fission_results + proxy._weighted_tally.results = weighted_results + proxy.unpack() + expected_results = numpy.ones((1, 1)) * avg_energy + assert proxy.results == pytest.approx(expected_results) + + actual_yields = proxy.weighted_yields(0) + # constant U238 => no interpolation + assert actual_yields["U238"] == nuclide_bundle.u238.yield_data[5e5] + # construct expected yields + if avg_energy < 0.0253: # take thermal U235 yields + exp_u235_yields = nuclide_bundle.u235.yield_data[0.0253] + elif avg_energy > 14e6: # take fastest U235 yields + exp_u235_yields = nuclide_bundle.u235.yield_data[14e6] + else: # reconstruct between thermal and epithermal + thermal = nuclide_bundle.u235.yield_data[0.0253] + epithermal = nuclide_bundle.u235.yield_data[5e5] + split = (avg_energy - 0.0253) / (5e5 - 0.0253) + exp_u235_yields = thermal * (1 - split) + epithermal * split + assert actual_yields["U235"] == exp_u235_yields From aa86aa5262690e9a3e5b3ac08bac799ad642209f Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Wed, 14 Aug 2019 18:02:27 -0500 Subject: [PATCH 076/137] Improve documentation for FissionYieldHelpers Add a separate section in the documentation describing the helpers. Reword purpose of mat_indexes in generate_tallies --- docs/source/pythonapi/deplete.rst | 19 ++++++++++++++----- openmc/deplete/abc.py | 14 ++++++++++---- openmc/deplete/helpers.py | 19 +++++++++++++------ openmc/deplete/nuclide.py | 2 +- openmc/deplete/operator.py | 15 ++++++--------- 5 files changed, 44 insertions(+), 25 deletions(-) diff --git a/docs/source/pythonapi/deplete.rst b/docs/source/pythonapi/deplete.rst index 5699f3f2a8..5b26eb38aa 100644 --- a/docs/source/pythonapi/deplete.rst +++ b/docs/source/pythonapi/deplete.rst @@ -77,16 +77,25 @@ data, such as number densities and reaction rates for each material. :template: myclass.rst AtomNumber - AveragedFissionYieldHelper - ChainFissionHelper - ConstantFissionYieldHelper - DirectReactionRateHelper - FissionYieldCutoffHelper 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: diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py index d1dc2cdc4d..fd5278d894 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -443,8 +443,11 @@ class FissionYieldHelper(ABC): materials : iterable of C-API materials Materials to be used in :class:`openmc.capi.MaterialFilter` mat_indexes : iterable of int - Indexes for materials in ``materials`` tracked on this - process + 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_nuclides_from_operator(self, nuclides): @@ -526,8 +529,11 @@ class TalliedFissionYieldHelper(FissionYieldHelper): materials : iterable of :class:`openmc.capi.Material` Materials to be used in :class:`openmc.capi.MaterialFilter` mat_indexes : iterable of int - Indexes for materials in ``materials`` tracked on this - process + 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) diff --git a/openmc/deplete/helpers.py b/openmc/deplete/helpers.py index 19e51628d9..8dc9035b3d 100644 --- a/openmc/deplete/helpers.py +++ b/openmc/deplete/helpers.py @@ -376,8 +376,11 @@ class FissionYieldCutoffHelper(TalliedFissionYieldHelper): materials : iterable of :class:`openmc.capi.Material` Materials to be used in :class:`openmc.capi.MaterialFilter` mat_indexes : iterable of int - Indexes for materials in ``materials`` tracked on this - process + 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() @@ -465,8 +468,9 @@ class AveragedFissionYieldHelper(TalliedFissionYieldHelper): 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, a geometric mean of the yield distributions is - used. + of yields, the effective fission yield computed by + linearly interpolating between yields provided at the + nearest energies above and below the average. Parameters ---------- @@ -502,8 +506,11 @@ class AveragedFissionYieldHelper(TalliedFissionYieldHelper): materials : iterable of :class:`openmc.capi.Material` Materials to be used in :class:`openmc.capi.MaterialFilter` mat_indexes : iterable of int - Indexes for materials in ``materials`` tracked on this - process + 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 diff --git a/openmc/deplete/nuclide.py b/openmc/deplete/nuclide.py index 649b6700f8..d2b0acc169 100644 --- a/openmc/deplete/nuclide.py +++ b/openmc/deplete/nuclide.py @@ -236,7 +236,7 @@ class Nuclide(object): class FissionYieldDistribution(Mapping): - """Class for storing energy-dependent fission yields for a single nuclide + """Energy-dependent fission product yields for a single nuclide Can be used as a dictionary mapping energies and products to fission yields:: diff --git a/openmc/deplete/operator.py b/openmc/deplete/operator.py index b7df9b8fc7..5b9d2fca67 100644 --- a/openmc/deplete/operator.py +++ b/openmc/deplete/operator.py @@ -86,19 +86,16 @@ 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 : str, {"constant", "cutoff"} + fission_yield_mode : ("constant", "cutoff", "average") Key indicating what fission product yield scheme to use. The - key determines what fission energy helper is used:: + key determines what fission energy helper is used: - * "constant": :class:`openmc.deplete.ConstantFissionYieldHelper` - * "cutoff": :class:`openmc.deplete.FissionYieldCutoffHelper` - * "average": :class:`openmc.deplete.AveragedFissionYieldHelper` + * "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. ``"constant"`` will treat fission yields as - constant with respect to energy, while ``"cutoff"`` will - compute effective yields based on the number of fission events - above and below a cutoff. Default: ``"constant"`` + 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 From d8b5752b59da10eb4d0a902da9e902e0c83ce5d4 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Thu, 15 Aug 2019 11:20:04 -0500 Subject: [PATCH 077/137] Make n_bmat required only for FissionYieldCutoffHelper Base class TalliedFissionYieldHelper and AveragedFissionYieldHelper instances do not need to know the number of burnable materials, and thus it is no longer a required input argument. Changes propagated into the from_operator methods and tests/unit_tests/test_deplete_fission_yields.py Removed explicit TalliedFissionYieldHelper.from_operator method as it is identical and now inherited from the base FissionYieldHelper.from_operator method --- openmc/deplete/abc.py | 25 +------------------ openmc/deplete/helpers.py | 15 +++++------ .../unit_tests/test_deplete_fission_yields.py | 2 +- 3 files changed, 8 insertions(+), 34 deletions(-) diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py index fd5278d894..56914367e2 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -497,13 +497,9 @@ class TalliedFissionYieldHelper(FissionYieldHelper): chain_nuclides : iterable of openmc.deplete.Nuclide Nuclides tracked in the depletion chain. Not necessary that all have yield data. - n_bmats : int - Number of burnable materials tracked in the problem. Attributes ---------- - n_bmats : int - Number of burnable materials tracked in the problem. 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}}`` @@ -513,9 +509,8 @@ class TalliedFissionYieldHelper(FissionYieldHelper): _upper_energy = 20.0e6 # upper energy for tallies - def __init__(self, chain_nuclides, n_bmats): + def __init__(self, chain_nuclides): super().__init__(chain_nuclides) - self.n_bmats = n_bmats self._local_indexes = None self._fission_rate_tally = None self._tally_nucs = [] @@ -578,24 +573,6 @@ class TalliedFissionYieldHelper(FissionYieldHelper): tally data. """ - @classmethod - def from_operator(cls, operator, **kwargs): - """Create a new instance by pulling data from the operator - - Require the more concrete :class:`openmc.deplete.Operator` - because this needs knowledge of burnable materials - - Parameters - ---------- - operator : openmc.deplete.TransportOperator - Operator with a depletion chain - kwargs: optional - Optional keyword arguments to be passed to the underlying - ``__init__`` method - """ - return cls(operator.chain.nuclides, len(operator.burnable_mats), - **kwargs) - class Integrator(ABC): """Abstract class for solving the time-integration for depletion diff --git a/openmc/deplete/helpers.py b/openmc/deplete/helpers.py index 8dc9035b3d..3303dd4e40 100644 --- a/openmc/deplete/helpers.py +++ b/openmc/deplete/helpers.py @@ -299,7 +299,8 @@ class FissionYieldCutoffHelper(TalliedFissionYieldHelper): 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) - super().__init__(chain_nuclides, n_bmats) + self.n_bmats = n_bmats + super().__init__(chain_nuclides) self._cutoff = cutoff self._thermal_yields = {} self._fast_yields = {} @@ -477,13 +478,9 @@ class AveragedFissionYieldHelper(TalliedFissionYieldHelper): chain_nuclides : iterable of openmc.deplete.Nuclide Nuclides tracked in the depletion chain. Not necessary that all have yield data. - n_bmats : int - Number of burnable materials tracked in the problem. Attributes ---------- - n_bmats : int - Number of burnable materials tracked in the problem. 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}}`` @@ -494,8 +491,8 @@ class AveragedFissionYieldHelper(TalliedFissionYieldHelper): is the number of nuclides with multiple sets of fission yields. """ - def __init__(self, chain_nuclides, n_bmats): - super().__init__(chain_nuclides, n_bmats) + def __init__(self, chain_nuclides): + super().__init__(chain_nuclides) self._weighted_tally = None def generate_tallies(self, materials, mat_indexes): @@ -588,11 +585,11 @@ class AveragedFissionYieldHelper(TalliedFissionYieldHelper): Parameters ---------- - operator : openmc.deplete.Operator + operator : openmc.deplete.TransportOperator Operator with a depletion chain Returns ------- AveragedFissionYieldHelper """ - return cls(operator.chain.nuclides, len(operator.burnable_mats)) + return cls(operator.chain.nuclides) diff --git a/tests/unit_tests/test_deplete_fission_yields.py b/tests/unit_tests/test_deplete_fission_yields.py index 0a87c4fb6c..6285fe5b9b 100644 --- a/tests/unit_tests/test_deplete_fission_yields.py +++ b/tests/unit_tests/test_deplete_fission_yields.py @@ -142,7 +142,7 @@ class AverageProxy(ProxyMixin, AveragedFissionYieldHelper): @pytest.mark.parametrize("avg_energy", (0.01, 100, 15e6)) def test_averaged_helper(nuclide_bundle, avg_energy): - proxy = AverageProxy(nuclide_bundle, len(MATERIALS)) + proxy = AverageProxy(nuclide_bundle) proxy.generate_tallies(MATERIALS, [0]) tallied_nucs = proxy.update_nuclides_from_operator( [n.name for n in nuclide_bundle]) From 73a3ad3e5ee26386259dbeb66f17f2a9f6445e61 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Fri, 16 Aug 2019 16:13:33 -0500 Subject: [PATCH 078/137] Guard against using FPY tally results if not needed If there are no nuclides with multiple sets of yields, then the tallies needed for fission yields may or or may not exist. Regardless, this means that using constant_yields is absolutely appropriate and there is no need for unpacking on FissionYieldCutoffHelper nor AveragedFissionYieldHelper. The weighted_yields methods for these two classes simply return the set of constant yields if this is the case. --- openmc/deplete/abc.py | 5 ++++- openmc/deplete/helpers.py | 12 +++++++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py index 56914367e2..4406614eb4 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -556,8 +556,11 @@ class TalliedFissionYieldHelper(FissionYieldHelper): """ overlap = set(self._chain_nuclides).intersection(set(nuclides)) if len(overlap) == 0: + if self._fission_rate_tally is None: + # No tally to update + return tuple() # tally no nuclides, but keep the Tally alive - self._fission_rate_tally.nuclides = None + self._fission_rate_tally.nuclides = [] self._tally_nucs = [] return tuple() nuclides = tuple(sorted(overlap)) diff --git a/openmc/deplete/helpers.py b/openmc/deplete/helpers.py index 3303dd4e40..1336af7e69 100644 --- a/openmc/deplete/helpers.py +++ b/openmc/deplete/helpers.py @@ -390,6 +390,9 @@ class FissionYieldCutoffHelper(TalliedFissionYieldHelper): 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] @@ -424,8 +427,10 @@ class FissionYieldCutoffHelper(TalliedFissionYieldHelper): library : dict Dictionary of ``{parent: {product: fyield}}`` """ - rates = self.results[local_mat_index] 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, nuc in zip(rates[0], self._tally_nucs): yields[nuc.name] = self._thermal_yields[nuc.name] * therm_frac @@ -528,6 +533,9 @@ class AveragedFissionYieldHelper(TalliedFissionYieldHelper): 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 = ( @@ -556,6 +564,8 @@ class AveragedFissionYieldHelper(TalliedFissionYieldHelper): 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): From 0cf3ea3b16e65e928af1b66e9b0b3e38567a8106 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Mon, 19 Aug 2019 09:43:40 -0500 Subject: [PATCH 079/137] Improve updating of nuclides on tallied FPY helpers Renamed update_nuclides_from_operator -> update_tally_nuclides to be more general. The base method TalliedFissionYieldHelper.update_tally_nuclides will raise an AttributeError if this method is called before the tallies are generated. The inner logic for setting the tallied nuclides can be streamlined given the assumption that the tally exists. AveragedFissionYieldHelper.update_tally_nuclides has been improved to set the nuclides for the weighted tally now. This was not captured prior because the nuclides were copied over from the fission rate tally during tally generation, prior to when nuclides were set. --- openmc/deplete/abc.py | 33 ++++++++++--------- openmc/deplete/helpers.py | 27 ++++++++++++++- openmc/deplete/operator.py | 2 +- .../unit_tests/test_deplete_fission_yields.py | 4 +-- 4 files changed, 46 insertions(+), 20 deletions(-) diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py index 4406614eb4..da9bb5244e 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -450,7 +450,7 @@ class FissionYieldHelper(ABC): in parallel mode. """ - def update_nuclides_from_operator(self, nuclides): + def update_tally_nuclides(self, nuclides): """Return nuclides with non-zero densities and yield data Parameters @@ -489,7 +489,7 @@ class TalliedFissionYieldHelper(FissionYieldHelper): Generates a basic fission rate tally in all burnable materials with :meth:`generate_tallies`, and set nuclides to be tallied with - :meth:`update_nuclides_from_operator`. Subclasses will need to implement + :meth:`update_tally_nuclides`. Subclasses will need to implement :meth:`unpack` and :meth:`weighted_yields`. Parameters @@ -538,31 +538,32 @@ class TalliedFissionYieldHelper(FissionYieldHelper): self._fission_rate_tally.filters = [MaterialFilter(materials)] - def update_nuclides_from_operator(self, nuclides): + 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 - Nuclides with non-zero densities from the - :class:`openmc.deplete.Operator` + Potential nuclides to be tallied, such as those with + non-zero density at this stage. Returns ------- nuclides : tuple of str - Union of nuclides that the :class:`openmc.deplete.Operator` - says have non-zero densities at this stage and those that - have multiple sets of yield data. Sorted by nuclide name + Union of input nuclides and those that have multiple sets + of yield data. Sorted by nuclide name + + Raises + ------ + AttributeError + If tallies not generated """ + if self._fission_rate_tally is None: + raise AttributeError( + "Tallies not built. Run generate_tallies first") overlap = set(self._chain_nuclides).intersection(set(nuclides)) - if len(overlap) == 0: - if self._fission_rate_tally is None: - # No tally to update - return tuple() - # tally no nuclides, but keep the Tally alive - self._fission_rate_tally.nuclides = [] - self._tally_nucs = [] - return tuple() nuclides = tuple(sorted(overlap)) self._tally_nucs = [self._chain_nuclides[n] for n in nuclides] self._fission_rate_tally.nuclides = nuclides diff --git a/openmc/deplete/helpers.py b/openmc/deplete/helpers.py index 1336af7e69..aeafd412e8 100644 --- a/openmc/deplete/helpers.py +++ b/openmc/deplete/helpers.py @@ -519,7 +519,6 @@ class AveragedFissionYieldHelper(TalliedFissionYieldHelper): weighted_tally = Tally() weighted_tally.filters = fission_tally.filters.copy() - weighted_tally.nuclides = fission_tally.nuclides weighted_tally.scores = ['fission'] ene_bin = EnergyFilter() @@ -531,6 +530,32 @@ class AveragedFissionYieldHelper(TalliedFissionYieldHelper): weighted_tally.filters.append(ene_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: diff --git a/openmc/deplete/operator.py b/openmc/deplete/operator.py index 5b9d2fca67..b7a4d2c8f0 100644 --- a/openmc/deplete/operator.py +++ b/openmc/deplete/operator.py @@ -242,7 +242,7 @@ class Operator(TransportOperator): nuclides = self._get_tally_nuclides() self._rate_helper.nuclides = nuclides self._energy_helper.nuclides = nuclides - self._fsn_yield_helper.update_nuclides_from_operator(nuclides) + self._fsn_yield_helper.update_tally_nuclides(nuclides) # Run OpenMC openmc.capi.reset() diff --git a/tests/unit_tests/test_deplete_fission_yields.py b/tests/unit_tests/test_deplete_fission_yields.py index 6285fe5b9b..3b29aac96e 100644 --- a/tests/unit_tests/test_deplete_fission_yields.py +++ b/tests/unit_tests/test_deplete_fission_yields.py @@ -109,7 +109,7 @@ def test_cutoff_helper(nuclide_bundle, therm_frac): proxy = CutoffProxy(nuclide_bundle, n_bmats) proxy.generate_tallies(MATERIALS, [0]) non_zero_nucs = [n.name for n in nuclide_bundle] - tally_nucs = proxy.update_nuclides_from_operator(non_zero_nucs) + tally_nucs = proxy.update_tally_nuclides(non_zero_nucs) assert tally_nucs == ("U235",) # Emulate building tallies # material x energy, tallied_nuclides, 3 @@ -144,7 +144,7 @@ class AverageProxy(ProxyMixin, AveragedFissionYieldHelper): def test_averaged_helper(nuclide_bundle, avg_energy): proxy = AverageProxy(nuclide_bundle) proxy.generate_tallies(MATERIALS, [0]) - tallied_nucs = proxy.update_nuclides_from_operator( + tallied_nucs = proxy.update_tally_nuclides( [n.name for n in nuclide_bundle]) assert tallied_nucs == ("U235", ) # enforce some average energy From 0de7618f955a8e07a734ea7f257fc75b99163abc Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Mon, 19 Aug 2019 10:02:14 -0500 Subject: [PATCH 080/137] Use independent FP yields for Te129 in CASL chain --- scripts/casl_chain.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/scripts/casl_chain.py b/scripts/casl_chain.py index 61f5da9bc3..94fc9dd966 100755 --- a/scripts/casl_chain.py +++ b/scripts/casl_chain.py @@ -189,7 +189,7 @@ CASL_CHAIN = { 'Sb127': (False, 3, 2, None), 'Te127': (False, 3, -1, None), 'Te127_m1': (False, 3, -1, None), - 'Te129': (False, 3, 2, None), + 'Te129': (False, 3, 1, None), 'Te129_m1': (False, 3, 2, None), 'Te132': (False, 3, 2, None), 'I127': (True, 3, 1, None), From 32f3c27cf64b904d879db1fbd907868d131d8157 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Mon, 19 Aug 2019 10:07:20 -0500 Subject: [PATCH 081/137] Add minor changes from code review to deplete.Nuclide.validate --- openmc/deplete/chain.py | 3 ++- openmc/deplete/nuclide.py | 8 ++------ tests/unit_tests/test_deplete_nuclide.py | 2 +- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/openmc/deplete/chain.py b/openmc/deplete/chain.py index bfbfcc2bef..4706bfabb9 100644 --- a/openmc/deplete/chain.py +++ b/openmc/deplete/chain.py @@ -623,7 +623,7 @@ class Chain(object): 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 2? + fractions equal about two? Parameters ---------- @@ -639,6 +639,7 @@ class Chain(object): value ``x`` to intended value ``y`` as:: valid = (y - tolerance <= x <= y + tolerance) + Returns ------- valid : bool diff --git a/openmc/deplete/nuclide.py b/openmc/deplete/nuclide.py index 95af6d03de..538b697904 100644 --- a/openmc/deplete/nuclide.py +++ b/openmc/deplete/nuclide.py @@ -4,7 +4,6 @@ Contains the per-nuclide components of a depletion chain. """ from collections import namedtuple, defaultdict -from operator import attrgetter, itemgetter from warnings import warn try: import lxml.etree as ET @@ -266,10 +265,8 @@ class Nuclide(object): openmc.deplete.Chain.validate """ - branch_getter = attrgetter("branching_ratio") msg_func = ("Nuclide {name} has {prop} that sum to {actual} " "instead of {expected} +/- {tol:7.4e}").format - type_map = defaultdict(set) valid = True # check decay modes @@ -288,7 +285,7 @@ class Nuclide(object): valid = False if self.reactions: - type_map.clear() + type_map = defaultdict(set) for reaction in self.reactions: type_map[reaction.type].add(reaction) for rxn_type, reactions in type_map.items(): @@ -306,8 +303,7 @@ class Nuclide(object): warn(msg) valid = False - if self.yield_data > 0: - yield_getter = itemgetter(1) + if self.yield_data: for energy, yield_list in self.yield_data.items(): sum_yield = sum(y[1] for y in yield_list) stat = 2.0 - tolerance <= sum_yield <= 2.0 + tolerance diff --git a/tests/unit_tests/test_deplete_nuclide.py b/tests/unit_tests/test_deplete_nuclide.py index a93f6bb4c9..38848e9c19 100644 --- a/tests/unit_tests/test_deplete_nuclide.py +++ b/tests/unit_tests/test_deplete_nuclide.py @@ -176,7 +176,7 @@ def test_validate(): # restore reactions, invalidate fission yields nuc.reactions.append(reaction) - fission_yields = nuc.yield_data[1e6].pop() + nuc.yield_data[1e6].pop() with pytest.raises(ValueError, match=r"fission yields.*1\.0*e"): nuc.validate(strict=True, quiet=False, tolerance=0.0) From 37c871cbad673b125420c91ebf7059f675e1a9b1 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Mon, 19 Aug 2019 10:08:15 -0500 Subject: [PATCH 082/137] Add hyperlink to CASL depletion benchmark in scripts documentation https://doi.org/10.2172/1256820 --- docs/source/usersguide/scripts.rst | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/source/usersguide/scripts.rst b/docs/source/usersguide/scripts.rst index c97e7f1bd5..388130947c 100644 --- a/docs/source/usersguide/scripts.rst +++ b/docs/source/usersguide/scripts.rst @@ -127,8 +127,9 @@ do not exist, then ENDF/B-VII.1 data will be downloaded. 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. +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. From 68b3fd6ec8299cc903a3c4ce347a45175881fe32 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Mon, 19 Aug 2019 10:15:25 -0500 Subject: [PATCH 083/137] Improve file checking and reporting in openmc-make-depletion-chain --- scripts/openmc-make-depletion-chain | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/scripts/openmc-make-depletion-chain b/scripts/openmc-make-depletion-chain index 5ad0bc66a1..51092a7224 100755 --- a/scripts/openmc-make-depletion-chain +++ b/scripts/openmc-make-depletion-chain @@ -33,9 +33,8 @@ def main(): nfy_files = tuple((endf_dir / "nfy").glob("*endf")) # check files exist - for flist, ftype in zip( - (decay_files, neutron_files, nfy_files), - ("decay", "neutron", "nfy")): + 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)) From e37ff271c6326fde875619d8ff7cc3bc01c67995 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Tue, 20 Aug 2019 16:56:15 -0500 Subject: [PATCH 084/137] Generalize Chain.set_capture_branches -> set_branch_ratios Generalized method that allows the modification of branching ratios for an arbitrary reaction. A similar replacement has been made with the companion get_capture_branches -> get_branch_ratios. Both of the new methods accept an additional reaction argument, which defaults to "(n,gamma)", indicating what reaction to modify/retrieve. Also added a tolerance argument, defaults to 1e-5, for checking the sum of user-supplied branching ratios. This tolerance is used as 1 - tolerance < sum_br < 1 + tolerance. Additional logic is included if the user has not specified the ground state, which can be inferred. For this case, the lower end is allowed to be less than 1 - tolerance, as the ground state will have a branch ratio such the sum of ratios is unity. Products are not checked for consistency, that is left up to the user. tests/unit_test/test_deplete_chain.py has been modified with the new notation, and also with a variety of calls with and without the optional reaction argument. A minor test is added that modifies (n,alpha) and (n,2n) reactions just to ensure that arbitrary reactions can be used. Minor changes: * Internal variables changed to reflect the generality of the method * Added documentation on what exceptions are raised in set_branch_ratios Related: openmc issue #1237 --- openmc/deplete/chain.py | 138 +++++++++++++++++-------- tests/unit_tests/test_deplete_chain.py | 70 ++++++++++--- 2 files changed, 146 insertions(+), 62 deletions(-) diff --git a/openmc/deplete/chain.py b/openmc/deplete/chain.py index 1f16d9cafa..3b1870b97d 100644 --- a/openmc/deplete/chain.py +++ b/openmc/deplete/chain.py @@ -454,70 +454,96 @@ 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 = {} # Check for validity before manipulation @@ -543,11 +569,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: indexes.append(ix) if "_m" not in rx.target: grounds[parent] = rx.target @@ -555,24 +581,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 +622,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 +660,4 @@ 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)) diff --git a/tests/unit_tests/test_deplete_chain.py b/tests/unit_tests/test_deplete_chain.py index 33b13b596d..686c6d5080 100644 --- a/tests/unit_tests/test_deplete_chain.py +++ b/tests/unit_tests/test_deplete_chain.py @@ -248,29 +248,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(): @@ -289,9 +289,9 @@ def test_capture_branch_infer_ground(): 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(): @@ -307,9 +307,8 @@ def test_capture_branch_no_rxn(): 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 +317,51 @@ 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)") + + +@pytest.mark.parametrize("reaction", ("(n,alpha)", "(n,2n)")) +def test_set_general_branches(reaction): + """Test setting of non-capture branching ratios""" + # Build a mock chain + chain = Chain() + + parent = nuclide.Nuclide() + parent.name = "A" + + 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)): + chain.nuclides.append(nuc) + chain.nuclide_dict[nuc.name] = ix + + # add reactions to parent + parent.reactions.append(nuclide.ReactionTuple( + reaction, ground_tgt.name, 1.0, 0.6)) + parent.reactions.append(nuclide.ReactionTuple( + reaction, meta_tgt.name, 1.0, 0.4)) + + expected_ref = {"A": {"B": 0.6, "B_m1": 0.4}} + + assert chain.get_branch_ratios(reaction) == expected_ref + + # alter and check again + + altered = {"A": {"B": 0.5, "B_m1": 0.5}} + + chain.set_branch_ratios(altered, reaction) + assert chain.get_branch_ratios(reaction) == altered From 34add135be85bba0e4a3c1659f235198875ee18c Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Tue, 20 Aug 2019 17:19:53 -0500 Subject: [PATCH 085/137] Leave He4 alone in Chain.set_branch_ratios In an (n, alpha) reaction, the branching ratio for helium should always be one, as it is produced for every reaction. It is the branching ratio of the other targets that must be modified. He4 is treated as a secondary particle for this case, and its reaction is not modified nor removed from the parent. --- openmc/deplete/chain.py | 5 ++++- tests/unit_tests/test_deplete_chain.py | 30 ++++++++++++++++++-------- 2 files changed, 25 insertions(+), 10 deletions(-) diff --git a/openmc/deplete/chain.py b/openmc/deplete/chain.py index 3b1870b97d..b557a61116 100644 --- a/openmc/deplete/chain.py +++ b/openmc/deplete/chain.py @@ -545,6 +545,9 @@ class Chain(object): missing_reaction = set() bad_sums = {} + # Secondary products, like alpha particles, should not be modified + secondary = "He4" if reaction == "(n,a)" else None + # Check for validity before manipulation for parent, sub in branch_ratios.items(): @@ -573,7 +576,7 @@ class Chain(object): indexes = [] for ix, rx in enumerate(self[parent].reactions): - if rx.type == reaction: + if rx.type == reaction and rx.target != secondary: indexes.append(ix) if "_m" not in rx.target: grounds[parent] = rx.target diff --git a/tests/unit_tests/test_deplete_chain.py b/tests/unit_tests/test_deplete_chain.py index 686c6d5080..3cf726ed1a 100644 --- a/tests/unit_tests/test_deplete_chain.py +++ b/tests/unit_tests/test_deplete_chain.py @@ -330,38 +330,50 @@ def test_capture_branch_failures(simple_chain): simple_chain.set_branch_ratios(br, "(n,gamma)") -@pytest.mark.parametrize("reaction", ("(n,alpha)", "(n,2n)")) -def test_set_general_branches(reaction): - """Test setting of non-capture branching ratios""" + +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)): + 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( - reaction, ground_tgt.name, 1.0, 0.6)) + "(n,a)", ground_tgt.name, 1.0, 0.6)) parent.reactions.append(nuclide.ReactionTuple( - reaction, meta_tgt.name, 1.0, 0.4)) + "(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(reaction) == expected_ref + 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, reaction) - assert chain.get_branch_ratios(reaction) == altered + 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") From b767b2adb08731c55b786bf7d55fb6c8d335074b Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Wed, 21 Aug 2019 08:39:38 -0500 Subject: [PATCH 086/137] Change MPI_REAL8 -> MPI_DOUBLE in state_point.cpp (Thanks to Cliff Dugal, CNL) --- src/state_point.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 } From 2aa1849edaf01ef763a3da27355a4523a7a79ef7 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Wed, 21 Aug 2019 08:46:55 -0500 Subject: [PATCH 087/137] Apply suggestions from code review Co-Authored-By: Paul Romano --- openmc/deplete/chain.py | 6 +++--- openmc/deplete/helpers.py | 4 ++-- tests/unit_tests/test_deplete_chain.py | 2 +- tests/unit_tests/test_deplete_fission_yields.py | 2 +- tests/unit_tests/test_deplete_nuclide.py | 2 +- 5 files changed, 8 insertions(+), 8 deletions(-) diff --git a/openmc/deplete/chain.py b/openmc/deplete/chain.py index dd13a338be..27b73134b3 100644 --- a/openmc/deplete/chain.py +++ b/openmc/deplete/chain.py @@ -127,7 +127,7 @@ class Chain(object): 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: Real}}``, where ``yield`` is the fission product yield + 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. @@ -395,14 +395,14 @@ class Chain(object): Returns ------- fission_yields : dict - Dictionary of ``{parent : {product : f_yield}}`` + 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 len(nuc.yield_data) == 0: + if not nuc.yield_data: continue yield_obj = nuc.yield_data[min(nuc.yield_energies)] out[nuc.name] = dict(yield_obj) diff --git a/openmc/deplete/helpers.py b/openmc/deplete/helpers.py index aeafd412e8..a07630df76 100644 --- a/openmc/deplete/helpers.py +++ b/openmc/deplete/helpers.py @@ -317,7 +317,7 @@ class FissionYieldCutoffHelper(TalliedFissionYieldHelper): if fast is None: # find first index <= cutoff rev_ix = self._find_fallback_energy( - name, list(reversed(energies)), cutoff, False) + name, reversed(energies), cutoff, False) fast = yields[energies[-rev_ix]] self._thermal_yields[name] = thermal self._fast_yields[name] = fast @@ -518,7 +518,7 @@ class AveragedFissionYieldHelper(TalliedFissionYieldHelper): fission_tally = self._fission_rate_tally weighted_tally = Tally() - weighted_tally.filters = fission_tally.filters.copy() + weighted_tally.filters = fission_tally.filters weighted_tally.scores = ['fission'] ene_bin = EnergyFilter() diff --git a/tests/unit_tests/test_deplete_chain.py b/tests/unit_tests/test_deplete_chain.py index 98ca023f00..7a5a128450 100644 --- a/tests/unit_tests/test_deplete_chain.py +++ b/tests/unit_tests/test_deplete_chain.py @@ -129,7 +129,7 @@ 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].products == ("A", "B") assert (nuc.yield_data[0.0253].yields == [0.0292737, 0.002566345]).all() diff --git a/tests/unit_tests/test_deplete_fission_yields.py b/tests/unit_tests/test_deplete_fission_yields.py index 3b29aac96e..547f34c227 100644 --- a/tests/unit_tests/test_deplete_fission_yields.py +++ b/tests/unit_tests/test_deplete_fission_yields.py @@ -91,7 +91,7 @@ def test_cutoff_failure(key): FissionYieldCutoffHelper(None, None, **{key: -1}) -class ProxyMixin(object): +class ProxyMixin: """Mixing that overloads the tally generation""" def generate_tallies(self, materials, mat_indexes): self._fission_rate_tally = Mock() diff --git a/tests/unit_tests/test_deplete_nuclide.py b/tests/unit_tests/test_deplete_nuclide.py index ef5c3d1ca7..2cfb98e6ca 100644 --- a/tests/unit_tests/test_deplete_nuclide.py +++ b/tests/unit_tests/test_deplete_nuclide.py @@ -76,7 +76,7 @@ def test_from_xml(): 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 == (0.0253,) assert u235.yield_energies is u235.yield_data.energies with pytest.raises(AttributeError): # not settable u235.yield_energies = [0.0253, 5e5] From 1d046ab4f62eec2c699999fb921c893240b98257 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Wed, 21 Aug 2019 09:34:44 -0500 Subject: [PATCH 088/137] Support kwargs for FissionYieldHelper.from_operator Applied to concrete classes AveragedFissionYieldHelper, FissionYieldCutoffHelper, ConstantFissionYieldHelper. --- openmc/deplete/abc.py | 17 +++++++------- openmc/deplete/helpers.py | 49 +++++++++++++++++++-------------------- 2 files changed, 32 insertions(+), 34 deletions(-) diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py index da9bb5244e..7cccad3695 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -371,13 +371,11 @@ class FissionYieldHelper(ABC): Parameters ---------- chain_nuclides : iterable of openmc.deplete.Nuclide - Nuclides tracked in the depletion chain. Not necessary - that all have yield data. + Nuclides tracked in the depletion chain. All nuclides are + not required to have fission yield data. Attributes ---------- - n_bmats : int - Number of burnable materials tracked in the problem 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}}`` @@ -473,13 +471,15 @@ class FissionYieldHelper(ABC): 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 - Optional keyword arguments to be passed to the underlying - ``__init__`` method + Additional keyword arguments to be used in constuction """ return cls(operator.chain.nuclides, **kwargs) @@ -560,9 +560,8 @@ class TalliedFissionYieldHelper(FissionYieldHelper): AttributeError If tallies not generated """ - if self._fission_rate_tally is None: - raise AttributeError( - "Tallies not built. Run generate_tallies first") + assert self._fission_rate_tally is not None, ( + "Run generate_tallies first") overlap = set(self._chain_nuclides).intersection(set(nuclides)) nuclides = tuple(sorted(overlap)) self._tally_nucs = [self._chain_nuclides[n] for n in nuclides] diff --git a/openmc/deplete/helpers.py b/openmc/deplete/helpers.py index a07630df76..ced3a2523a 100644 --- a/openmc/deplete/helpers.py +++ b/openmc/deplete/helpers.py @@ -166,8 +166,8 @@ class ConstantFissionYieldHelper(FissionYieldHelper): Parameters ---------- chain_nuclides : iterable of openmc.deplete.Nuclide - Nuclides tracked in the depletion chain. Not necessary - that all have yield data. + 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 @@ -204,22 +204,24 @@ class ConstantFissionYieldHelper(FissionYieldHelper): nuc.yield_data[nuc.yield_energies[min_index]]) @classmethod - def from_operator(cls, operator, energy=0.0253): + 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 - energy : float, optional - Energy for default fission yield libraries for nuclides with - multiple sets of yield data + kwargs: + Additional keyword arguments to be used in construction Returns ------- ConstantFissionYieldHelper """ - return cls(operator.chain.nuclides, energy=energy) + return cls(operator.chain.nuclides, **kwargs) @property def energy(self): @@ -255,8 +257,8 @@ class FissionYieldCutoffHelper(TalliedFissionYieldHelper): Parameters ---------- chain_nuclides : iterable of openmc.deplete.Nuclide - Nuclides tracked in the depletion chain. Not necessary - that all have yield data. + 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 @@ -323,25 +325,18 @@ class FissionYieldCutoffHelper(TalliedFissionYieldHelper): self._fast_yields[name] = fast @classmethod - def from_operator(cls, operator, cutoff=112.0, - thermal_energy=0.0253, fast_energy=500e3): + def from_operator(cls, operator, **kwargs): """Construct a helper from an operator - All keyword arguments are identical to their counterpart + 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 - cutoff : float, optional - Cutoff energy for tallying fast and thermal fissions - thermal_energy : float, optional - Energy to use when pulling thermal fission yields from - nuclides with multiple sets of yields - fast_energy : float, optional - Energy to use when pulling fast fission yields from - nuclides with multiple sets of yields + kwargs: + Additional keyword arguments to be used in construction Returns ------- @@ -349,8 +344,7 @@ class FissionYieldCutoffHelper(TalliedFissionYieldHelper): """ return cls(operator.chain.nuclides, len(operator.burnable_mats), - cutoff=cutoff, thermal_energy=thermal_energy, - fast_energy=fast_energy) + **kwargs) @staticmethod def _find_fallback_energy(name, energies, cutoff, check_under): @@ -481,8 +475,8 @@ class AveragedFissionYieldHelper(TalliedFissionYieldHelper): Parameters ---------- chain_nuclides : iterable of openmc.deplete.Nuclide - Nuclides tracked in the depletion chain. Not necessary - that all have yield data. + Nuclides tracked in the depletion chain. All nuclides are + not required to have fission yield data. Attributes ---------- @@ -615,13 +609,18 @@ class AveragedFissionYieldHelper(TalliedFissionYieldHelper): return mat_yields @classmethod - def from_operator(cls, operator): + 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 ------- From 6735657cb33e028e647104e19859f59d6f77cf10 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Wed, 21 Aug 2019 09:45:09 -0500 Subject: [PATCH 089/137] Don't append to Tally filters on FY helpers As pointed out in https://github.com/openmc-dev/openmc/pull/1313/files#r315844775, appending to Tally.filters does not propagate to the C++ side. New filter lists are created using the previous lists, and then passed onto to the Tally.filters setter. --- openmc/deplete/helpers.py | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/openmc/deplete/helpers.py b/openmc/deplete/helpers.py index ced3a2523a..cc8b63c0fa 100644 --- a/openmc/deplete/helpers.py +++ b/openmc/deplete/helpers.py @@ -378,9 +378,9 @@ class FissionYieldCutoffHelper(TalliedFissionYieldHelper): in parallel mode. """ super().generate_tallies(materials, mat_indexes) - energy_filter = EnergyFilter() - energy_filter.bins = (0.0, self._cutoff, self._upper_energy) - self._fission_rate_tally.filters.append(energy_filter) + energy_filter = EnergyFilter(bins=[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""" @@ -510,18 +510,16 @@ class AveragedFissionYieldHelper(TalliedFissionYieldHelper): """ super().generate_tallies(materials, mat_indexes) fission_tally = self._fission_rate_tally + filters = fission_tally.filters + ene_filter = EnergyFilter(bins=[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.filters = fission_tally.filters weighted_tally.scores = ['fission'] - - ene_bin = EnergyFilter() - ene_bin.bins = (0, self._upper_energy) - fission_tally.filters.append(ene_bin) - - ene_filter = EnergyFunctionFilter() - ene_filter.set_data((0, self._upper_energy), (0, self._upper_energy)) - weighted_tally.filters.append(ene_filter) + weighted_tally.filters = filters + [func_filter] self._weighted_tally = weighted_tally def update_tally_nuclides(self, nuclides): From b19a2db0eef3e5cab2b252e15b3f9bbd806ebec4 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Wed, 21 Aug 2019 10:05:59 -0500 Subject: [PATCH 090/137] Respond to reviewer comments on FY helpers - Change Operator attribute name for fission yield helper - Use copy.deepcopy for constant_yields, fast_yields, and thermal_yields - Change import location of openmc in tests - Use bisect.bisect_left to find replacement fission yields on FissionYieldCutoffHelper if thermal or fast energies not found --- openmc/deplete/abc.py | 5 +- openmc/deplete/chain.py | 11 ++-- openmc/deplete/helpers.py | 55 +++++++------------ openmc/deplete/operator.py | 20 +++---- .../unit_tests/test_deplete_fission_yields.py | 5 +- tests/unit_tests/test_deplete_nuclide.py | 1 - 6 files changed, 37 insertions(+), 60 deletions(-) diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py index 7cccad3695..17ed33e854 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -396,10 +396,7 @@ class FissionYieldHelper(ABC): @property def constant_yields(self): - out = {} - for key, sub in self._constant_yields.items(): - out[key] = sub.copy() - return out + return deepcopy(self._constant_yields) @abstractmethod def weighted_yields(self, local_mat_index): diff --git a/openmc/deplete/chain.py b/openmc/deplete/chain.py index 27b73134b3..ae1c33f194 100644 --- a/openmc/deplete/chain.py +++ b/openmc/deplete/chain.py @@ -663,11 +663,8 @@ class Chain(object): @fission_yields.setter def fission_yields(self, yields): - if yields is None: - self._fission_yields = None - return - if isinstance(yields, Mapping): - self._fission_yields = [yields] - return - check_type("fission_yields", yields, Iterable, Mapping) + if yields is not None: + if isinstance(yields, Mapping): + yields = [yields] + check_type("fission_yields", yields, Iterable, Mapping) self._fission_yields = yields diff --git a/openmc/deplete/helpers.py b/openmc/deplete/helpers.py index cc8b63c0fa..37c8075d48 100644 --- a/openmc/deplete/helpers.py +++ b/openmc/deplete/helpers.py @@ -1,9 +1,10 @@ """ Class for normalizing fission energy deposition """ +from copy import deepcopy from itertools import product from numbers import Real -import operator +import bisect from numpy import dot, zeros, newaxis @@ -200,8 +201,8 @@ class ConstantFissionYieldHelper(FissionYieldHelper): distances = [abs(energy - ene) for ene in nuc.yield_energies] min_index = min( range(len(nuc.yield_energies)), key=distances.__getitem__) - self._constant_yields[name] = ( - nuc.yield_data[nuc.yield_energies[min_index]]) + 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): @@ -310,17 +311,21 @@ class FissionYieldCutoffHelper(TalliedFissionYieldHelper): yields = nuc.yield_data energies = nuc.yield_energies thermal = yields.get(thermal_energy) - if thermal is None: - # find first index >= cutoff - ix = self._find_fallback_energy( - name, energies, cutoff, True) - thermal = yields[energies[ix - 1]] fast = yields.get(fast_energy) - if fast is None: - # find first index <= cutoff - rev_ix = self._find_fallback_energy( - name, reversed(energies), cutoff, False) - fast = yields[energies[-rev_ix]] + if thermal is None or fast is None: + insert_ix = bisect.bisect_left(energies, cutoff) + # if zero, then all energies > cutoff + # if len(energies), then all energies <= cutoff + if insert_ix == 0 or insert_ix == len(energies): + tail = map("{:5.3e}".format, energies) + raise ValueError( + "Cannot find replacement fission yields for {} given " + "cutoff {:5.3e} eV. Yields provided at [{}] eV".format( + name, cutoff, ", ".join(tail))) + if thermal is None: + thermal = yields[energies[insert_ix - 1]] + if fast is None: + fast = yields[energies[insert_ix]] self._thermal_yields[name] = thermal self._fast_yields[name] = fast @@ -346,20 +351,6 @@ class FissionYieldCutoffHelper(TalliedFissionYieldHelper): return cls(operator.chain.nuclides, len(operator.burnable_mats), **kwargs) - @staticmethod - def _find_fallback_energy(name, energies, cutoff, check_under): - cutoff_func = operator.ge if check_under else operator.le - found = False - for ix, ene in enumerate(energies): - if cutoff_func(ene, cutoff): - found = True - break - if found and ix != 0: - return ix - domain = "thermal" if check_under else "fast" - raise ValueError("Could not find {} yields for {} " - "with cutoff {} eV".format(domain, name, cutoff)) - def generate_tallies(self, materials, mat_indexes): """Use C API to produce a fission rate tally in burnable materials @@ -435,17 +426,11 @@ class FissionYieldCutoffHelper(TalliedFissionYieldHelper): @property def thermal_yields(self): - out = {} - for key, sub in self._thermal_yields.items(): - out[key] = sub.copy() - return out + return deepcopy(self._thermal_yields) @property def fast_yields(self): - out = {} - for key, sub in self._fast_yields.items(): - out[key] = sub.copy() - return out + return deepcopy(self._fast_yields) class AveragedFissionYieldHelper(TalliedFissionYieldHelper): diff --git a/openmc/deplete/operator.py b/openmc/deplete/operator.py index b7a4d2c8f0..43523b0fe1 100644 --- a/openmc/deplete/operator.py +++ b/openmc/deplete/operator.py @@ -137,7 +137,7 @@ class Operator(TransportOperator): diff_burnable_mats : bool Whether to differentiate burnable materials with multiple instances """ - _fission_helpers_ = { + _fission_helpers = { "average": AveragedFissionYieldHelper, "constant": ConstantFissionYieldHelper, "cutoff": FissionYieldCutoffHelper, @@ -147,10 +147,10 @@ class Operator(TransportOperator): diff_burnable_mats=False, fission_q=None, dilute_initial=1.0e3, fission_yield_mode="constant", fission_yield_opts=None): - if fission_yield_mode not in self._fission_helpers_: + 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)) + ", ".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 @@ -207,10 +207,10 @@ class Operator(TransportOperator): self._energy_helper = ChainFissionHelper() # Select and create fission yield helper - fission_helper = self._fission_helpers_[fission_yield_mode] + fission_helper = self._fission_helpers[fission_yield_mode] fission_yield_opts = ( {} if fission_yield_opts is None else fission_yield_opts) - self._fsn_yield_helper = fission_helper.from_operator( + self._yield_helper = fission_helper.from_operator( self, **fission_yield_opts) def __call__(self, vec, power): @@ -242,7 +242,7 @@ class Operator(TransportOperator): nuclides = self._get_tally_nuclides() self._rate_helper.nuclides = nuclides self._energy_helper.nuclides = nuclides - self._fsn_yield_helper.update_tally_nuclides(nuclides) + self._yield_helper.update_tally_nuclides(nuclides) # Run OpenMC openmc.capi.reset() @@ -448,8 +448,8 @@ class Operator(TransportOperator): self.chain.nuclides, self.reaction_rates.index_nuc, materials) # Tell fission yield helper what materials this process is # responsible for - self._fsn_yield_helper.generate_tallies( - materials, tuple(sorted(self._mat_index_map.values()))) + 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_[:])) @@ -595,7 +595,7 @@ class Operator(TransportOperator): # Keep track of energy produced from all reactions in eV per source # particle self._energy_helper.reset() - self._fsn_yield_helper.unpack() + self._yield_helper.unpack() # Store fission yield dictionaries fission_yields = [] @@ -622,7 +622,7 @@ class Operator(TransportOperator): mat_index, nuc_ind, react_ind) # Compute fission yields for this material - fission_yields.append(self._fsn_yield_helper.weighted_yields(i)) + fission_yields.append(self._yield_helper.weighted_yields(i)) # Accumulate energy from fission self._energy_helper.update(tally_rates[:, fission_ind], mat_index) diff --git a/tests/unit_tests/test_deplete_fission_yields.py b/tests/unit_tests/test_deplete_fission_yields.py index 547f34c227..2ef7c90b93 100644 --- a/tests/unit_tests/test_deplete_fission_yields.py +++ b/tests/unit_tests/test_deplete_fission_yields.py @@ -5,7 +5,6 @@ from unittest.mock import Mock import pytest import numpy - from openmc.deplete.nuclide import Nuclide, FissionYieldDistribution from openmc.deplete.helpers import ( FissionYieldCutoffHelper, ConstantFissionYieldHelper, @@ -75,10 +74,10 @@ def test_cutoff_construction(nuclide_bundle): "U235": nuclide_bundle.u235.yield_data[0.0253]} assert helper.fast_yields == {"U235": nuclide_bundle.u235.yield_data[5e5]} # test failures in cutoff: super low, super high - with pytest.raises(ValueError, match="thermal yields"): + with pytest.raises(ValueError, match="replacement fission yields"): FissionYieldCutoffHelper( nuclide_bundle, 1, thermal_energy=0.001, cutoff=0.002) - with pytest.raises(ValueError, match="fast yields"): + with pytest.raises(ValueError, match="replacement fission yields"): FissionYieldCutoffHelper( nuclide_bundle, 1, cutoff=15e6, fast_energy=17e6) diff --git a/tests/unit_tests/test_deplete_nuclide.py b/tests/unit_tests/test_deplete_nuclide.py index 2cfb98e6ca..f1dfa4f770 100644 --- a/tests/unit_tests/test_deplete_nuclide.py +++ b/tests/unit_tests/test_deplete_nuclide.py @@ -4,7 +4,6 @@ import xml.etree.ElementTree as ET import numpy import pytest - from openmc.deplete import nuclide From 4fd59d1f8d57e3d23c511ae0b5123aeec1e017be Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 15 Aug 2019 09:28:55 -0500 Subject: [PATCH 091/137] Update score_current test --- .../score_current/geometry.xml | 181 -- .../score_current/inputs_true.dat | 55 + .../score_current/materials.xml | 270 --- .../score_current/results_true.dat | 1949 ++++++++++++++++- .../score_current/settings.xml | 18 - .../score_current/tallies.xml | 31 - tests/regression_tests/score_current/test.py | 53 +- 7 files changed, 2053 insertions(+), 504 deletions(-) delete mode 100644 tests/regression_tests/score_current/geometry.xml create mode 100644 tests/regression_tests/score_current/inputs_true.dat delete mode 100644 tests/regression_tests/score_current/materials.xml delete mode 100644 tests/regression_tests/score_current/settings.xml delete mode 100644 tests/regression_tests/score_current/tallies.xml 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() From f3ace1a5e2105910113e635f9b61c639da7f0fb6 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 15 Aug 2019 11:50:37 -0500 Subject: [PATCH 092/137] Update tally_assumpsep test --- .../tally_assumesep/geometry.xml | 184 +----------- .../tally_assumesep/materials.xml | 267 +----------------- .../tally_assumesep/results_true.dat | 14 +- .../tally_assumesep/settings.xml | 10 +- .../tally_assumesep/tallies.xml | 8 +- 5 files changed, 25 insertions(+), 458 deletions(-) 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..1e1c5492af 100644 --- a/tests/regression_tests/tally_assumesep/tallies.xml +++ b/tests/regression_tests/tally_assumesep/tallies.xml @@ -1,21 +1,21 @@ - true + false cell - 21 + 1 cell - 22 + 2 cell - 23 + 3 From 961c5a025bd92204118bdc6c4b359dddc69df9fd Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 15 Aug 2019 12:18:01 -0500 Subject: [PATCH 093/137] Update lattice_multiple test --- .../lattice_multiple/geometry.xml | 181 ------------ .../lattice_multiple/inputs_true.dat | 53 ++++ .../lattice_multiple/materials.xml | 270 ------------------ .../lattice_multiple/results_true.dat | 2 +- .../lattice_multiple/settings.xml | 18 -- .../regression_tests/lattice_multiple/test.py | 62 +++- 6 files changed, 113 insertions(+), 473 deletions(-) delete mode 100644 tests/regression_tests/lattice_multiple/geometry.xml create mode 100644 tests/regression_tests/lattice_multiple/inputs_true.dat delete mode 100644 tests/regression_tests/lattice_multiple/materials.xml delete mode 100644 tests/regression_tests/lattice_multiple/settings.xml 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..a9b15c9c85 100644 --- a/tests/regression_tests/lattice_multiple/test.py +++ b/tests/regression_tests/lattice_multiple/test.py @@ -1,6 +1,62 @@ -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) + c1 = openmc.Cell(fill=uo2, region=-cyl) + c2 = openmc.Cell(fill=water, region=+cyl) + pin = openmc.Universe(cells=[c1, c2]) + c3 = openmc.Cell(fill=uo2, region=-big_cyl) + c4 = openmc.Cell(fill=water, region=+big_cyl) + big_pin = openmc.Universe(cells=[c3, c4]) + + 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() From fa11bacde75a8a93d63b4e53109d306cf1c59d56 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 15 Aug 2019 12:43:10 -0500 Subject: [PATCH 094/137] Update void test (allowing fixed source tests to work with PyAPITestHarness too) --- tests/regression_tests/void/geometry.xml | 40 ------ tests/regression_tests/void/inputs_true.dat | 132 +++++++++++++++++++ tests/regression_tests/void/materials.xml | 61 --------- tests/regression_tests/void/results_true.dat | 105 ++++++++++++++- tests/regression_tests/void/settings.xml | 16 --- tests/regression_tests/void/test.py | 39 +++++- tests/testing_harness.py | 10 +- 7 files changed, 277 insertions(+), 126 deletions(-) delete mode 100644 tests/regression_tests/void/geometry.xml create mode 100644 tests/regression_tests/void/inputs_true.dat delete mode 100644 tests/regression_tests/void/materials.xml delete mode 100644 tests/regression_tests/void/settings.xml 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): From 7bc96c822c14d36a233ca375817e5e6f86eaabce Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 15 Aug 2019 15:45:35 -0500 Subject: [PATCH 095/137] Update tally_aggregation test --- .../tally_aggregation/inputs_true.dat | 316 ++---------------- .../tally_aggregation/results_true.dat | 98 +++++- .../tally_aggregation/test.py | 77 +++-- 3 files changed, 176 insertions(+), 315 deletions(-) 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..8914ac2a8e 100644 --- a/tests/regression_tests/tally_aggregation/test.py +++ b/tests/regression_tests/tally_aggregation/test.py @@ -1,26 +1,63 @@ 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) + c1 = openmc.Cell(fill=fuel, region=-cyl) + c2 = openmc.Cell(fill=water, region=+cyl) + pin = openmc.Universe(cells=[c1, c2]) + 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(c1) + 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 +89,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() From e1d2a9d802a469919b1749afcbfc11b17af2a7b1 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Mon, 19 Aug 2019 09:34:01 -0500 Subject: [PATCH 096/137] Update tally_arithmetic test --- .../tally_arithmetic/inputs_true.dat | 327 ++---------------- .../tally_arithmetic/results_true.dat | 188 +++------- .../regression_tests/tally_arithmetic/test.py | 114 +++--- 3 files changed, 140 insertions(+), 489 deletions(-) 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() From c245f78148cb2534f5e1058f2da751c514fc5125 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Mon, 19 Aug 2019 09:49:41 -0500 Subject: [PATCH 097/137] Update mgxs_library_mesh test --- .../mgxs_library_mesh/inputs_true.dat | 318 +---------- .../mgxs_library_mesh/results_true.dat | 504 +++++++++--------- .../mgxs_library_mesh/test.py | 100 ++-- 3 files changed, 336 insertions(+), 586 deletions(-) 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() From 92433b255c397db3cd8da41222fd9f022f896b22 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Mon, 19 Aug 2019 09:56:21 -0500 Subject: [PATCH 098/137] Update filter_mesh test --- .../filter_mesh/inputs_true.dat | 334 ++---------------- .../filter_mesh/results_true.dat | 2 +- tests/regression_tests/filter_mesh/test.py | 151 ++++---- 3 files changed, 117 insertions(+), 370 deletions(-) diff --git a/tests/regression_tests/filter_mesh/inputs_true.dat b/tests/regression_tests/filter_mesh/inputs_true.dat index 6755a64c82..919347d69c 100644 --- a/tests/regression_tests/filter_mesh/inputs_true.dat +++ b/tests/regression_tests/filter_mesh/inputs_true.dat @@ -1,332 +1,56 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 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 + -10.0 + 10.0 17 17 - -182.07 -182.07 - 182.07 182.07 + -10.0 -10.0 + 10.0 10.0 17 17 17 - -182.07 -182.07 -183.0 - 182.07 182.07 183.0 + -10.0 -10.0 -183.0 + 10.0 10.0 183.0 - -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 + -10.0 -8.823529411764707 -7.647058823529411 -6.470588235294118 -5.294117647058823 -4.117647058823529 -2.9411764705882355 -1.7647058823529402 -0.5882352941176467 0.5882352941176467 1.764705882352942 2.9411764705882355 4.117647058823529 5.294117647058824 6.4705882352941195 7.647058823529413 8.823529411764707 10.0 + -10.0 -8.823529411764707 -7.647058823529411 -6.470588235294118 -5.294117647058823 -4.117647058823529 -2.9411764705882355 -1.7647058823529402 -0.5882352941176467 0.5882352941176467 1.764705882352942 2.9411764705882355 4.117647058823529 5.294117647058824 6.4705882352941195 7.647058823529413 8.823529411764707 10.0 1.0 1.683624003879018 2.8345897864376153 4.772383405596668 8.034899257376447 13.52774925846868 22.77564337001445 38.34561988154435 64.55960607618856 108.69410247084474 182.99999999999991 diff --git a/tests/regression_tests/filter_mesh/results_true.dat b/tests/regression_tests/filter_mesh/results_true.dat index 05efffe2fb..55653d1a57 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 +2bc8fce4a61bc431e90c44400ac626e7043144b575eeb5ca66b8c4fefb9bed076b20663d096801dca63085f090a96e662f12b6281c85c3ba8ce73efbc55356ba \ 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..e68c5f0e16 100644 --- a/tests/regression_tests/filter_mesh/test.py +++ b/tests/regression_tests/filter_mesh/test.py @@ -1,87 +1,110 @@ 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) + # Initialize Meshes + mesh_1d = openmc.RegularMesh() + mesh_1d.dimension = [17] + mesh_1d.lower_left = [-10.0] + mesh_1d.upper_right = [10.0] - # 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 = [17, 17] + mesh_2d.lower_left = [-10.0, -10.0] + mesh_2d.upper_right = [10.0, 10.0] - # Initialized the tallies - tally = openmc.Tally(name='tally 1') - tally.filters = [mesh_1d_filter] - tally.scores = ['total'] - self._model.tallies.append(tally) + mesh_3d = openmc.RegularMesh() + mesh_3d.dimension = [17, 17, 17] + mesh_3d.lower_left = [-10.0, -10.0, -183.00] + mesh_3d.upper_right = [10.0, 10.0, 183.00] - tally = openmc.Tally(name='tally 2') - tally.filters = [meshsurf_1d_filter] - tally.scores = ['current'] - self._model.tallies.append(tally) + recti_mesh = openmc.RectilinearMesh() + recti_mesh.x_grid = np.linspace(-10.0, 10.0, 18) + recti_mesh.y_grid = np.linspace(-10.0, 10.0, 18) + recti_mesh.z_grid = np.logspace(0, np.log10(183), 11) - tally = openmc.Tally(name='tally 3') - tally.filters = [mesh_2d_filter] - tally.scores = ['total'] - self._model.tallies.append(tally) + # 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) - tally = openmc.Tally(name='tally 4') - tally.filters = [meshsurf_2d_filter] - tally.scores = ['current'] - self._model.tallies.append(tally) + # Initialized the tallies + tally = openmc.Tally(name='tally 1') + tally.filters = [mesh_1d_filter] + tally.scores = ['total'] + 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 2') + tally.filters = [meshsurf_1d_filter] + tally.scores = ['current'] + 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 3') + tally.filters = [mesh_2d_filter] + tally.scores = ['total'] + 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 4') + tally.filters = [meshsurf_2d_filter] + tally.scores = ['current'] + model.tallies.append(tally) - tally = openmc.Tally(name='tally 8') - tally.filters = [recti_meshsurf_filter] - tally.scores = ['current'] - self._model.tallies.append(tally) + tally = openmc.Tally(name='tally 5') + tally.filters = [mesh_3d_filter] + tally.scores = ['total'] + model.tallies.append(tally) + + tally = openmc.Tally(name='tally 6') + tally.filters = [meshsurf_3d_filter] + tally.scores = ['current'] + model.tallies.append(tally) + + tally = openmc.Tally(name='tally 7') + tally.filters = [recti_mesh_filter] + tally.scores = ['total'] + model.tallies.append(tally) + + tally = openmc.Tally(name='tally 8') + tally.filters = [recti_meshsurf_filter] + tally.scores = ['current'] + 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() From 36bfa08eae44423a10c60ca213d8e0e7b640b024 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Mon, 19 Aug 2019 10:03:19 -0500 Subject: [PATCH 099/137] Update filter_energyfun test --- .../filter_energyfun/inputs_true.dat | 307 +----------------- .../filter_energyfun/results_true.dat | 2 +- .../regression_tests/filter_energyfun/test.py | 73 +++-- 3 files changed, 53 insertions(+), 329 deletions(-) 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() From da18820a88a69d864948074d5dca53bbb566f280 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Mon, 19 Aug 2019 14:35:48 -0500 Subject: [PATCH 100/137] Add a nuclide with Maxwell fission spectrum to energy_laws test --- .../regression_tests/energy_laws/geometry.xml | 5 --- .../energy_laws/inputs_true.dat | 23 ++++++++++ .../energy_laws/materials.xml | 10 ----- .../energy_laws/results_true.dat | 2 +- .../regression_tests/energy_laws/settings.xml | 10 ----- tests/regression_tests/energy_laws/test.py | 42 +++++++++++++++---- 6 files changed, 59 insertions(+), 33 deletions(-) delete mode 100644 tests/regression_tests/energy_laws/geometry.xml create mode 100644 tests/regression_tests/energy_laws/inputs_true.dat delete mode 100644 tests/regression_tests/energy_laws/materials.xml delete mode 100644 tests/regression_tests/energy_laws/settings.xml 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() From 37c0fff7ed6f3b0f9fdf91d5f109f24bd571d4af Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Mon, 19 Aug 2019 14:57:38 -0500 Subject: [PATCH 101/137] Update filter_mesh test to cover a few cases in mesh.cpp that were missed --- .../filter_mesh/inputs_true.dat | 40 ++++---- .../filter_mesh/results_true.dat | 2 +- tests/regression_tests/filter_mesh/test.py | 98 +++++++------------ 3 files changed, 57 insertions(+), 83 deletions(-) diff --git a/tests/regression_tests/filter_mesh/inputs_true.dat b/tests/regression_tests/filter_mesh/inputs_true.dat index 919347d69c..37716c02e4 100644 --- a/tests/regression_tests/filter_mesh/inputs_true.dat +++ b/tests/regression_tests/filter_mesh/inputs_true.dat @@ -34,24 +34,24 @@ - 17 - -10.0 - 10.0 + 5 + -7.5 + 7.5 - 17 17 - -10.0 -10.0 - 10.0 10.0 + 5 5 + -7.5 -7.5 + 7.5 7.5 - 17 17 17 - -10.0 -10.0 -183.0 - 10.0 10.0 183.0 + 5 5 5 + -7.5 -7.5 -7.5 + 7.5 7.5 7.5 - -10.0 -8.823529411764707 -7.647058823529411 -6.470588235294118 -5.294117647058823 -4.117647058823529 -2.9411764705882355 -1.7647058823529402 -0.5882352941176467 0.5882352941176467 1.764705882352942 2.9411764705882355 4.117647058823529 5.294117647058824 6.4705882352941195 7.647058823529413 8.823529411764707 10.0 - -10.0 -8.823529411764707 -7.647058823529411 -6.470588235294118 -5.294117647058823 -4.117647058823529 -2.9411764705882355 -1.7647058823529402 -0.5882352941176467 0.5882352941176467 1.764705882352942 2.9411764705882355 4.117647058823529 5.294117647058824 6.4705882352941195 7.647058823529413 8.823529411764707 10.0 - 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 @@ -77,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 55653d1a57..10ea99f646 100644 --- a/tests/regression_tests/filter_mesh/results_true.dat +++ b/tests/regression_tests/filter_mesh/results_true.dat @@ -1 +1 @@ -2bc8fce4a61bc431e90c44400ac626e7043144b575eeb5ca66b8c4fefb9bed076b20663d096801dca63085f090a96e662f12b6281c85c3ba8ce73efbc55356ba \ 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 e68c5f0e16..c8aa871a83 100644 --- a/tests/regression_tests/filter_mesh/test.py +++ b/tests/regression_tests/filter_mesh/test.py @@ -30,77 +30,51 @@ def model(): model.settings.inactive = 0 model.settings.particles = 1000 - # Initialize Meshes + # Create meshes mesh_1d = openmc.RegularMesh() - mesh_1d.dimension = [17] - mesh_1d.lower_left = [-10.0] - mesh_1d.upper_right = [10.0] + mesh_1d.dimension = [5] + mesh_1d.lower_left = [-7.5] + mesh_1d.upper_right = [7.5] mesh_2d = openmc.RegularMesh() - mesh_2d.dimension = [17, 17] - mesh_2d.lower_left = [-10.0, -10.0] - mesh_2d.upper_right = [10.0, 10.0] + mesh_2d.dimension = [5, 5] + mesh_2d.lower_left = [-7.5, -7.5] + mesh_2d.upper_right = [7.5, 7.5] mesh_3d = openmc.RegularMesh() - mesh_3d.dimension = [17, 17, 17] - mesh_3d.lower_left = [-10.0, -10.0, -183.00] - mesh_3d.upper_right = [10.0, 10.0, 183.00] + 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(-10.0, 10.0, 18) - recti_mesh.y_grid = np.linspace(-10.0, 10.0, 18) - recti_mesh.z_grid = np.logspace(0, np.log10(183), 11) + 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) - # 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) + # 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) + ] - # Initialized the tallies - tally = openmc.Tally(name='tally 1') - tally.filters = [mesh_1d_filter] - tally.scores = ['total'] - model.tallies.append(tally) - - tally = openmc.Tally(name='tally 2') - tally.filters = [meshsurf_1d_filter] - tally.scores = ['current'] - model.tallies.append(tally) - - tally = openmc.Tally(name='tally 3') - tally.filters = [mesh_2d_filter] - tally.scores = ['total'] - model.tallies.append(tally) - - tally = openmc.Tally(name='tally 4') - tally.filters = [meshsurf_2d_filter] - tally.scores = ['current'] - model.tallies.append(tally) - - tally = openmc.Tally(name='tally 5') - tally.filters = [mesh_3d_filter] - tally.scores = ['total'] - model.tallies.append(tally) - - tally = openmc.Tally(name='tally 6') - tally.filters = [meshsurf_3d_filter] - tally.scores = ['current'] - model.tallies.append(tally) - - tally = openmc.Tally(name='tally 7') - tally.filters = [recti_mesh_filter] - tally.scores = ['total'] - model.tallies.append(tally) - - tally = openmc.Tally(name='tally 8') - tally.filters = [recti_meshsurf_filter] - tally.scores = ['current'] - model.tallies.append(tally) + # Create tallies + for f1, f2 in zip(reg_filters, surf_filters): + tally = openmc.Tally() + tally.filters = [f1] + tally.scores = ['total'] + model.tallies.append(tally) + tally = openmc.Tally() + tally.filters = [f2] + tally.scores = ['current'] + model.tallies.append(tally) return model From f97796211306197194d21e5b79209f94d963a2a9 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Wed, 21 Aug 2019 10:44:28 -0500 Subject: [PATCH 102/137] Support passing nuclide name to Nuclide objects Allow the creation of a Nuclide with >>> n = Nuclide("U235") --- openmc/deplete/chain.py | 3 +-- openmc/deplete/nuclide.py | 14 +++++++++----- tests/unit_tests/test_deplete_chain.py | 15 +++++---------- tests/unit_tests/test_deplete_fission_yields.py | 9 +++------ tests/unit_tests/test_deplete_nuclide.py | 3 +-- 5 files changed, 19 insertions(+), 25 deletions(-) diff --git a/openmc/deplete/chain.py b/openmc/deplete/chain.py index ae1c33f194..d590cd8d2c 100644 --- a/openmc/deplete/chain.py +++ b/openmc/deplete/chain.py @@ -207,8 +207,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 diff --git a/openmc/deplete/nuclide.py b/openmc/deplete/nuclide.py index d2b0acc169..d4f501d442 100644 --- a/openmc/deplete/nuclide.py +++ b/openmc/deplete/nuclide.py @@ -68,11 +68,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]. @@ -91,12 +96,11 @@ class Nuclide(object): 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 diff --git a/tests/unit_tests/test_deplete_chain.py b/tests/unit_tests/test_deplete_chain.py index 7a5a128450..cf6414b5cc 100644 --- a/tests/unit_tests/test_deplete_chain.py +++ b/tests/unit_tests/test_deplete_chain.py @@ -141,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), @@ -150,14 +149,12 @@ 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), @@ -292,8 +289,7 @@ 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 @@ -310,8 +306,7 @@ 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 diff --git a/tests/unit_tests/test_deplete_fission_yields.py b/tests/unit_tests/test_deplete_fission_yields.py index 2ef7c90b93..4b71eba696 100644 --- a/tests/unit_tests/test_deplete_fission_yields.py +++ b/tests/unit_tests/test_deplete_fission_yields.py @@ -20,17 +20,14 @@ def nuclide_bundle(): 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.name = "U235" + u235 = Nuclide("U235") u235.yield_data = FissionYieldDistribution.from_dict(u5yield_dict) u8yield_dict = {5.00e5: {"Xe135": 1.12e-3, "Gd155": 1.32e-12}} - u238 = Nuclide() - u238.name = "U238" + u238 = Nuclide("U238") u238.yield_data = FissionYieldDistribution.from_dict(u8yield_dict) - xe135 = Nuclide() - xe135.name = "Xe135" + xe135 = Nuclide("Xe135") NuclideBundle = namedtuple("NuclideBundle", "u235 u238 xe135") return NuclideBundle(u235, u238, xe135) diff --git a/tests/unit_tests/test_deplete_nuclide.py b/tests/unit_tests/test_deplete_nuclide.py index f1dfa4f770..4310478c96 100644 --- a/tests/unit_tests/test_deplete_nuclide.py +++ b/tests/unit_tests/test_deplete_nuclide.py @@ -84,8 +84,7 @@ def test_from_xml(): 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), From 60f89794f684a30a5aed43868deef8e8b45fd847 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Wed, 21 Aug 2019 10:55:43 -0500 Subject: [PATCH 103/137] Use dictionary to create FissionYieldDistribution Removes the FissionYieldDistribution.from_dict method as the most natural way to create such a distribution is with a dictionary. --- openmc/deplete/chain.py | 13 ++- openmc/deplete/nuclide.py | 79 ++++++------------- tests/unit_tests/test_deplete_chain.py | 2 +- .../unit_tests/test_deplete_fission_yields.py | 4 +- tests/unit_tests/test_deplete_nuclide.py | 6 +- 5 files changed, 38 insertions(+), 66 deletions(-) diff --git a/openmc/deplete/chain.py b/openmc/deplete/chain.py index d590cd8d2c..ba959a3fcc 100644 --- a/openmc/deplete/chain.py +++ b/openmc/deplete/chain.py @@ -10,7 +10,6 @@ import math import re from collections import OrderedDict, defaultdict from collections.abc import Mapping, Iterable -from numbers import Real from warnings import warn from openmc.checkvalue import check_type, check_less_than @@ -127,8 +126,8 @@ class Chain(object): 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``. + 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 @@ -222,7 +221,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 @@ -285,7 +285,7 @@ class Chain(object): 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 @@ -297,8 +297,7 @@ class Chain(object): missing_fp.append((parent, E, yield_replace)) yield_data[E] = yields - nuclide.yield_data = ( - FissionYieldDistribution.from_dict(yield_data)) + nuclide.yield_data = FissionYieldDistribution(yield_data) # Display warnings if missing_daughter: diff --git a/openmc/deplete/nuclide.py b/openmc/deplete/nuclide.py index d4f501d442..a3c60019e3 100644 --- a/openmc/deplete/nuclide.py +++ b/openmc/deplete/nuclide.py @@ -3,16 +3,15 @@ Contains the per-nuclide components of a depletion chain. """ -from numbers import Real from collections import namedtuple -from collections.abc import Iterable, Mapping +from collections.abc import Mapping try: import lxml.etree as ET except ImportError: import xml.etree.ElementTree as ET -from numpy import asarray, empty +from numpy import empty from openmc.checkvalue import check_type @@ -132,7 +131,7 @@ class Nuclide(object): check_type("fission_yields", fission_yields, Mapping) if isinstance(fission_yields, FissionYieldDistribution): self._yield_data = fission_yields - self._yield_data = FissionYieldDistribution.from_dict(fission_yields) + self._yield_data = FissionYieldDistribution(fission_yields) @property def yield_energies(self): @@ -245,7 +244,7 @@ class FissionYieldDistribution(Mapping): Can be used as a dictionary mapping energies and products to fission yields:: - >>> fydist = FissionYieldDistribution.from_dict({ + >>> fydist = FissionYieldDistribution{ ... {0.0253: {"Xe135": 0.021}}) >>> fydist[0.0253]["Xe135"] 0.021 @@ -266,11 +265,10 @@ class FissionYieldDistribution(Mapping): Attributes ---------- energies : tuple - Energies for which fission yields exist. Converted for - indexing + Energies for which fission yields exist. Sorted by + increasing energy products : tuple - Fission products produced at all energies. Converted - for indexing + 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 @@ -278,22 +276,28 @@ class FissionYieldDistribution(Mapping): See Also -------- - * :meth:`from_xml_element`, :meth:`from_dict` - Construction methods + * :meth:`from_xml_element` - Construction methods * :class:`FissionYield` - Class used for storing yields at a given energy """ - def __init__(self, energies, products, yield_matrix): - check_type("energies", energies, Iterable, Real) + 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) - check_type("products", products, Iterable, str) - self.products = tuple(products) - yield_matrix = asarray(yield_matrix, dtype=float) - if yield_matrix.shape != (len(self.energies), len(self.products)): - raise ValueError( - "Shape of yield matrix inconsistent. " - "Should be ({}, {}), is {}".format( - len(energies), len(products), - yield_matrix.shape)) + self.products = tuple(ordered_prod) self.yield_matrix = yield_matrix def __len__(self): @@ -329,38 +333,7 @@ class FissionYieldDistribution(Mapping): # Get a map of products to their corresponding yield all_yields[energy] = dict(zip(products, yields)) - return cls.from_dict(all_yields) - - @classmethod - def from_dict(cls, fission_yields): - """Construct a distribution from a dictionary of yields - - Parameters - ----------- - fission_yields : dict - Dictionary ``{energy: {product: yield}}`` - - Returns - ------- - FissionYieldDistribution - """ - # 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) - - return cls(energies, ordered_prod, yield_matrix) + return cls(all_yields) def to_xml_element(self, root): """Write fission yield data to an xml element diff --git a/tests/unit_tests/test_deplete_chain.py b/tests/unit_tests/test_deplete_chain.py index cf6414b5cc..b1ab1ae248 100644 --- a/tests/unit_tests/test_deplete_chain.py +++ b/tests/unit_tests/test_deplete_chain.py @@ -160,7 +160,7 @@ def test_export_to_xml(run_in_tmpdir): nuclide.ReactionTuple("(n,gamma)", "A", 0.0, 0.7), nuclide.ReactionTuple("(n,gamma)", "B", 0.0, 0.3) ] - C.yield_data = nuclide.FissionYieldDistribution.from_dict({ + C.yield_data = nuclide.FissionYieldDistribution({ 0.0253: {"A": 0.0292737, "B": 0.002566345}}) chain = Chain() diff --git a/tests/unit_tests/test_deplete_fission_yields.py b/tests/unit_tests/test_deplete_fission_yields.py index 4b71eba696..81c3e2d420 100644 --- a/tests/unit_tests/test_deplete_fission_yields.py +++ b/tests/unit_tests/test_deplete_fission_yields.py @@ -21,11 +21,11 @@ def nuclide_bundle(): 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.from_dict(u5yield_dict) + 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.from_dict(u8yield_dict) + u238.yield_data = FissionYieldDistribution(u8yield_dict) xe135 = Nuclide("Xe135") diff --git a/tests/unit_tests/test_deplete_nuclide.py b/tests/unit_tests/test_deplete_nuclide.py index 4310478c96..3386e28984 100644 --- a/tests/unit_tests/test_deplete_nuclide.py +++ b/tests/unit_tests/test_deplete_nuclide.py @@ -71,7 +71,7 @@ def test_from_xml(): nuclide.ReactionTuple('(n,gamma)', 'U236', 6545200.0, 1.0), nuclide.ReactionTuple('fission', None, 193405400.0, 1.0), ] - expected_yield_data = nuclide.FissionYieldDistribution.from_dict({ + 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 @@ -94,7 +94,7 @@ def test_to_xml_element(): nuclide.ReactionTuple('fission', None, 2.0e8, 1.0), nuclide.ReactionTuple('(n,gamma)', 'A', 0.0, 1.0) ] - C.yield_data = nuclide.FissionYieldDistribution.from_dict( + C.yield_data = nuclide.FissionYieldDistribution( {0.0253: {"A": 0.0292737, "B": 0.002566345}}) element = C.to_xml_element() @@ -127,7 +127,7 @@ def test_fission_yield_distribution(): 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.from_dict(yield_dict) + 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(): From 687d287b243aa0ac45e3eddd9a424519312d3202 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Wed, 21 Aug 2019 11:34:54 -0500 Subject: [PATCH 104/137] Allow Nuclide.yield_data to be None Had to guard against some cases where it was assumed that yield_data would be a dictionary. For the most part, the fission yield helpers act on nuclides that have yield data already. The previous default state was to use an empty dictionary. This has been removed. A value of None for nuclide.yield_data indicates there is no yield data. --- openmc/deplete/abc.py | 2 ++ openmc/deplete/chain.py | 2 +- openmc/deplete/nuclide.py | 14 +++++++++----- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py index 17ed33e854..16d2d7abbd 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -387,6 +387,8 @@ class FissionYieldHelper(ABC): # 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]]) diff --git a/openmc/deplete/chain.py b/openmc/deplete/chain.py index ba959a3fcc..e38385ac52 100644 --- a/openmc/deplete/chain.py +++ b/openmc/deplete/chain.py @@ -400,7 +400,7 @@ class Chain(object): """ out = {} for nuc in self.nuclides: - if not nuc.yield_data: + if nuc.yield_data is None: continue yield_obj = nuc.yield_data[min(nuc.yield_energies)] out[nuc.name] = dict(yield_obj) diff --git a/openmc/deplete/nuclide.py b/openmc/deplete/nuclide.py index a3c60019e3..068891a5f0 100644 --- a/openmc/deplete/nuclide.py +++ b/openmc/deplete/nuclide.py @@ -123,15 +123,19 @@ class Nuclide(object): @property def yield_data(self): if self._yield_data is None: - return {} + return None return self._yield_data @yield_data.setter def yield_data(self, fission_yields): - check_type("fission_yields", fission_yields, Mapping) - if isinstance(fission_yields, FissionYieldDistribution): - self._yield_data = fission_yields - self._yield_data = FissionYieldDistribution(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): From 8e2bb0bbbc99d6caf93b2ca1cdcc047d7fbe8346 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Wed, 21 Aug 2019 17:20:19 -0500 Subject: [PATCH 105/137] Improve testing for FY helpers using C API Provide a module-scoped fixture that creates a temporary directory, adds simple settings, geometry, and material files, and initializes the openmc C API. This fixture also provides two empty openmc.capi.Material objects to help the helpers generate meaningful tallies. The main tests for AveragedFissionYieldHelper and FissionYieldCutoffHelper now go through the built tallies and examine the filters, nuclides, and scores. A helper function produces mocked-like tally data based on filters, nuclides, and scores found on a tally. Pu239 with 0.0253 eV, 500 keV, and 2 MeV yields is included in the nuclide_bundle fixture. This provides some additional heterogeneity in the results, as the 2 MeV data is not present on U235. --- openmc/deplete/helpers.py | 17 +- .../unit_tests/test_deplete_fission_yields.py | 297 +++++++++++++----- 2 files changed, 230 insertions(+), 84 deletions(-) diff --git a/openmc/deplete/helpers.py b/openmc/deplete/helpers.py index 37c8075d48..debc10403e 100644 --- a/openmc/deplete/helpers.py +++ b/openmc/deplete/helpers.py @@ -313,19 +313,24 @@ class FissionYieldCutoffHelper(TalliedFissionYieldHelper): thermal = yields.get(thermal_energy) fast = yields.get(fast_energy) if thermal is None or fast is None: - insert_ix = bisect.bisect_left(energies, cutoff) + cutoff_ix = bisect.bisect_left(energies, cutoff) # if zero, then all energies > cutoff # if len(energies), then all energies <= cutoff - if insert_ix == 0 or insert_ix == len(energies): + if cutoff_ix == 0 or cutoff_ix == len(energies): tail = map("{:5.3e}".format, energies) raise ValueError( "Cannot find replacement fission yields for {} given " "cutoff {:5.3e} eV. Yields provided at [{}] eV".format( name, cutoff, ", ".join(tail))) + # find closest energy to requested thermal, fast energies if thermal is None: - thermal = yields[energies[insert_ix - 1]] + min_E = min(energies[:cutoff_ix], + key=lambda e: abs(e - thermal_energy)) + thermal = yields[min_E] if fast is None: - fast = yields[energies[insert_ix]] + 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 @@ -369,7 +374,7 @@ class FissionYieldCutoffHelper(TalliedFissionYieldHelper): in parallel mode. """ super().generate_tallies(materials, mat_indexes) - energy_filter = EnergyFilter(bins=[0.0, self._cutoff, self._upper_energy]) + energy_filter = EnergyFilter([0.0, self._cutoff, self._upper_energy]) self._fission_rate_tally.filters = ( self._fission_rate_tally.filters + [energy_filter]) @@ -497,7 +502,7 @@ class AveragedFissionYieldHelper(TalliedFissionYieldHelper): fission_tally = self._fission_rate_tally filters = fission_tally.filters - ene_filter = EnergyFilter(bins=[0, self._upper_energy]) + ene_filter = EnergyFilter([0, self._upper_energy]) fission_tally.filters = filters + [ene_filter] func_filter = EnergyFunctionFilter() diff --git a/tests/unit_tests/test_deplete_fission_yields.py b/tests/unit_tests/test_deplete_fission_yields.py index 81c3e2d420..05f62eaceb 100644 --- a/tests/unit_tests/test_deplete_fission_yields.py +++ b/tests/unit_tests/test_deplete_fission_yields.py @@ -1,17 +1,97 @@ """Test the FissionYieldHelpers""" +import os from collections import namedtuple from unittest.mock import Mock +import bisect import pytest import numpy +from openmc import capi from openmc.deplete.nuclide import Nuclide, FissionYieldDistribution from openmc.deplete.helpers import ( FissionYieldCutoffHelper, ConstantFissionYieldHelper, AveragedFissionYieldHelper) -MATERIALS = ["1", "2"] +@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 xml files to please openmc + with open("geometry.xml", "w") as stream: + stream.write(""" + + + + + + + + +""") + with open("settings.xml", "w") as stream: + stream.write(""" + + + eigenvalue + 100 + 10 + 0 + 1 + + + 0.0 0.0 0.0 + + + +""") + with open("materials.xml", "w") as stream: + stream.write(""" + + + + + + + + + +""") + try: + with capi.run_in_memory(): + yield [capi.Material(), capi.Material()] + finally: + print(os.path.abspath(os.curdir)) + os.remove(tmpdir / "settings.xml") + os.remove(tmpdir / "geometry.xml") + os.remove(tmpdir / "materials.xml") + os.remove(tmpdir / "summary.h5") + orig.chdir() + os.rmdir(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 = numpy.empty((n_bins, n_nucs * n_scores, 3)) + if fill is not None: + data.fill(fill) + return data @pytest.fixture(scope="module") @@ -29,47 +109,74 @@ def nuclide_bundle(): xe135 = Nuclide("Xe135") - NuclideBundle = namedtuple("NuclideBundle", "u235 u238 xe135") - return NuclideBundle(u235, u238, xe135) + pu239 = Nuclide("Pu239") + pu239.yield_data = FissionYieldDistribution({ + 0.0253: {"Xe135": 3.141e-3, "Sm149": 8.19e-10, "Gd155": 1.66e-9}, + 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, u5_yield_energy", + "input_energy, yield_energy", ((0.0253, 0.0253), (0.01, 0.0253), (4e5, 5e5))) -def test_constant_helper(nuclide_bundle, input_energy, u5_yield_energy): +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[u5_yield_energy], - "U238": nuclide_bundle.u238.yield_data[5.00e5]} # only epithermal + "U235": nuclide_bundle.u235.yield_data[yield_energy], + "U238": nuclide_bundle.u238.yield_data[5.00e5], # only epithermal + "Pu239": nuclide_bundle.pu239.yield_data[yield_energy]} 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": nuclide_bundle.u238.yield_data[5.0e5]} + "U238": u238.yield_data[5.0e5]} assert helper.thermal_yields == { - "U235": nuclide_bundle.u235.yield_data[0.0253]} - assert helper.fast_yields == {"U235": nuclide_bundle.u235.yield_data[5e5]} + "U235": u235.yield_data[0.0253], + "Pu239": pu239.yield_data[0.0253]} + assert helper.fast_yields == { + "U235": u235.yield_data[5e5], + "Pu239": pu239.yield_data[5e5]} + # use 14 MeV yields helper = FissionYieldCutoffHelper(nuclide_bundle, 1, fast_energy=14e6) assert helper.constant_yields == { - "U238": nuclide_bundle.u238.yield_data[5.0e5]} + "U238": u238.yield_data[5.0e5]} assert helper.thermal_yields == { - "U235": nuclide_bundle.u235.yield_data[0.0253]} - assert helper.fast_yields == {"U235": nuclide_bundle.u235.yield_data[14e6]} + "U235": u235.yield_data[0.0253], + "Pu239": pu239.yield_data[0.0253]} + assert helper.fast_yields == { + "U235": u235.yield_data[14e6], + "Pu239": pu239.yield_data[2e6]} + # specify missing thermal yields -> use 0.0253 helper = FissionYieldCutoffHelper(nuclide_bundle, 1, thermal_energy=1) assert helper.thermal_yields == { - "U235": nuclide_bundle.u235.yield_data[0.0253]} - assert helper.fast_yields == {"U235": nuclide_bundle.u235.yield_data[5e5]} + "U235": u235.yield_data[0.0253], + "Pu239": pu239.yield_data[0.0253]} + assert helper.fast_yields == { + "U235": u235.yield_data[5e5], + "Pu239": pu239.yield_data[5e5]} + # request missing fast yields -> use epithermal helper = FissionYieldCutoffHelper(nuclide_bundle, 1, fast_energy=1e4) assert helper.thermal_yields == { - "U235": nuclide_bundle.u235.yield_data[0.0253]} - assert helper.fast_yields == {"U235": nuclide_bundle.u235.yield_data[5e5]} + "U235": u235.yield_data[0.0253], + "Pu239": pu239.yield_data[0.0253]} + assert helper.fast_yields == { + "U235": u235.yield_data[5e5], + "Pu239": pu239.yield_data[5e5]} + # test failures in cutoff: super low, super high with pytest.raises(ValueError, match="replacement fission yields"): FissionYieldCutoffHelper( @@ -87,83 +194,117 @@ def test_cutoff_failure(key): FissionYieldCutoffHelper(None, None, **{key: -1}) -class ProxyMixin: - """Mixing that overloads the tally generation""" - def generate_tallies(self, materials, mat_indexes): - self._fission_rate_tally = Mock() - self._local_indexes = numpy.asarray(mat_indexes) - - -class CutoffProxy(ProxyMixin, FissionYieldCutoffHelper): - """Proxy that supplies a set of tallies""" - - # emulate some split between fast and thermal U235 fissions @pytest.mark.parametrize("therm_frac", (0.5, 0.2, 0.8)) -def test_cutoff_helper(nuclide_bundle, therm_frac): - n_bmats = len(MATERIALS) - proxy = CutoffProxy(nuclide_bundle, n_bmats) - proxy.generate_tallies(MATERIALS, [0]) +def test_cutoff_helper(materials, nuclide_bundle, therm_frac): + helper = FissionYieldCutoffHelper(nuclide_bundle, len(materials)) + helper.generate_tallies(materials, [0]) + non_zero_nucs = [n.name for n in nuclide_bundle] - tally_nucs = proxy.update_tally_nuclides(non_zero_nucs) - assert tally_nucs == ("U235",) + 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 - proxy_flux = 1e6 - tally_data = numpy.empty((n_bmats * 2, 1, 3)) - tally_data[0, 0, 1] = therm_frac * proxy_flux - tally_data[1, 0, 1] = (1 - therm_frac) * proxy_flux - proxy._fission_rate_tally.results = tally_data + 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 - proxy.unpack() + helper.unpack() # expected results of shape (n_mats, 2, n_tnucs) - expected_results = numpy.empty((1, 2, 1)) + expected_results = numpy.empty((1, 2, len(tally_nucs))) expected_results[:, 0] = therm_frac expected_results[:, 1] = 1 - therm_frac - assert proxy.results == pytest.approx(expected_results) + assert helper.results == pytest.approx(expected_results) - actual_yields = proxy.weighted_yields(0) + actual_yields = helper.weighted_yields(0) assert actual_yields["U238"] == nuclide_bundle.u238.yield_data[5e5] - assert actual_yields["U235"] == ( - proxy.thermal_yields["U235"] * therm_frac - + proxy.fast_yields["U235"] * (1 - therm_frac)) + for nuc in tally_nucs: + assert actual_yields[nuc] == ( + helper.thermal_yields[nuc] * therm_frac + + helper.fast_yields[nuc] * (1 - therm_frac)) -class AverageProxy(ProxyMixin, AveragedFissionYieldHelper): - """Proxy for generating mock set of tallies""" - def generate_tallies(self, materials, mat_indexes): - super().generate_tallies(materials, mat_indexes) - self._weighted_tally = Mock() - - -@pytest.mark.parametrize("avg_energy", (0.01, 100, 15e6)) -def test_averaged_helper(nuclide_bundle, avg_energy): - proxy = AverageProxy(nuclide_bundle) - proxy.generate_tallies(MATERIALS, [0]) - tallied_nucs = proxy.update_tally_nuclides( +@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 == ("U235", ) - # enforce some average energy - proxy_flux = 1e16 - fission_results = numpy.ones((len(MATERIALS), 1, 3)) * proxy_flux - weighted_results = fission_results * avg_energy - proxy._fission_rate_tally.results = fission_results - proxy._weighted_tally.results = weighted_results - proxy.unpack() - expected_results = numpy.ones((1, 1)) * avg_energy - assert proxy.results == pytest.approx(expected_results) + assert tallied_nucs == ("Pu239", "U235") - actual_yields = proxy.weighted_yields(0) + # 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 = numpy.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 - if avg_energy < 0.0253: # take thermal U235 yields - exp_u235_yields = nuclide_bundle.u235.yield_data[0.0253] - elif avg_energy > 14e6: # take fastest U235 yields - exp_u235_yields = nuclide_bundle.u235.yield_data[14e6] - else: # reconstruct between thermal and epithermal - thermal = nuclide_bundle.u235.yield_data[0.0253] - epithermal = nuclide_bundle.u235.yield_data[5e5] - split = (avg_energy - 0.0253) / (5e5 - 0.0253) - exp_u235_yields = thermal * (1 - split) + epithermal * split + 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 From 4f46aa895c5abb84ef4827a75709e529d86d03f3 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Wed, 21 Aug 2019 17:46:51 -0500 Subject: [PATCH 106/137] Teach deplete.Nuclide.validate about new fission yields Reconfigure tests/unit_tests/test_deplete_chain.py and tests/unit_tests/test_deplete_nuclide.py to use the new dictionary-like representation of fission yields. --- openmc/deplete/nuclide.py | 4 ++-- tests/unit_tests/test_deplete_chain.py | 2 +- tests/unit_tests/test_deplete_nuclide.py | 6 +++--- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/openmc/deplete/nuclide.py b/openmc/deplete/nuclide.py index db503c8a59..a989bf7029 100644 --- a/openmc/deplete/nuclide.py +++ b/openmc/deplete/nuclide.py @@ -321,8 +321,8 @@ class Nuclide(object): valid = False if self.yield_data: - for energy, yield_list in self.yield_data.items(): - sum_yield = sum(y[1] for y in yield_list) + 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 diff --git a/tests/unit_tests/test_deplete_chain.py b/tests/unit_tests/test_deplete_chain.py index ce45b84d27..bdaf6c5ab1 100644 --- a/tests/unit_tests/test_deplete_chain.py +++ b/tests/unit_tests/test_deplete_chain.py @@ -377,7 +377,7 @@ def test_validate(simple_chain): # 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)]} + 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: diff --git a/tests/unit_tests/test_deplete_nuclide.py b/tests/unit_tests/test_deplete_nuclide.py index d81691932b..6f5356b129 100644 --- a/tests/unit_tests/test_deplete_nuclide.py +++ b/tests/unit_tests/test_deplete_nuclide.py @@ -177,8 +177,8 @@ def test_validate(): # fission yields nuc.yield_data = { - 0.0253: [("0", 1.5), ("1", 0.5)], - 1e6: [("0", 1.5), ("1", 0.5)], + 0.0253: {"0": 1.5, "1": 0.5}, + 1e6: {"0": 1.5, "1": 0.5}, } # nuclide is good and should have no warnings raise @@ -212,7 +212,7 @@ def test_validate(): # restore reactions, invalidate fission yields nuc.reactions.append(reaction) - nuc.yield_data[1e6].pop() + 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) From 9287db4a2636bc725050b8f49b427e493c165f0b Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Thu, 22 Aug 2019 09:41:39 -0500 Subject: [PATCH 107/137] Support more secondary nuclides in Chain.set_branch_ratios Reactions are pulled from reaction scores table on https://docs.openmc.org/en/latest/usersguide/tallies.html#scores for reactions that produce hydrogen and helium isotopes --- openmc/deplete/chain.py | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/openmc/deplete/chain.py b/openmc/deplete/chain.py index b557a61116..3cb3dbf010 100644 --- a/openmc/deplete/chain.py +++ b/openmc/deplete/chain.py @@ -12,7 +12,7 @@ from collections import OrderedDict, defaultdict from collections.abc import Mapping from warnings import warn -from openmc.checkvalue import check_type, check_less_than +from openmc.checkvalue import check_type from openmc.data import gnd_name, zam # Try to use lxml if it is available. It preserves the order of attributes and @@ -103,6 +103,19 @@ def replace_missing(product, decay_data): return product +_secondary_particles = { + "p": ["H1"], "d": ["H2"], "t": ["H3"], "3He": ["He3"], "a": ["He4"], + "2nd": ["H2"], "na": ["He4"], "n3a": ["He4"] * 3, "2na": ["He4"], + "3na": ["He4"], "np": ["H1"], "n2a": ["He4"] * 2, + "2n2a": ["He4"] * 2, "nd": ["H2"], "nt": ["H3"], + "nHe-3": ["He3"], "nd2a": ["H2", "He4"], "nt2a": ["H3", "He4", "He4"], + "2np": ["H1"], "3np": ["H1"], "n2p": ["H1"] * 2, + "2a": ["He4"] * 2, "3a": ["He4"] * 3, "2p": ["H1"] * 2, + "pa": ["H1", "He4"], "t2a": ["H3", "He4", "He4"], + "d2a": ["H2", "He4", "He4"], "pd": ["H1", "H2"], "pt": ["H1", "H3"], + "da": ["H2", "He4"]} + + class Chain(object): """Full representation of a depletion chain. @@ -122,7 +135,6 @@ 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. - """ def __init__(self): @@ -546,7 +558,8 @@ class Chain(object): bad_sums = {} # Secondary products, like alpha particles, should not be modified - secondary = "He4" if reaction == "(n,a)" else None + secondary = _secondary_particles.get( + reaction[reaction.index(",") + 1:-1], []) # Check for validity before manipulation @@ -576,7 +589,7 @@ class Chain(object): indexes = [] for ix, rx in enumerate(self[parent].reactions): - if rx.type == reaction and rx.target != secondary: + if rx.type == reaction and rx.target not in secondary: indexes.append(ix) if "_m" not in rx.target: grounds[parent] = rx.target From dbd24035a348a2c1d4898e3a9373f9893c4452bc Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Tue, 20 Aug 2019 18:51:52 -0500 Subject: [PATCH 108/137] Refactor depletion unit tests with parametrization Removed unit test files for test_deplete_*py that pertained to a single integrator type only. Similarly removed tests where the integrators are used to perform simple restart analysis. These tests are still present, but done with two parametrized tests. Each of the integrators has the exact same solution in the unit tests with and without restarting,by nature of their design. This and the near-fixture like way the tests are structured allowed these tests to be easily parametrized provided 1) integrator, 2) reference solutions for atoms 1 and 2. Exact results for each integrator in the unit test are contained inside a collections.namedtuple, along with the Integrator class. These named tuples are placed in a dictionary in tests/dummy_operator.py that can be easily iterated over to provide access to a name of the scheme, e.g. "predictor", and the tuple of integrator, results for atom 1, and results for atom 2. Exact results for atoms 1 and 2 should be provided using the depletion matrix produced by the tests.dummy_operator.DummyOperator for two time steps of 0.75 seconds. --- tests/dummy_operator.py | 75 ++++- tests/unit_tests/test_deplete_cecm.py | 44 --- tests/unit_tests/test_deplete_celi.py | 43 --- tests/unit_tests/test_deplete_cf4.py | 43 --- tests/unit_tests/test_deplete_epc_rk4.py | 43 --- tests/unit_tests/test_deplete_integrator.py | 35 +- tests/unit_tests/test_deplete_leqi.py | 43 --- tests/unit_tests/test_deplete_predictor.py | 44 --- tests/unit_tests/test_deplete_restart.py | 347 ++------------------ tests/unit_tests/test_deplete_si_celi.py | 43 --- tests/unit_tests/test_deplete_si_leqi.py | 43 --- 11 files changed, 136 insertions(+), 667 deletions(-) delete mode 100644 tests/unit_tests/test_deplete_cecm.py delete mode 100644 tests/unit_tests/test_deplete_celi.py delete mode 100644 tests/unit_tests/test_deplete_cf4.py delete mode 100644 tests/unit_tests/test_deplete_epc_rk4.py delete mode 100644 tests/unit_tests/test_deplete_leqi.py delete mode 100644 tests/unit_tests/test_deplete_predictor.py delete mode 100644 tests/unit_tests/test_deplete_si_celi.py delete mode 100644 tests/unit_tests/test_deplete_si_leqi.py diff --git a/tests/dummy_operator.py b/tests/dummy_operator.py index c23abef631..37269b7bd6 100644 --- a/tests/dummy_operator.py +++ b/tests/dummy_operator.py @@ -1,9 +1,76 @@ +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 DummyOperator(TransportOperator): @@ -21,6 +88,7 @@ class DummyOperator(TransportOperator): """ def __init__(self, previous_results=None): self.prev_res = previous_results + self.output_dir = "." def __call__(self, vec, power, print_out=False): """Evaluates F(y) @@ -76,7 +144,6 @@ class DummyOperator(TransportOperator): 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) @@ -106,7 +173,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"] @@ -153,7 +221,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/unit_tests/test_deplete_cecm.py b/tests/unit_tests/test_deplete_cecm.py deleted file mode 100644 index 65dd61643b..0000000000 --- a/tests/unit_tests/test_deplete_cecm.py +++ /dev/null @@ -1,44 +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 -from openmc.deplete import CECMIntegrator, ResultsList - -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 - integrator = CECMIntegrator(op, dt, power) - integrator.integrate() - - # Load the files - res = ResultsList.from_hdf5(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 01ec60d372..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 -from openmc.deplete import ResultsList, CELIIntegrator - -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 - CELIIntegrator(op, dt, power).integrate() - - # Load the files - res = ResultsList.from_hdf5(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 817dfb5e17..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 -from openmc.deplete import CF4Integrator, ResultsList - -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 - CF4Integrator(op, dt, power).integrate() - - # Load the files - res = ResultsList.from_hdf5(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_epc_rk4.py b/tests/unit_tests/test_deplete_epc_rk4.py deleted file mode 100644 index 0c656d7dbe..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 -from openmc.deplete import EPCRK4Integrator, ResultsList - -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 - EPCRK4Integrator(op, dt, power).integrate() - - # Load the files - res = ResultsList.from_hdf5(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_integrator.py b/tests/unit_tests/test_deplete_integrator.py index fd4e2ba3ff..47c9690ae9 100644 --- a/tests/unit_tests/test_deplete_integrator.py +++ b/tests/unit_tests/test_deplete_integrator.py @@ -7,7 +7,6 @@ will be left unimplemented and testing will be done via regression. """ import copy -import os from unittest.mock import MagicMock import numpy as np @@ -18,6 +17,8 @@ from openmc.deplete import ( ReactionRates, Results, ResultsList, comm, OperatorResult, PredictorIntegrator, SICELIIntegrator) +from tests import dummy_operator + def test_results_save(run_in_tmpdir): """Test data save module""" @@ -41,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 = [] @@ -130,3 +132,30 @@ def test_bad_integrator_inputs(timesteps): 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 113cb6e306..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 -from openmc.deplete import LEQIIntegrator, ResultsList - -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 - LEQIIntegrator(op, dt, power).integrate() - - # Load the files - res = ResultsList.from_hdf5(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_predictor.py b/tests/unit_tests/test_deplete_predictor.py deleted file mode 100644 index 38bee86c39..0000000000 --- a/tests/unit_tests/test_deplete_predictor.py +++ /dev/null @@ -1,44 +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 -from openmc.deplete import PredictorIntegrator, ResultsList - -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 - PredictorIntegrator(op, dt, power).integrate() - - - # Load the files - res = ResultsList.from_hdf5(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 c55f7fd0f4..82ef05ac7b 100644 --- a/tests/unit_tests/test_deplete_restart.py +++ b/tests/unit_tests/test_deplete_restart.py @@ -4,96 +4,13 @@ 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, raises +import pytest + import openmc.deplete -from openmc.deplete import ( - CECMIntegrator, PredictorIntegrator, CELIIntegrator, LEQIIntegrator, - EPCRK4Integrator, CF4Integrator, SICELIIntegrator, SILEQIIntegrator -) 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 - PredictorIntegrator(op, dt, power).integrate() - - # Load the files - 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 - PredictorIntegrator(op, dt, power).integrate() - - # Load the files - res = openmc.deplete.ResultsList.from_hdf5(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 - cecm = CECMIntegrator(op, dt, power) - cecm.integrate() - - # Load the files - 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 - cecm_restart = CECMIntegrator(op, dt, power) - cecm_restart.integrate() - - # Load the files - res = openmc.deplete.ResultsList.from_hdf5(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]) - - def test_restart_predictor_cecm(run_in_tmpdir): """Test to ensure that schemes with different stages are not compatible""" @@ -104,18 +21,19 @@ def test_restart_predictor_cecm(run_in_tmpdir): # Perform simulation using the predictor algorithm dt = [0.75] power = 1.0 - PredictorIntegrator(op, dt, power).integrate() + openmc.deplete.PredictorIntegrator(op, dt, power).integrate() # Load the files - prev_res = openmc.deplete.ResultsList.from_hdf5(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 # check ValueError is raised, indicating previous and current stages - with raises(ValueError, match="incompatible.* 1.*2"): - CECMIntegrator(op, dt, power) + with pytest.raises(ValueError, match="incompatible.* 1.*2"): + openmc.deplete.CECMIntegrator(op, dt, power) def test_restart_cecm_predictor(run_in_tmpdir): @@ -129,249 +47,48 @@ def test_restart_cecm_predictor(run_in_tmpdir): # Perform simulation using the MCNPX/MCNP6 algorithm dt = [0.75] power = 1.0 - cecm = CECMIntegrator(op, dt, power) + cecm = openmc.deplete.CECMIntegrator(op, dt, power) cecm.integrate() # Load the files - prev_res = openmc.deplete.ResultsList.from_hdf5(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 # check ValueError is raised, indicating previous and current stages - with raises(ValueError, match="incompatible.* 2.*1"): - PredictorIntegrator(op, dt, power) + with pytest.raises(ValueError, match="incompatible.* 2.*1"): + openmc.deplete.PredictorIntegrator(op, dt, power) -def test_restart_cf4(run_in_tmpdir): - """Integral regression test of integrator algorithm using CF4.""" - op = dummy_operator.DummyOperator() - output_dir = "test_restart_cf4" - op.output_dir = output_dir +@pytest.mark.parametrize("scheme", dummy_operator.SCHEMES) +def test_restart(run_in_tmpdir, scheme): + # set up the problem - # Perform simulation - dt = [0.75] - power = 1.0 - CF4Integrator(op, dt, power).integrate() + bundle = dummy_operator.SCHEMES[scheme] - # Load the files - prev_res = openmc.deplete.ResultsList.from_hdf5(op.output_dir / "depletion_results.h5") + operator = dummy_operator.DummyOperator() - # Re-create depletion operator and load previous results - op = dummy_operator.DummyOperator(prev_res) - op.output_dir = output_dir + # take first step + bundle.solver(operator, [0.75], 1.0).integrate() - # Perform restarts simulation - CF4Integrator(op, dt, power).integrate() + # restart + prev_res = openmc.deplete.ResultsList.from_hdf5( + operator.output_dir / "depletion_results.h5") + operator = dummy_operator.DummyOperator(prev_res) - # Load the files - res = openmc.deplete.ResultsList.from_hdf5(op.output_dir / "depletion_results.h5") + # take second step + bundle.solver(operator, [0.75], 1.0).integrate() - _, y1 = res.get_atoms("1", "1") - _, y2 = res.get_atoms("1", "2") + # compare results - # Reference solution - s1 = [2.06101629, 1.37783588] - s2 = [2.57241318, 2.63731630] + results = openmc.deplete.ResultsList.from_hdf5( + operator.output_dir / "depletion_results.h5") - assert y1[1] == approx(s1[0]) - assert y2[1] == approx(s1[1]) + _t, y1 = results.get_atoms("1", "1") + _t, y2 = results.get_atoms("1", "2") - assert y1[2] == approx(s2[0]) - assert y2[2] == 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 - EPCRK4Integrator(op, dt, power).integrate() - - # Load the files - 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 - EPCRK4Integrator(op, dt, power).integrate() - - # Load the files - res = openmc.deplete.ResultsList.from_hdf5(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]) - - -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 - CELIIntegrator(op, dt, power).integrate() - - # Load the files - 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 - CELIIntegrator(op, dt, power).integrate() - - # Load the files - res = openmc.deplete.ResultsList.from_hdf5(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]) - - -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 - LEQIIntegrator(op, dt, power).integrate() - - # Load the files - 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 - LEQIIntegrator(op, dt, power).integrate() - - # Load the files - res = openmc.deplete.ResultsList.from_hdf5(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]) - -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 - SICELIIntegrator(op, dt, power).integrate() - - # Load the files - 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 - SICELIIntegrator(op, dt, power).integrate() - - # Load the files - res = openmc.deplete.ResultsList.from_hdf5(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]) - - -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 - nstages = 10 - SILEQIIntegrator(op, dt, power, nstages).integrate() - - # Load the files - 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 - SILEQIIntegrator(op, dt, power, nstages).integrate() - - # Load the files - res = openmc.deplete.ResultsList.from_hdf5(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]) + assert y1 == pytest.approx(bundle.atoms_1) + assert y2 == pytest.approx(bundle.atoms_2) 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 5cc0d4e6c4..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 -from openmc.deplete import SICELIIntegrator, ResultsList - -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 - SICELIIntegrator(op, dt, power).integrate() - - # Load the files - res = ResultsList.from_hdf5(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 8e3c3a4648..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 -from openmc.deplete import SILEQIIntegrator, ResultsList - -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 - SILEQIIntegrator(op, dt, power, 10).integrate() - - # Load the files - res = ResultsList.from_hdf5(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) From 7b175961fa45bef717b30894f1659d0f2f436a57 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Thu, 22 Aug 2019 11:24:39 -0500 Subject: [PATCH 109/137] Use constant FY if cutoff energy outside provided yields The FissionYieldCutoffHelper will always find a set of yields to use for all nuclides with yield data now, rather than raise an error. Previously, if the cutoff was outside the provided bounds, an error was raised because there wasn't a clear set of "fast" and "thermal" yields to use. However, this caused issues with nuclides that are missing a set of lower yields, like Th232 with yields at 5e5 and 6e6 per ENDF/B-VII.1 data. Now, if the cutoff energy is outside the bounds of provided yield data, the closet set of yields to the cutoff is taken to be constant. These nuclides will not be tallied during the transport routine. --- openmc/deplete/helpers.py | 23 +++--- .../unit_tests/test_deplete_fission_yields.py | 70 ++++++++++--------- 2 files changed, 50 insertions(+), 43 deletions(-) diff --git a/openmc/deplete/helpers.py b/openmc/deplete/helpers.py index debc10403e..eb86b0ea4a 100644 --- a/openmc/deplete/helpers.py +++ b/openmc/deplete/helpers.py @@ -199,8 +199,6 @@ class ConstantFissionYieldHelper(FissionYieldHelper): continue # Specific energy not found, use closest energy distances = [abs(energy - ene) for ene in nuc.yield_energies] - min_index = min( - range(len(nuc.yield_energies)), key=distances.__getitem__) min_E = min(nuc.yield_energies, key=lambda e: abs(e - energy)) self._constant_yields[name] = nuc.yield_data[min_E] @@ -307,21 +305,24 @@ class FissionYieldCutoffHelper(TalliedFissionYieldHelper): 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) - # if zero, then all energies > cutoff - # if len(energies), then all energies <= cutoff - if cutoff_ix == 0 or cutoff_ix == len(energies): - tail = map("{:5.3e}".format, energies) - raise ValueError( - "Cannot find replacement fission yields for {} given " - "cutoff {:5.3e} eV. Yields provided at [{}] eV".format( - name, cutoff, ", ".join(tail))) # find closest energy to requested thermal, fast energies if thermal is None: min_E = min(energies[:cutoff_ix], @@ -333,6 +334,8 @@ class FissionYieldCutoffHelper(TalliedFissionYieldHelper): 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): diff --git a/tests/unit_tests/test_deplete_fission_yields.py b/tests/unit_tests/test_deplete_fission_yields.py index 05f62eaceb..2842043d12 100644 --- a/tests/unit_tests/test_deplete_fission_yields.py +++ b/tests/unit_tests/test_deplete_fission_yields.py @@ -111,7 +111,6 @@ def nuclide_bundle(): pu239 = Nuclide("Pu239") pu239.yield_data = FissionYieldDistribution({ - 0.0253: {"Xe135": 3.141e-3, "Sm149": 8.19e-10, "Gd155": 1.66e-9}, 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}}) @@ -127,8 +126,8 @@ def test_constant_helper(nuclide_bundle, input_energy, yield_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], # only epithermal - "Pu239": nuclide_bundle.pu239.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) @@ -140,50 +139,54 @@ def test_cutoff_construction(nuclide_bundle): # defaults helper = FissionYieldCutoffHelper(nuclide_bundle, 1) assert helper.constant_yields == { - "U238": u238.yield_data[5.0e5]} - assert helper.thermal_yields == { - "U235": u235.yield_data[0.0253], - "Pu239": pu239.yield_data[0.0253]} - assert helper.fast_yields == { - "U235": u235.yield_data[5e5], + "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]} - assert helper.thermal_yields == { - "U235": u235.yield_data[0.0253], - "Pu239": pu239.yield_data[0.0253]} - assert helper.fast_yields == { - "U235": u235.yield_data[14e6], - "Pu239": pu239.yield_data[2e6]} + "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], - "Pu239": pu239.yield_data[0.0253]} - assert helper.fast_yields == { - "U235": u235.yield_data[5e5], - "Pu239": pu239.yield_data[5e5]} + 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[0.0253], - "Pu239": pu239.yield_data[0.0253]} + "U235": u235.yield_data[5e5], "Pu239": pu239.yield_data[5e5]} assert helper.fast_yields == { - "U235": u235.yield_data[5e5], + "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]} - # test failures in cutoff: super low, super high - with pytest.raises(ValueError, match="replacement fission yields"): - FissionYieldCutoffHelper( - nuclide_bundle, 1, thermal_energy=0.001, cutoff=0.002) - with pytest.raises(ValueError, match="replacement fission yields"): - FissionYieldCutoffHelper( - nuclide_bundle, 1, cutoff=15e6, fast_energy=17e6) + 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")) @@ -197,7 +200,8 @@ def test_cutoff_failure(key): # 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)) + 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] From 91c08ef3232b5789532f788ee679f37027ad5664 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Thu, 22 Aug 2019 15:20:56 -0500 Subject: [PATCH 110/137] Improve/expand special methods for FissionYield Remove copy method. __contains__ and __getitem__ now take advantage of the ordering of products. This can improve time searching for fission products and retrieving yields. radd and rmul methods defer to their left counterparts, e.g. x * fy => fy * x. Return NotImplemented types if addition is not done with another set of fission yields and multiplication is not done with a scalar. Improve/expand special methods for FissionYield Remove copy method. __contains__ and __getitem__ now take advantage of the ordering of products. This can improve time searching for fission products and retrieving yields. radd and rmul methods defer to their left counterparts, e.g. x * fy => fy * x. Return NotImplemented types if addition is not done with another set of fission yields and multiplication is not done with a scalar. --- openmc/deplete/nuclide.py | 39 ++++++++++++++++-------- tests/unit_tests/test_deplete_nuclide.py | 39 +++++++++++++++++------- 2 files changed, 55 insertions(+), 23 deletions(-) diff --git a/openmc/deplete/nuclide.py b/openmc/deplete/nuclide.py index a989bf7029..2ec1d3a66d 100644 --- a/openmc/deplete/nuclide.py +++ b/openmc/deplete/nuclide.py @@ -3,9 +3,11 @@ Contains the per-nuclide components of a depletion chain. """ +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: @@ -339,6 +341,7 @@ class Nuclide(object): return valid + class FissionYieldDistribution(Mapping): """Energy-dependent fission product yields for a single nuclide @@ -504,10 +507,15 @@ class FissionYield(Mapping): 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): - if product not in self.products: + ix = bisect.bisect_left(self.products, product) + if ix == len(self.products) or self.products[ix] != product: raise KeyError(product) - return self.yields[self.products.index(product)] + return self.yields[ix] def __len__(self): return len(self.products) @@ -516,35 +524,42 @@ class FissionYield(Mapping): return iter(self.products) def items(self): + """Return pairs of product, yield""" return zip(self.products, self.yields) def __add__(self, other): - new = self.copy() + 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): - new = self.copy() + if not isinstance(scalar, Real): + return NotImplemented + new = FissionYield(self.products, self.yields.copy()) new *= scalar return new def __rmul__(self, scalar): - new = self.copy() - new *= scalar - return new + return self * scalar def __repr__(self): - return "<{}: {}>".format(self.__class__.__name__, repr(dict(self))) - - def copy(self): - """Return an identical yield object, with unique yields""" - return FissionYield(self.products, self.yields.copy()) + return "<{} containing {} products and yields>".format( + self.__class__.__name__, len(self)) diff --git a/tests/unit_tests/test_deplete_nuclide.py b/tests/unit_tests/test_deplete_nuclide.py index 6f5356b129..7d68b3a3d8 100644 --- a/tests/unit_tests/test_deplete_nuclide.py +++ b/tests/unit_tests/test_deplete_nuclide.py @@ -134,25 +134,42 @@ def test_fission_yield_distribution(): act_dist = yield_dict[exp_ene] for exp_prod, exp_yield in exp_dist.items(): assert act_dist[exp_prod] == exp_yield - exp_yield_matrix = numpy.array([ + 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_matrix) + assert numpy.array_equal(yield_dist.yield_matrix, exp_yield) # Test the operations / special methods for fission yield - orig_yield_obj = yield_dist[0.0253] + 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_yield_obj.yields.base is yield_dist.yield_matrix - copied_yield = orig_yield_obj.copy() - # copied yields own their own memory -> not a view - assert copied_yield.yields.base is None + assert orig_yields.yields.base is yield_dist.yield_matrix # Fission yield feature uses scaled and incremented - mod_yields = orig_yield_obj * 2 - assert numpy.array_equal(orig_yield_obj.yields * 2, mod_yields.yields) - mod_yields += orig_yield_obj - assert numpy.array_equal(orig_yield_obj.yields * 3, mod_yields.yields) + 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(): From 61232248dffef6ec14a1f23387949af6cfe452f5 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Thu, 22 Aug 2019 15:23:45 -0500 Subject: [PATCH 111/137] Update documentation for FissionYieldDistribution --- openmc/deplete/nuclide.py | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/openmc/deplete/nuclide.py b/openmc/deplete/nuclide.py index 2ec1d3a66d..8896180a88 100644 --- a/openmc/deplete/nuclide.py +++ b/openmc/deplete/nuclide.py @@ -355,16 +355,12 @@ class FissionYieldDistribution(Mapping): Parameters ---------- - energies : iterable of float - Energies for which fission yield data exist. Must be ordered - by increasing energy - products : iterable of str - Fission products produced by this parent at all energies. - Must be ordered alphabetically - yield_matrix : numpy.ndarray or iterable of iterable of float - Array of shape ``(n_energy, n_products)`` where - ``yield_matrix[g][j]`` is the yield of - ``ordered_products[j]`` due to a fission in energy region ``g``. + 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 ---------- @@ -416,6 +412,11 @@ class FissionYieldDistribution(Mapping): 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 From 1e5cfe09613cf8260840d56900408702e662eba8 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Thu, 22 Aug 2019 15:32:45 -0500 Subject: [PATCH 112/137] Return list of str from FY helpers update_tally_nuclides --- openmc/deplete/abc.py | 8 ++++---- tests/unit_tests/test_deplete_fission_yields.py | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py index 16d2d7abbd..fb638e5c21 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -458,13 +458,13 @@ class FissionYieldHelper(ABC): Returns ------- - nuclides : tuple of str + 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 tuple(sorted(self._chain_set & set(nuclides))) + return sorted(self._chain_set & set(nuclides)) @classmethod def from_operator(cls, operator, **kwargs): @@ -550,7 +550,7 @@ class TalliedFissionYieldHelper(FissionYieldHelper): Returns ------- - nuclides : tuple of str + nuclides : list of str Union of input nuclides and those that have multiple sets of yield data. Sorted by nuclide name @@ -562,7 +562,7 @@ class TalliedFissionYieldHelper(FissionYieldHelper): assert self._fission_rate_tally is not None, ( "Run generate_tallies first") overlap = set(self._chain_nuclides).intersection(set(nuclides)) - nuclides = tuple(sorted(overlap)) + nuclides = sorted(overlap) self._tally_nucs = [self._chain_nuclides[n] for n in nuclides] self._fission_rate_tally.nuclides = nuclides return nuclides diff --git a/tests/unit_tests/test_deplete_fission_yields.py b/tests/unit_tests/test_deplete_fission_yields.py index 2842043d12..d3faec35bf 100644 --- a/tests/unit_tests/test_deplete_fission_yields.py +++ b/tests/unit_tests/test_deplete_fission_yields.py @@ -206,7 +206,7 @@ def test_cutoff_helper(materials, nuclide_bundle, therm_frac): non_zero_nucs = [n.name for n in nuclide_bundle] tally_nucs = helper.update_tally_nuclides(non_zero_nucs) - assert tally_nucs == ("Pu239", "U235",) + assert tally_nucs == ["Pu239", "U235",] # Check tallies fission_tally = helper._fission_rate_tally @@ -249,7 +249,7 @@ def test_averaged_helper(materials, nuclide_bundle, avg_energy): helper.generate_tallies(materials, [0]) tallied_nucs = helper.update_tally_nuclides( [n.name for n in nuclide_bundle]) - assert tallied_nucs == ("Pu239", "U235") + assert tallied_nucs == ["Pu239", "U235"] # check generated tallies fission_tally = helper._fission_rate_tally From 991a36df8dce07c15a0a94f2e813ea9f309ea9be Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Thu, 22 Aug 2019 10:13:28 -0500 Subject: [PATCH 113/137] Pass strings to os functions in test_deplete_fission_yields os.remove and os.chdir support path-like objects only for python 3.6+ --- tests/unit_tests/test_deplete_fission_yields.py | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/tests/unit_tests/test_deplete_fission_yields.py b/tests/unit_tests/test_deplete_fission_yields.py index d3faec35bf..3a63d718b9 100644 --- a/tests/unit_tests/test_deplete_fission_yields.py +++ b/tests/unit_tests/test_deplete_fission_yields.py @@ -63,13 +63,12 @@ def materials(tmpdir_factory): with capi.run_in_memory(): yield [capi.Material(), capi.Material()] finally: - print(os.path.abspath(os.curdir)) - os.remove(tmpdir / "settings.xml") - os.remove(tmpdir / "geometry.xml") - os.remove(tmpdir / "materials.xml") - os.remove(tmpdir / "summary.h5") + # 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(tmpdir) + os.rmdir(str(tmpdir)) def proxy_tally_data(tally, fill=None): From c8e205aff87af51ecc83aafcb8406d091cec9bcb Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Fri, 23 Aug 2019 11:43:19 -0500 Subject: [PATCH 114/137] Update dictionary of secondary particles for Chain --- openmc/deplete/chain.py | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/openmc/deplete/chain.py b/openmc/deplete/chain.py index 04fdf658e1..b3e2dae29e 100644 --- a/openmc/deplete/chain.py +++ b/openmc/deplete/chain.py @@ -104,17 +104,17 @@ def replace_missing(product, decay_data): return product -_secondary_particles = { - "p": ["H1"], "d": ["H2"], "t": ["H3"], "3He": ["He3"], "a": ["He4"], - "2nd": ["H2"], "na": ["He4"], "n3a": ["He4"] * 3, "2na": ["He4"], - "3na": ["He4"], "np": ["H1"], "n2a": ["He4"] * 2, - "2n2a": ["He4"] * 2, "nd": ["H2"], "nt": ["H3"], - "nHe-3": ["He3"], "nd2a": ["H2", "He4"], "nt2a": ["H3", "He4", "He4"], - "2np": ["H1"], "3np": ["H1"], "n2p": ["H1"] * 2, - "2a": ["He4"] * 2, "3a": ["He4"] * 3, "2p": ["H1"] * 2, - "pa": ["H1", "He4"], "t2a": ["H3", "He4", "He4"], - "d2a": ["H2", "He4", "He4"], "pd": ["H1", "H2"], "pt": ["H1", "H3"], - "da": ["H2", "He4"]} +_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): @@ -559,8 +559,7 @@ class Chain(object): bad_sums = {} # Secondary products, like alpha particles, should not be modified - secondary = _secondary_particles.get( - reaction[reaction.index(",") + 1:-1], []) + secondary = _SECONDARY_PARTICLES.get(reaction, []) # Check for validity before manipulation From d81aaeca981e2e10d06adfc560b6726d979f9a6e Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Fri, 9 Aug 2019 11:25:16 -0500 Subject: [PATCH 115/137] Store fission heating IncidentNeutron.from_ace IncidentNeutron.from_ace now computes the fission heating and a fission-less heating coefficient. The fission heating is the product of the heating number and the fission cross section, and stored as MT318. The fission-less heating is the heating from all reactions except fission, computed as heating_number * (total_xs - fission_xs). The MT number for this is taken to be 999 as a temporary value. A test is added for Am244 in test_data_neutron.py to examine the new heating values --- openmc/data/neutron.py | 12 ++++++++++++ tests/unit_tests/test_data_neutron.py | 12 ++++++++++++ 2 files changed, 24 insertions(+) diff --git a/openmc/data/neutron.py b/openmc/data/neutron.py index 9c73152d45..e903b6a92f 100644 --- a/openmc/data/neutron.py +++ b/openmc/data/neutron.py @@ -633,6 +633,18 @@ class IncidentNeutron(EqualityMixin): rx = Reaction.from_ace(ace, i) data.reactions[rx.mt] = rx + # If present, use fission xs to compute "fission heating" coefficient + fission_reaction = data.reactions.get(18) + if fission_reaction is not None: + fission_xs = fission_reaction.xs[strT].y + + # Compute "fission-less" heating coefficient + no_fission_heating = Reaction(999) + no_fission_heating.xs[strT] = Tabulated1D( + energy, heating_number * (total_xs - fission_xs)) + no_fission_heating.redundant = True + data.reactions[999] = no_fission_heating + # Some photon production reactions may be assigned to MTs that don't # exist, usually MT=4. In this case, we create a new reaction and add # them diff --git a/tests/unit_tests/test_data_neutron.py b/tests/unit_tests/test_data_neutron.py index 3cf036aa2c..7fccb33d88 100644 --- a/tests/unit_tests/test_data_neutron.py +++ b/tests/unit_tests/test_data_neutron.py @@ -205,6 +205,18 @@ def test_derived_products(am244): assert total_neutron.yield_(6e6) == pytest.approx(4.2558) +def test_heating(run_in_tmpdir, am244): + assert 999 in am244.reactions # TBD + strT = min(am244.reactions[1].xs.keys()) + total_xs = am244.reactions[1].xs[strT].y + fission_xs = am244.reactions[18].xs[strT].y + total_heating = am244.reactions[301].xs[strT].y + no_fission_heating = am244.reactions[999].xs[strT].y + heating_number = total_heating / total_xs + assert no_fission_heating == pytest.approx( + heating_number * (total_xs - fission_xs)) + + def test_urr(pu239): for T, ptable in pu239.urr.items(): assert T.endswith('K') From 81c6259289da9f661096e6b91a7bec9460a30a3b Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Mon, 19 Aug 2019 11:39:11 -0500 Subject: [PATCH 116/137] Write neutron fission-less heating coefficient to HDF5 Mark reaction 999 as one to be kept during the export process --- openmc/data/neutron.py | 2 +- tests/unit_tests/test_data_neutron.py | 8 +++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/openmc/data/neutron.py b/openmc/data/neutron.py index e903b6a92f..a53194aa78 100644 --- a/openmc/data/neutron.py +++ b/openmc/data/neutron.py @@ -453,7 +453,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, 444, 999) if not (photon_rx or rx.mt in keep_mts): continue diff --git a/tests/unit_tests/test_data_neutron.py b/tests/unit_tests/test_data_neutron.py index 7fccb33d88..22aebe20ad 100644 --- a/tests/unit_tests/test_data_neutron.py +++ b/tests/unit_tests/test_data_neutron.py @@ -206,7 +206,7 @@ def test_derived_products(am244): def test_heating(run_in_tmpdir, am244): - assert 999 in am244.reactions # TBD + assert 999 in am244.reactions strT = min(am244.reactions[1].xs.keys()) total_xs = am244.reactions[1].xs[strT].y fission_xs = am244.reactions[18].xs[strT].y @@ -215,6 +215,12 @@ def test_heating(run_in_tmpdir, am244): heating_number = total_heating / total_xs assert no_fission_heating == pytest.approx( heating_number * (total_xs - fission_xs)) + # Re-read to ensure data is written + am244.export_to_hdf5("Am244.h5") + new = openmc.data.IncidentNeutron.from_hdf5("Am244.h5") + assert 999 in new.reactions + new_rxn = new.reactions[999].xs[strT].y + assert (new_rxn == no_fission_heating).all() def test_urr(pu239): From 12ec18796af10bd2eccce403778b6a4074987d94 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Fri, 23 Aug 2019 13:17:03 -0500 Subject: [PATCH 117/137] Respond to @drewejohnson comments on #1320 --- tests/regression_tests/lattice_multiple/test.py | 8 ++------ tests/regression_tests/tally_aggregation/test.py | 6 ++---- tests/regression_tests/tally_assumesep/tallies.xml | 2 +- 3 files changed, 5 insertions(+), 11 deletions(-) diff --git a/tests/regression_tests/lattice_multiple/test.py b/tests/regression_tests/lattice_multiple/test.py index a9b15c9c85..c287c01027 100644 --- a/tests/regression_tests/lattice_multiple/test.py +++ b/tests/regression_tests/lattice_multiple/test.py @@ -22,12 +22,8 @@ def model(): cyl = openmc.ZCylinder(r=0.4) big_cyl = openmc.ZCylinder(r=0.5) - c1 = openmc.Cell(fill=uo2, region=-cyl) - c2 = openmc.Cell(fill=water, region=+cyl) - pin = openmc.Universe(cells=[c1, c2]) - c3 = openmc.Cell(fill=uo2, region=-big_cyl) - c4 = openmc.Cell(fill=water, region=+big_cyl) - big_pin = openmc.Universe(cells=[c3, c4]) + pin = openmc.model.pin([cyl], [uo2, water]) + big_pin = openmc.model.pin([big_cyl], [uo2, water]) d = 1.2 inner_lattice = openmc.RectLattice() diff --git a/tests/regression_tests/tally_aggregation/test.py b/tests/regression_tests/tally_aggregation/test.py index 8914ac2a8e..70a948be67 100644 --- a/tests/regression_tests/tally_aggregation/test.py +++ b/tests/regression_tests/tally_aggregation/test.py @@ -24,9 +24,7 @@ def model(): model.materials.extend([fuel, water]) cyl = openmc.ZCylinder(r=0.4) - c1 = openmc.Cell(fill=fuel, region=-cyl) - c2 = openmc.Cell(fill=water, region=+cyl) - pin = openmc.Universe(cells=[c1, c2]) + pin = openmc.model.pin([cyl], [fuel, water]) d = 1.2 lattice = openmc.RectLattice() lattice.lower_left = (-d, -d) @@ -45,7 +43,7 @@ def model(): model.settings.particles = 1000 energy_filter = openmc.EnergyFilter([0.0, 0.253, 1.0e3, 1.0e6, 20.0e6]) - distrib_filter = openmc.DistribcellFilter(c1) + distrib_filter = openmc.DistribcellFilter(pin.cells[1]) tally = openmc.Tally(name='distribcell tally') tally.filters = [energy_filter, distrib_filter] tally.scores = ['nu-fission', 'total'] diff --git a/tests/regression_tests/tally_assumesep/tallies.xml b/tests/regression_tests/tally_assumesep/tallies.xml index 1e1c5492af..f65e5dfec6 100644 --- a/tests/regression_tests/tally_assumesep/tallies.xml +++ b/tests/regression_tests/tally_assumesep/tallies.xml @@ -1,7 +1,7 @@ - false + true cell From 399b77e38fea9f4488049c7c918038e04cfc3971 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Fri, 30 Aug 2019 09:36:28 -0500 Subject: [PATCH 118/137] Clean up internals for AveragedFissionYieldHelper Respond to some other reviewer comments for #1313 --- openmc/deplete/chain.py | 1 + openmc/deplete/helpers.py | 14 ++++++-------- openmc/deplete/operator.py | 2 +- tests/unit_tests/test_deplete_fission_yields.py | 2 +- 4 files changed, 9 insertions(+), 10 deletions(-) diff --git a/openmc/deplete/chain.py b/openmc/deplete/chain.py index 9f6b9ecf7f..c2b5b75b31 100644 --- a/openmc/deplete/chain.py +++ b/openmc/deplete/chain.py @@ -731,6 +731,7 @@ class Chain(object): 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 diff --git a/openmc/deplete/helpers.py b/openmc/deplete/helpers.py index eb86b0ea4a..56644a3bba 100644 --- a/openmc/deplete/helpers.py +++ b/openmc/deplete/helpers.py @@ -425,11 +425,9 @@ class FissionYieldCutoffHelper(TalliedFissionYieldHelper): return yields rates = self.results[local_mat_index] # iterate over thermal then fast yields, prefer __mul__ to __rmul__ - for therm_frac, nuc in zip(rates[0], self._tally_nucs): - yields[nuc.name] = self._thermal_yields[nuc.name] * therm_frac - - for fast_frac, nuc in zip(rates[1], self._tally_nucs): - yields[nuc.name] += self._fast_yields[nuc.name] * fast_frac + 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 @@ -444,9 +442,7 @@ class FissionYieldCutoffHelper(TalliedFissionYieldHelper): class AveragedFissionYieldHelper(TalliedFissionYieldHelper): r"""Class that computes fission yields based on average fission energy - Computes average energy at which fission events occured - reactions for all nuclides with multiple sets of fission yields - by + Computes average energy at which fission events occured with .. math:: @@ -481,6 +477,8 @@ class AveragedFissionYieldHelper(TalliedFissionYieldHelper): 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): diff --git a/openmc/deplete/operator.py b/openmc/deplete/operator.py index 43523b0fe1..01bf92dcc9 100644 --- a/openmc/deplete/operator.py +++ b/openmc/deplete/operator.py @@ -86,7 +86,7 @@ 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") + fission_yield_mode : {"constant", "cutoff", "average"} Key indicating what fission product yield scheme to use. The key determines what fission energy helper is used: diff --git a/tests/unit_tests/test_deplete_fission_yields.py b/tests/unit_tests/test_deplete_fission_yields.py index 3a63d718b9..62d2f65866 100644 --- a/tests/unit_tests/test_deplete_fission_yields.py +++ b/tests/unit_tests/test_deplete_fission_yields.py @@ -205,7 +205,7 @@ def test_cutoff_helper(materials, nuclide_bundle, therm_frac): non_zero_nucs = [n.name for n in nuclide_bundle] tally_nucs = helper.update_tally_nuclides(non_zero_nucs) - assert tally_nucs == ["Pu239", "U235",] + assert tally_nucs == ["Pu239", "U235"] # Check tallies fission_tally = helper._fission_rate_tally From c486b4187ffbf6ae3c36e2ab9a7e3489c0cbdf70 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Fri, 30 Aug 2019 09:55:58 -0500 Subject: [PATCH 119/137] Use python API for setup in test_deplete_fission_yields --- .../unit_tests/test_deplete_fission_yields.py | 68 +++++++------------ 1 file changed, 24 insertions(+), 44 deletions(-) diff --git a/tests/unit_tests/test_deplete_fission_yields.py b/tests/unit_tests/test_deplete_fission_yields.py index 62d2f65866..bedd702a90 100644 --- a/tests/unit_tests/test_deplete_fission_yields.py +++ b/tests/unit_tests/test_deplete_fission_yields.py @@ -6,7 +6,8 @@ from unittest.mock import Mock import bisect import pytest -import numpy +import numpy as np +import openmc from openmc import capi from openmc.deplete.nuclide import Nuclide, FissionYieldDistribution from openmc.deplete.helpers import ( @@ -19,46 +20,25 @@ def materials(tmpdir_factory): """Use C API to construct realistic materials for testing tallies""" tmpdir = tmpdir_factory.mktemp("capi") orig = tmpdir.chdir() - # Create proxy xml files to please openmc - with open("geometry.xml", "w") as stream: - stream.write(""" - - - - - - - - -""") - with open("settings.xml", "w") as stream: - stream.write(""" - - - eigenvalue - 100 - 10 - 0 - 1 - - - 0.0 0.0 0.0 - - - -""") - with open("materials.xml", "w") as stream: - stream.write(""" - - - - - - - - - -""") + # 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()] @@ -87,7 +67,7 @@ def proxy_tally_data(tally, fill=None): if isinstance(tfilter, capi.EnergyFilter): this_bins -= 1 n_bins *= max(this_bins, 1) - data = numpy.empty((n_bins, n_nucs * n_scores, 3)) + data = np.empty((n_bins, n_nucs * n_scores, 3)) if fill is not None: data.fill(fill) return data @@ -229,7 +209,7 @@ def test_cutoff_helper(materials, nuclide_bundle, therm_frac): helper.unpack() # expected results of shape (n_mats, 2, n_tnucs) - expected_results = numpy.empty((1, 2, len(tally_nucs))) + 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) @@ -285,7 +265,7 @@ def test_averaged_helper(materials, nuclide_bundle, avg_energy): helper._weighted_tally.results = weighted_results helper.unpack() - expected_results = numpy.ones((1, len(tallied_nucs))) * avg_energy + expected_results = np.ones((1, len(tallied_nucs))) * avg_energy assert helper.results == pytest.approx(expected_results) actual_yields = helper.weighted_yields(0) From d8c3b8a015c590d9312f6ee749f46ac1163a69fa Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Fri, 30 Aug 2019 10:38:08 -0500 Subject: [PATCH 120/137] Use level for neutron data temperature messages By setting a level, the messages will be printed only if the verbosity setting allows it. This reduces some of the noise printed in testing. Related output from travis: https://travis-ci.org/openmc-dev/openmc/jobs/574867362#L1666-L1683 A level of 4 was chosen, indicating this should be printed with the OpenMC logo, headers, and results. --- src/cross_sections.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/cross_sections.cpp b/src/cross_sections.cpp index f9bfe1d229..3c29dd8e6d 100644 --- a/src/cross_sections.cpp +++ b/src/cross_sections.cpp @@ -359,9 +359,9 @@ 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"); + std::to_string(data::temperature_min) + " K", 4); write_message("Maximum neutron data temperature: " + - std::to_string(data::temperature_max) + " K"); + std::to_string(data::temperature_max) + " K", 4); // If the user wants multipole, make sure we found a multipole library. if (settings::temperature_multipole) { From 99bff4f23166c94ebe585ef4abfca9f48684dc3f Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Fri, 30 Aug 2019 14:22:59 -0500 Subject: [PATCH 121/137] Use helper function when scoring fission_q_* Add a brief helper function get_nuc_fission_q in src/tallies/tally_scoring.cpp that is responsible for returning the correct fission q value for a given score. The two allowed scores are SCORE_FISS_Q_PROMPT and SCORE_FISS_Q_RECOV. The helper function examines the correct dataset on the nuclide object and evaluates the function given the incident particle energy or returns zero. The default value of zero is returned if the fission_q_prompt_ or fission_q_recov_ datasets are empty, or the score is not one of the expected types. --- src/tallies/tally_scoring.cpp | 68 ++++++++++++++--------------------- 1 file changed, 26 insertions(+), 42 deletions(-) diff --git a/src/tallies/tally_scoring.cpp b/src/tallies/tally_scoring.cpp index 6bf9ca6e9f..3016799026 100644 --- a/src/tallies/tally_scoring.cpp +++ b/src/tallies/tally_scoring.cpp @@ -171,6 +171,22 @@ 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 for nu-fission tallies with energyout filters. // //! In this case, we may need to score to multiple bins if there were multiple @@ -1040,17 +1056,9 @@ score_general_ce(Particle* p, int i_tally, int start_index, // 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_]}; + const Nuclide& 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 + score = p->wgt_absorb_ * get_nuc_fission_q(nuc, p, score_bin) * p->neutron_xs_[p->event_nuclide_].fission / p->neutron_xs_[p->event_nuclide_].absorption * flux; } @@ -1060,51 +1068,27 @@ score_general_ce(Particle* p, int i_tally, int start_index, // 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_]}; + const Nuclide& 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 + score = p->wgt_last_ * get_nuc_fission_q(nuc, p, score_bin) * 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; + const Nuclide& nuc {*data::nuclides[i_nuclide]}; + score = get_nuc_fission_q(nuc, p, score_bin) + * 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; + const Nuclide& nuc {*data::nuclides[j_nuclide]}; + score += get_nuc_fission_q(nuc, p, score_bin) + * p->neutron_xs_[j_nuclide].fission * atom_density * flux; } } } From f2e14dc6bef6156c56fe5fa7e2b775183aaf625f Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Fri, 30 Aug 2019 15:29:00 -0500 Subject: [PATCH 122/137] Separate fission-q scoring into separate function In order to reduce the amount of duplicated code in building the energy deposition tally, the logic for scoring SCORE_FISS_Q_PROMPT and SCORE_FISS_Q_RECOV has been pulled into a new function, score_fission_q. The code is almost entirely copied over, with some minor cleanup operations. --- src/tallies/tally_scoring.cpp | 97 +++++++++++++++++++---------------- 1 file changed, 52 insertions(+), 45 deletions(-) diff --git a/src/tallies/tally_scoring.cpp b/src/tallies/tally_scoring.cpp index 3016799026..bbbd3fefb9 100644 --- a/src/tallies/tally_scoring.cpp +++ b/src/tallies/tally_scoring.cpp @@ -187,6 +187,56 @@ double get_nuc_fission_q(const Nuclide& nuc, const Particle* p, int score_bin) return 0.0; } +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 for nu-fission tallies with energyout filters. // //! In this case, we may need to score to multiple bins if there were multiple @@ -339,7 +389,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_; @@ -1048,51 +1098,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 Nuclide& nuc {*data::nuclides[p->event_nuclide_]}; - if (p->neutron_xs_[p->event_nuclide_].absorption > 0) { - score = p->wgt_absorb_ * get_nuc_fission_q(nuc, p, score_bin) - * 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 Nuclide& nuc {*data::nuclides[p->event_nuclide_]}; - if (p->neutron_xs_[p->event_nuclide_].absorption > 0) { - score = p->wgt_last_ * get_nuc_fission_q(nuc, p, score_bin) - * p->neutron_xs_[p->event_nuclide_].fission - / p->neutron_xs_[p->event_nuclide_].absorption * flux; - } - } - } else { - if (i_nuclide >= 0) { - const Nuclide& nuc {*data::nuclides[i_nuclide]}; - score = get_nuc_fission_q(nuc, p, score_bin) - * 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 Nuclide& nuc {*data::nuclides[j_nuclide]}; - score += get_nuc_fission_q(nuc, p, score_bin) - * p->neutron_xs_[j_nuclide].fission * atom_density * flux; - } - } - } - } + score = score_fission_q(p, score_bin, tally, flux, i_nuclide, atom_density); break; From d9e5249e8edbd63f82c5f04ad4a531fca310e13e Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Tue, 3 Sep 2019 12:40:36 -0500 Subject: [PATCH 123/137] Make sure output is always verbosity-limited --- src/dagmc.cpp | 2 +- src/output.cpp | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) 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/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; From 7badc48c225671a087fa78300bfd3dbcd16f2906 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Tue, 3 Sep 2019 16:15:14 -0500 Subject: [PATCH 124/137] Instruct njoy interface to retain heatr output HEATR contains the 318 fission heating data needed to construct the energy deposition tally. Until now, the output tape was left in the njoy temporary directory with the other output tapes. This commit instructs openmc.data.njoy to move the heatr output from the running directory used in njoy.run to the working directory used in njoy.make_ace Removed unused imports argparse and sys --- openmc/data/neutron.py | 5 ++--- openmc/data/njoy.py | 8 ++++---- 2 files changed, 6 insertions(+), 7 deletions(-) diff --git a/openmc/data/neutron.py b/openmc/data/neutron.py index a53194aa78..22c9d03054 100644 --- a/openmc/data/neutron.py +++ b/openmc/data/neutron.py @@ -813,9 +813,8 @@ 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')) + for key in ["ace", "xsdir", "pendf", "heatr"]: + kwargs.setdefault(key, os.path.join(tmpdir, key)) kwargs['evaluation'] = evaluation make_ace(filename, temperatures, **kwargs) diff --git a/openmc/data/njoy.py b/openmc/data/njoy.py index c8d630851b..3fb75751f5 100644 --- a/openmc/data/njoy.py +++ b/openmc/data/njoy.py @@ -1,10 +1,8 @@ -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 . import endf @@ -237,8 +235,9 @@ def make_ace(filename, temperatures=None, ace='ace', xsdir='xsdir', pendf=None, 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 + 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, optional Indicating whether to add gas production data when running NJOY purr : bool, optional @@ -292,6 +291,7 @@ def make_ace(filename, temperatures=None, ace='ace', xsdir='xsdir', pendf=None, if heatr: nheatr_in = nlast nheatr = nheatr_in + 1 + tapeout[nheatr] = "heatr" if heatr is True else heatr commands += _TEMPLATE_HEATR nlast = nheatr From 2675ebc2c6bb22f36c791280dc6332d8cb9227e2 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Tue, 3 Sep 2019 17:09:15 -0500 Subject: [PATCH 125/137] Compute fission heating and fission-less heating with njoy This reverts d81aaeca9 and 81c625928 in the following ways. Fission heating data, MT318, is pulled from the heatr file produced when running NJOY. Reaction data that is potentially temperature dependent is set onto the IncidentNeutron object by scaling MT318 by the ratio of the fission cross section used in HEATR and other fission cross sections stored on the object. This produces potentially many MT318 fission heating KERMA coefficients on the nuclide. The fission-less heating coefficient, MT999, if computed by subtracting MT318 from MT301, total heating coefficients. This reaction is marked as redundant, as it can easily be reconstructed. MT318 is allowed to be written to the HDF5 file, while 999 is not. When reading back in the library, MT999 is rebuilt in exactly the same manner. The test_heating test in tests/unit_tests/test_data_neutron.py has been updated given the changes in this commit. It is worth noting that the heating coefficients computed in NJOY only contain prompt neutrons, as k_{i,j}(E) = sigma_{i,j}(E) * (E + Q - \bar{E}) where k_{i,j} is the kerma coefficient for reaction j of material i, sigma_{i,j} is the corresponding reaction cross section, E is the energy of incident particle, Q is the mass-difference Q value, and \bar{E} is the average energy of secondary particles. Source: NJOY16 Manual on HEATR --- openmc/data/neutron.py | 71 +++++++++++++++++++++------ tests/unit_tests/test_data_neutron.py | 26 +++++----- 2 files changed, 67 insertions(+), 30 deletions(-) diff --git a/openmc/data/neutron.py b/openmc/data/neutron.py index 22c9d03054..ae3b2c6c22 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 @@ -453,7 +451,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, 999) + 203, 204, 205, 206, 207, 301, 318, 444) if not (photon_rx or rx.mt in keep_mts): continue @@ -554,6 +552,22 @@ class IncidentNeutron(EqualityMixin): fer_group = group['fission_energy_release'] data.fission_energy = FissionEnergyRelease.from_hdf5(fer_group) + # Rebuild fission-less 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: + fission_less_heating = Reaction(999) + fission_less_heating.redundant = True + for strT, total in total_heating.xs.items(): + fission = fission_heating.xs.get(strT) + if fission is None: + continue + fission_less_heating.xs[strT] = Tabulated1D( + total.x, total.y - fission(total.x), + breakpoints=total.breakpoints, + interpolation=total.interpolation) + data.reactions[999] = fission_less_heating + return data @classmethod @@ -633,18 +647,6 @@ class IncidentNeutron(EqualityMixin): rx = Reaction.from_ace(ace, i) data.reactions[rx.mt] = rx - # If present, use fission xs to compute "fission heating" coefficient - fission_reaction = data.reactions.get(18) - if fission_reaction is not None: - fission_xs = fission_reaction.xs[strT].y - - # Compute "fission-less" heating coefficient - no_fission_heating = Reaction(999) - no_fission_heating.xs[strT] = Tabulated1D( - energy, heating_number * (total_xs - fission_xs)) - no_fission_heating.redundant = True - data.reactions[999] = no_fission_heating - # Some photon production reactions may be assigned to MTs that don't # exist, usually MT=4. In this case, we create a new reaction and add # them @@ -828,6 +830,43 @@ 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 + heatr = Evaluation(kwargs["heatr"]) + f318 = StringIO(heatr.section[3, 318]) + get_head_record(f318) + _params, fission_kerma = get_tab1_record(f318) + # Assume that only cross section changes with temperature + # to compute heating data for multiple temperatures + f18 = StringIO(heatr.section[3, 18]) + get_head_record(f18) + _params, fission_xs_heatr = get_tab1_record(f18) + heat_num = Tabulated1D( + fission_kerma.x, + fission_kerma.y / fission_xs_heatr(fission_kerma.x), + breakpoints=fission_kerma.breakpoints, + interpolation=fission_kerma.interpolation) + + fission_heating = Reaction(318) + fission_less_heating = Reaction(999) + fission_less_heating.redundant = True + + for strT, fission_xs in data.reactions[18].xs.items(): + total_heating = data.reactions[301].xs.get(strT) + if total_heating is None: + continue + heater_at_temp = Tabulated1D( + fission_xs.x, heat_num(fission_xs.x) * fission_xs.y, + breakpoints=fission_xs.breakpoints, + interpolation=fission_xs.interpolation) + fission_heating.xs[strT] = heater_at_temp + fission_less_heating.xs[strT] = Tabulated1D( + total_heating.x, + total_heating.y - heater_at_temp(total_heating.x), + breakpoints=total_heating.breakpoints, + interpolation=total_heating.interpolation) + + data.reactions[318] = fission_heating + data.reactions[999] = fission_less_heating # Add 0K elastic scattering cross section if '0K' not in data.energy: diff --git a/tests/unit_tests/test_data_neutron.py b/tests/unit_tests/test_data_neutron.py index 22aebe20ad..c5da4acfaf 100644 --- a/tests/unit_tests/test_data_neutron.py +++ b/tests/unit_tests/test_data_neutron.py @@ -206,21 +206,19 @@ def test_derived_products(am244): def test_heating(run_in_tmpdir, am244): + assert 318 in am244.reactions assert 999 in am244.reactions - strT = min(am244.reactions[1].xs.keys()) - total_xs = am244.reactions[1].xs[strT].y - fission_xs = am244.reactions[18].xs[strT].y - total_heating = am244.reactions[301].xs[strT].y - no_fission_heating = am244.reactions[999].xs[strT].y - heating_number = total_heating / total_xs - assert no_fission_heating == pytest.approx( - heating_number * (total_xs - fission_xs)) - # Re-read to ensure data is written - am244.export_to_hdf5("Am244.h5") - new = openmc.data.IncidentNeutron.from_hdf5("Am244.h5") - assert 999 in new.reactions - new_rxn = new.reactions[999].xs[strT].y - assert (new_rxn == no_fission_heating).all() + # 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): From bbe5b7ba5ec3d9aac035aec0757445d88dd9b96f Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Tue, 3 Sep 2019 21:49:32 -0500 Subject: [PATCH 126/137] Make sure MPI_Type_free is not called if MPI hasn't been initialized --- src/finalize.cpp | 4 +++- src/message_passing.cpp | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/src/finalize.cpp b/src/finalize.cpp index 120903f3fe..7c39a7c198 100644 --- a/src/finalize.cpp +++ b/src/finalize.cpp @@ -127,7 +127,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; } From 56322b9b3f57c5eeebfd967dff8dea44834b2dad Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Tue, 3 Sep 2019 21:50:16 -0500 Subject: [PATCH 127/137] Make sure capi.init is called with intracomm in tests when MPI is enabled --- tests/unit_tests/conftest.py | 11 +++++++++++ tests/unit_tests/test_capi.py | 8 ++++---- tests/unit_tests/test_complex_cell_capi.py | 4 ++-- 3 files changed, 17 insertions(+), 6 deletions(-) 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 ed0bfd441d..c8ad0907c5 100644 --- a/tests/unit_tests/test_capi.py +++ b/tests/unit_tests/test_capi.py @@ -50,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() @@ -408,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 From 1134b18cb9c65ea9473cbea40b44a736a9307525 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Tue, 3 Sep 2019 22:30:22 -0500 Subject: [PATCH 128/137] Set CMake policy CMP0079 when version >= 3.13 --- CMakeLists.txt | 5 +++++ 1 file changed, 5 insertions(+) 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) From a0bac56ebdb5a7062a8d6ca0f115b1ee0a195420 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Wed, 4 Sep 2019 13:47:32 -0500 Subject: [PATCH 129/137] Add fission-heating and non-fission-heating to reactions MT318 and MT999 for fission heating and non-fission heating have been added to the reactions supported in openmc/data/reaction.py and src/reaction.cpp Changed internal language from fission-less heating to non-fission heating --- openmc/data/neutron.py | 16 ++++++++-------- openmc/data/reaction.py | 1 + src/reaction.cpp | 4 ++++ 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/openmc/data/neutron.py b/openmc/data/neutron.py index ae3b2c6c22..0f474446cf 100644 --- a/openmc/data/neutron.py +++ b/openmc/data/neutron.py @@ -556,17 +556,17 @@ class IncidentNeutron(EqualityMixin): total_heating = data.reactions.get(301) fission_heating = data.reactions.get(318) if total_heating is not None and fission_heating is not None: - fission_less_heating = Reaction(999) - fission_less_heating.redundant = True + 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 - fission_less_heating.xs[strT] = Tabulated1D( + non_fission_heating.xs[strT] = Tabulated1D( total.x, total.y - fission(total.x), breakpoints=total.breakpoints, interpolation=total.interpolation) - data.reactions[999] = fission_less_heating + data.reactions[999] = non_fission_heating return data @@ -847,8 +847,8 @@ class IncidentNeutron(EqualityMixin): interpolation=fission_kerma.interpolation) fission_heating = Reaction(318) - fission_less_heating = Reaction(999) - fission_less_heating.redundant = True + non_fission_heating = Reaction(999) + non_fission_heating.redundant = True for strT, fission_xs in data.reactions[18].xs.items(): total_heating = data.reactions[301].xs.get(strT) @@ -859,14 +859,14 @@ class IncidentNeutron(EqualityMixin): breakpoints=fission_xs.breakpoints, interpolation=fission_xs.interpolation) fission_heating.xs[strT] = heater_at_temp - fission_less_heating.xs[strT] = Tabulated1D( + non_fission_heating.xs[strT] = Tabulated1D( total_heating.x, total_heating.y - heater_at_temp(total_heating.x), breakpoints=total_heating.breakpoints, interpolation=total_heating.interpolation) data.reactions[318] = fission_heating - data.reactions[999] = fission_less_heating + data.reactions[999] = non_fission_heating # Add 0K elastic scattering cross section if '0K' not in data.energy: 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/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) { From 6b23c21327ea660f5bafabc4e192e1b72252bbda Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Wed, 4 Sep 2019 13:28:06 -0500 Subject: [PATCH 130/137] Pass output_dir to make_ace to collect files The user can now pass a single argument indicating where output tapes for each njoy module should be written. The default argument is the current working directory, useful for removing any temporary files created during testing. The pendf, broadr, heatr, gaspr, and purr arguments can either be booleans or strings. A boolean value indicates the module output should be written into a file named after the module, e.g. heatr output written to /heatr. ace and xsdir can be None or strings. A value of None means the final ace and xsdir files will be written to /ace and /xsdir, respectively. The temporary temperature-dependent ace and xsdir files are now always named "ace_" and "xsdir_", as the ace and xsdir arguments can be more than "ace" and "xsdir". openmc.data.IncidentNeutron.from_njoy knows to use this output_dir argument to ensure all created tape files are written into the temporary directory or the user requested directory, if applicable. This commit was written because simply writing the broadr, gaspr, and purr output files to "broadr", "gaspr", and "purr" resulted in left over files after running tests. --- openmc/data/neutron.py | 6 ++-- openmc/data/njoy.py | 76 ++++++++++++++++++++++++++++++------------ 2 files changed, 59 insertions(+), 23 deletions(-) diff --git a/openmc/data/neutron.py b/openmc/data/neutron.py index 0f474446cf..26c0a71c5a 100644 --- a/openmc/data/neutron.py +++ b/openmc/data/neutron.py @@ -815,8 +815,10 @@ class IncidentNeutron(EqualityMixin): """ with tempfile.TemporaryDirectory() as tmpdir: # Run NJOY to create an ACE library - for key in ["ace", "xsdir", "pendf", "heatr"]: - kwargs.setdefault(key, os.path.join(tmpdir, key)) + kwargs.setdefault("output_dir", tmpdir) + for key in ("ace", "xsdir", "pendf", "heatr", "broadr", + "gaspr", "purr"): + kwargs.setdefault(key, os.path.join(kwargs["output_dir"], key)) kwargs['evaluation'] = evaluation make_ace(filename, temperatures, **kwargs) diff --git a/openmc/data/njoy.py b/openmc/data/njoy.py index 3fb75751f5..a648a53515 100644 --- a/openmc/data/njoy.py +++ b/openmc/data/njoy.py @@ -213,11 +213,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, ace=None, xsdir=None, + output_dir=None, pendf=False, error=0.001, broadr=True, + heatr=True, gaspr=True, purr=True, acer=True, evaluation=None, + **kwargs): """Generate incident neutron ACE file from an ENDF file + File names can be passed to + ``[ace, 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 @@ -226,22 +236,30 @@ def make_ace(filename, temperatures=None, ace='ace', xsdir='xsdir', pendf=None, 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 + 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"`` + 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 + 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, optional - Indicating whether to add gas production data when running NJOY - purr : bool, optional - Indicating whether to add probability table when running NJOY + 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. acer : bool, optional Indicating whether to generate ACE file when running NJOY evaluation : openmc.data.endf.Evaluation, optional @@ -256,6 +274,12 @@ def make_ace(filename, temperatures=None, ace='ace', xsdir='xsdir', pendf=None, If the NJOY process returns with a non-zero status """ + if output_dir is None: + output_dir = os.path.abspath(os.curdir) + else: + assert os.path.isdir(output_dir), output_dir + output_dir = os.path.abspath(output_dir) + ev = evaluation if evaluation is not None else endf.Evaluation(filename) mat = ev.material zsymam = ev.target['zsymam'] @@ -274,8 +298,9 @@ 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] = (os.path.join(output_dir, "pendf") if pendf is True + else pendf) # reconr commands += _TEMPLATE_RECONR @@ -284,6 +309,8 @@ def make_ace(filename, temperatures=None, ace='ace', xsdir='xsdir', pendf=None, # broadr if broadr: nbroadr = nlast + 1 + tapeout[nbroadr] = ( + os.path.join(output_dir, "broadr") if broadr is True else broadr) commands += _TEMPLATE_BROADR nlast = nbroadr @@ -291,7 +318,8 @@ def make_ace(filename, temperatures=None, ace='ace', xsdir='xsdir', pendf=None, if heatr: nheatr_in = nlast nheatr = nheatr_in + 1 - tapeout[nheatr] = "heatr" if heatr is True else heatr + tapeout[nheatr] = (os.path.join(output_dir, "heatr") if heatr is True + else heatr) commands += _TEMPLATE_HEATR nlast = nheatr @@ -299,6 +327,8 @@ def make_ace(filename, temperatures=None, ace='ace', xsdir='xsdir', pendf=None, if gaspr: ngaspr_in = nlast ngaspr = ngaspr_in + 1 + tapeout[ngaspr] = (os.path.join(output_dir, "gaspr") if gaspr is True + else gaspr) commands += _TEMPLATE_GASPR nlast = ngaspr @@ -306,6 +336,8 @@ def make_ace(filename, temperatures=None, ace='ace', xsdir='xsdir', pendf=None, if purr: npurr_in = nlast npurr = npurr_in + 1 + tapeout[npurr] = (os.path.join(output_dir, "purr") if purr is True + else purr) commands += _TEMPLATE_PURR nlast = npurr @@ -323,16 +355,18 @@ 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: + ace = os.path.join(output_dir, "ace") if ace is None else ace + xsdir = os.path.join(output_dir, "xsdir") if xsdir is None else xsdir with open(ace, 'w') as ace_file, open(xsdir, '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 @@ -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, From c7a8b6606e444046cd8b04609c74a549650ff18e Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Wed, 4 Sep 2019 14:46:32 -0500 Subject: [PATCH 131/137] Better collection of fission heating at temperatures Use openmc.data.endf.get_evaluations to read in potentially many 318 evaluations from the heatr file. The evaluated temperatures are pulled using the target attribute, rounded to the nearest integer for consistency, e.g. 293.6K -> 294K. --- openmc/data/neutron.py | 47 +++++++++++++++--------------------------- 1 file changed, 17 insertions(+), 30 deletions(-) diff --git a/openmc/data/neutron.py b/openmc/data/neutron.py index 26c0a71c5a..37230e62ca 100644 --- a/openmc/data/neutron.py +++ b/openmc/data/neutron.py @@ -13,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 @@ -833,39 +834,25 @@ class IncidentNeutron(EqualityMixin): if (1, 458) in ev.section: data.fission_energy = FissionEnergyRelease.from_endf(ev, data) # Add 318 fission heating data from heatr - heatr = Evaluation(kwargs["heatr"]) - f318 = StringIO(heatr.section[3, 318]) - get_head_record(f318) - _params, fission_kerma = get_tab1_record(f318) - # Assume that only cross section changes with temperature - # to compute heating data for multiple temperatures - f18 = StringIO(heatr.section[3, 18]) - get_head_record(f18) - _params, fission_xs_heatr = get_tab1_record(f18) - heat_num = Tabulated1D( - fission_kerma.x, - fission_kerma.y / fission_xs_heatr(fission_kerma.x), - breakpoints=fission_kerma.breakpoints, - interpolation=fission_kerma.interpolation) - - fission_heating = Reaction(318) non_fission_heating = Reaction(999) non_fission_heating.redundant = True + fission_heating = Reaction(318) - for strT, fission_xs in data.reactions[18].xs.items(): - total_heating = data.reactions[301].xs.get(strT) - if total_heating is None: + 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 - heater_at_temp = Tabulated1D( - fission_xs.x, heat_num(fission_xs.x) * fission_xs.y, - breakpoints=fission_xs.breakpoints, - interpolation=fission_xs.interpolation) - fission_heating.xs[strT] = heater_at_temp - non_fission_heating.xs[strT] = Tabulated1D( - total_heating.x, - total_heating.y - heater_at_temp(total_heating.x), - breakpoints=total_heating.breakpoints, - interpolation=total_heating.interpolation) + 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 From 390953050b88e848a828e3063400854a59664de6 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Wed, 4 Sep 2019 16:00:28 -0500 Subject: [PATCH 132/137] Pull out neutron heating scoring to standalone function Neutron heating logic has been pulled out from the main score_general_ce function into a single helper function score_neutron_heating. This function returns a double that is the neutron heating, using analog, collision, or track-length estimators, incorporating survival biasing, and the possiblity of scoring across all nuclides in a given material. The functionality is identical to the original implementation, just condensed into two functions to reduce repeated code. The second helper function digs through neutron data to obtain and interpolate kerma coefficients for a given nuclide and reaction. --- src/tallies/tally_scoring.cpp | 169 ++++++++++++++-------------------- 1 file changed, 70 insertions(+), 99 deletions(-) diff --git a/src/tallies/tally_scoring.cpp b/src/tallies/tally_scoring.cpp index bbbd3fefb9..1f6a8010a5 100644 --- a/src/tallies/tally_scoring.cpp +++ b/src/tallies/tally_scoring.cpp @@ -187,6 +187,11 @@ double get_nuc_fission_q(const Nuclide& nuc, const Particle* p, int score_bin) 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) { @@ -236,6 +241,69 @@ double score_fission_q(const Particle* p, int score_bin, const Tally& tally, 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. // @@ -1145,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 From 05f33849f51f4d6fba1d75979d958e1a24a9d0f8 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Thu, 5 Sep 2019 08:35:52 -0500 Subject: [PATCH 133/137] Remove breakpoints, interpolation for non-fission heating NJOY handles linearization of the cross sections in RECONR: Section 3.2 NJOY2016 Manual, LA-UR-17-20093 --- openmc/data/neutron.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/openmc/data/neutron.py b/openmc/data/neutron.py index 37230e62ca..16ff032a6d 100644 --- a/openmc/data/neutron.py +++ b/openmc/data/neutron.py @@ -553,7 +553,7 @@ class IncidentNeutron(EqualityMixin): fer_group = group['fission_energy_release'] data.fission_energy = FissionEnergyRelease.from_hdf5(fer_group) - # Rebuild fission-less heating + # 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: @@ -564,9 +564,7 @@ class IncidentNeutron(EqualityMixin): if fission is None: continue non_fission_heating.xs[strT] = Tabulated1D( - total.x, total.y - fission(total.x), - breakpoints=total.breakpoints, - interpolation=total.interpolation) + total.x, total.y - fission(total.x)) data.reactions[999] = non_fission_heating return data From c0566e0a2d269d0749a98f13464ec665cedbfe90 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Thu, 1 Aug 2019 09:24:52 -0500 Subject: [PATCH 134/137] Remove cram wrapper with maps of depletion matrices openmc.deplete.cram.deplete now pre-generates a map object that produces depletion matrices given the chain, rates, and matrix_func. The internal cram wrapper function has been removed, as the full inputs to CRAM48 are now known, in zipped and mapped objects. Some basic timing comparisons indicate that this approach is at least as fast, if not marginally faster, for a quarter PWR assembly and an SFR assembly. --- openmc/deplete/cram.py | 42 +++++++----------------------------------- 1 file changed, 7 insertions(+), 35 deletions(-) diff --git a/openmc/deplete/cram.py b/openmc/deplete/cram.py index 184c411bf3..67a455cb35 100644 --- a/openmc/deplete/cram.py +++ b/openmc/deplete/cram.py @@ -51,11 +51,15 @@ def deplete(chain, x, rates, dt, matrix_func=None): "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), - fission_yields, repeat(matrix_func)) - x_result = list(pool.starmap(_cram_wrapper, iters)) + inputs = zip(matrices, x, repeat(dt)) + x_result = list(pool.starmap(CRAM48, inputs)) return x_result @@ -79,38 +83,6 @@ def timed_deplete(*args, **kwargs): return time.time() - start, results -def _cram_wrapper(chain, n0, rates, dt, fission_yields, 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 - fission_yields : dict - Single-energy fission yields of the form - ``{parent: {product: fission_yield}}`` - - Returns - ------- - numpy.array - Results of the matrix exponent. - """ - - if matrix_func is None: - A = chain.form_matrix(rates, fission_yields) - else: - A = matrix_func(chain, rates, fission_yields) - return CRAM48(A, n0, dt) - - def CRAM16(A, n0, dt): """Chebyshev Rational Approximation Method, order 16 From 76adfb231c53ac276643046c8a5fbd728ea194f9 Mon Sep 17 00:00:00 2001 From: Andrew Johnson Date: Thu, 5 Sep 2019 09:18:28 -0500 Subject: [PATCH 135/137] Combine acer, ace arguments to openmc.data.make_ace Passing ace to make_ace is not supported. Use acer instead. The acer argument now signals if the acer njoy module should be run and where to save the output file. xsdir defaults to None now, rather than "xsdir". This change allows the xsdir file to be written to the same directory as acer, or a user supplied location potentially separate from ace file. --- openmc/data/neutron.py | 5 ++- openmc/data/njoy.py | 52 +++++++++++++-------------- tests/unit_tests/test_data_neutron.py | 2 +- 3 files changed, 29 insertions(+), 30 deletions(-) diff --git a/openmc/data/neutron.py b/openmc/data/neutron.py index 16ff032a6d..9024b932e4 100644 --- a/openmc/data/neutron.py +++ b/openmc/data/neutron.py @@ -815,14 +815,13 @@ class IncidentNeutron(EqualityMixin): with tempfile.TemporaryDirectory() as tmpdir: # Run NJOY to create an ACE library kwargs.setdefault("output_dir", tmpdir) - for key in ("ace", "xsdir", "pendf", "heatr", "broadr", - "gaspr", "purr"): + 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) diff --git a/openmc/data/njoy.py b/openmc/data/njoy.py index a648a53515..7993ec4004 100644 --- a/openmc/data/njoy.py +++ b/openmc/data/njoy.py @@ -4,6 +4,7 @@ import os import shutil from subprocess import Popen, PIPE, STDOUT, CalledProcessError import tempfile +from pathlib import Path from . import endf @@ -213,14 +214,14 @@ 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=None, xsdir=None, +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, acer=True, evaluation=None, + heatr=True, gaspr=True, purr=True, evaluation=None, **kwargs): """Generate incident neutron ACE file from an ENDF file File names can be passed to - ``[ace, xsdir, pendf, broadr, heatr, gaspr, purr]`` + ``[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 @@ -235,10 +236,13 @@ def make_ace(filename, temperatures=None, ace=None, xsdir=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. Defaults to ``"ace"`` + 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. Defaults to ``"xsdir"`` + 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]`` @@ -260,8 +264,6 @@ def make_ace(filename, temperatures=None, ace=None, xsdir=None, purr : bool or str, optional Indicating whether to add probability table when running NJOY. If string, write the output tape to this file. - acer : bool, optional - Indicating whether to generate ACE file when running NJOY evaluation : openmc.data.endf.Evaluation, optional If the ENDF file contains multiple material evaluations, this argument indicates which evaluation should be used. @@ -272,13 +274,16 @@ def make_ace(filename, temperatures=None, ace=None, xsdir=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 = os.path.abspath(os.curdir) + output_dir = Path() else: - assert os.path.isdir(output_dir), output_dir - output_dir = os.path.abspath(output_dir) + 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 @@ -299,8 +304,7 @@ def make_ace(filename, temperatures=None, ace=None, xsdir=None, tapein = {nendf: filename} tapeout = {} if pendf: - tapeout[npendf] = (os.path.join(output_dir, "pendf") if pendf is True - else pendf) + tapeout[npendf] = (output_dir / "pendf") if pendf is True else pendf # reconr commands += _TEMPLATE_RECONR @@ -309,8 +313,7 @@ def make_ace(filename, temperatures=None, ace=None, xsdir=None, # broadr if broadr: nbroadr = nlast + 1 - tapeout[nbroadr] = ( - os.path.join(output_dir, "broadr") if broadr is True else broadr) + tapeout[nbroadr] = (output_dir / "broadr") if broadr is True else broadr commands += _TEMPLATE_BROADR nlast = nbroadr @@ -318,8 +321,7 @@ def make_ace(filename, temperatures=None, ace=None, xsdir=None, if heatr: nheatr_in = nlast nheatr = nheatr_in + 1 - tapeout[nheatr] = (os.path.join(output_dir, "heatr") if heatr is True - else heatr) + tapeout[nheatr] = (output_dir / "heatr") if heatr is True else heatr commands += _TEMPLATE_HEATR nlast = nheatr @@ -327,8 +329,7 @@ def make_ace(filename, temperatures=None, ace=None, xsdir=None, if gaspr: ngaspr_in = nlast ngaspr = ngaspr_in + 1 - tapeout[ngaspr] = (os.path.join(output_dir, "gaspr") if gaspr is True - else gaspr) + tapeout[ngaspr] = (output_dir / "gaspr") if gaspr is True else gaspr commands += _TEMPLATE_GASPR nlast = ngaspr @@ -336,8 +337,7 @@ def make_ace(filename, temperatures=None, ace=None, xsdir=None, if purr: npurr_in = nlast npurr = npurr_in + 1 - tapeout[npurr] = (os.path.join(output_dir, "purr") if purr is True - else purr) + tapeout[npurr] = (output_dir / "purr") if purr is True else purr commands += _TEMPLATE_PURR nlast = npurr @@ -361,15 +361,15 @@ def make_ace(filename, temperatures=None, ace=None, xsdir=None, run(commands, tapein, tapeout, **kwargs) if acer: - ace = os.path.join(output_dir, "ace") if ace is None else ace - xsdir = os.path.join(output_dir, "xsdir") if xsdir is None else xsdir - 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() - # 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: diff --git a/tests/unit_tests/test_data_neutron.py b/tests/unit_tests/test_data_neutron.py index c5da4acfaf..40a286331e 100644 --- a/tests/unit_tests/test_data_neutron.py +++ b/tests/unit_tests/test_data_neutron.py @@ -481,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) From 862524c26add48461b28c77736619cfdc718a4f5 Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Thu, 5 Sep 2019 22:12:40 -0400 Subject: [PATCH 136/137] Fix Material try/catch and unit test assertion --- openmc/material.py | 2 ++ tests/unit_tests/test_pin.py | 12 ++++++------ 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/openmc/material.py b/openmc/material.py index 375e1dca1e..1700055268 100644 --- a/openmc/material.py +++ b/openmc/material.py @@ -413,6 +413,8 @@ class Material(IDManagerMixin): Z, _, _ = openmc.data.zam(nuclide) except ValueError as e: warnings.warn(str(e)) + except KeyError as e: + warnings.warn(repr(e)) else: # For actinides, have the material be depletable by default if Z >= 89: 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)) From 35c9f406b4ad2696ddc5230dd57ff1ebcb6e3e8b Mon Sep 17 00:00:00 2001 From: Sterling Harper Date: Fri, 6 Sep 2019 09:52:49 -0400 Subject: [PATCH 137/137] Address #1333 comment --- openmc/data/data.py | 7 ++++++- openmc/material.py | 2 -- 2 files changed, 6 insertions(+), 3 deletions(-) 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/material.py b/openmc/material.py index 1700055268..375e1dca1e 100644 --- a/openmc/material.py +++ b/openmc/material.py @@ -413,8 +413,6 @@ class Material(IDManagerMixin): Z, _, _ = openmc.data.zam(nuclide) except ValueError as e: warnings.warn(str(e)) - except KeyError as e: - warnings.warn(repr(e)) else: # For actinides, have the material be depletable by default if Z >= 89: