From 41653f02ea52442d64590c5be85e7830a0ab00aa Mon Sep 17 00:00:00 2001 From: church89 Date: Wed, 23 Aug 2023 18:03:07 +0200 Subject: [PATCH 01/59] first commit to new branch --- openmc/deplete/batchwise.py | 435 ++++++++++++++++++++++++++++++++++++ 1 file changed, 435 insertions(+) create mode 100644 openmc/deplete/batchwise.py diff --git a/openmc/deplete/batchwise.py b/openmc/deplete/batchwise.py new file mode 100644 index 00000000000..03dfcf9f046 --- /dev/null +++ b/openmc/deplete/batchwise.py @@ -0,0 +1,435 @@ +from abc import ABC, abstractmethod +from collections.abc import Iterable +from openmc.search import _SCALAR_BRACKETED_METHODS, search_for_keff +from openmc import Materials, Material, Cell +from openmc.data import atomic_mass, AVOGADRO, ELEMENT_SYMBOL + +import numpy as np +import os +import h5py + +class Batchwise(ABC): + + def __init__(self, operator, model, bracket, bracket_limit, + bracketed_method='brentq', tol=0.01, target=1.0, + print_iterations=True, search_for_keff_output=True): + + self.operator = operator + self.burn_mats = operator.burnable_mats + self.local_mats = operator.local_mats + self.model = model + self.geometry = model.geometry + + check_iterable_type('bracket', bracket, Real) + check_length('bracket', bracket, 2) + check_less_than('bracket values', bracket[0], bracket[1]) + self.bracket = bracket + + check_iterable_type('bracket_limit', bracket_limit, Real) + check_length('bracket_limit', bracket_limit, 2) + check_less_than('bracket limit values', + bracket_limit[0], bracket_limit[1]) + + self.bracket_limit = bracket_limit + + self.bracketed_method = bracketed_method + self.tol = tol + self.target = target + self.print_iterations = print_iterations + self.search_for_keff_output = search_for_keff_output + + @property + def bracketed_method(self): + return self._bracketed_method + + @bracketed_method.setter + def bracketed_method(self, value): + check_value('bracketed_method', value, _SCALAR_BRACKETED_METHODS) + if value != 'brentq': + warn('brentq bracketed method is recomended here') + self._bracketed_method = value + + @property + def tol(self): + return self._tol + + @tol.setter + def tol(self, value): + check_type("tol", value, Real) + self._tol = value + + @property + def target(self): + return self._target + + @target.setter + def target(self, value): + check_type("target", value, Real) + self._target = value + + @abstractmethod + def _model_builder(self, param): + + def _get_cell_id(self, val): + """Helper method for getting cell id from cell instance or cell name. + Parameters + ---------- + val : Openmc.Cell or str or int representing Cell + Returns + ------- + id : str + Cell id + """ + if isinstance(val, Cell): + check_value('Cell id', val.id, [cell.id for cell in \ + self.geometry.get_all_cells().values()]) + val = val.id + + elif isinstance(val, str): + if val.isnumeric(): + check_value('Cell id', val, [str(cell.id) for cell in \ + self.geometry.get_all_cells().values()]) + val = int(val) + else: + check_value('Cell name', val, [cell.name for cell in \ + self.geometry.get_all_cells().values()]) + + val = [cell.id for cell in \ + self.geometry.get_all_cells().values() \ + if cell.name == val][0] + + elif isinstance(val, int): + check_value('Cell id', val, [cell.id for cell in \ + self.geometry.get_all_cells().values()]) + + else: + ValueError(f'Cell: {val} is not recognized') + + return val + + def _search_for_keff(self, val): + """ + Perform the criticality search for a given parametric model. + It supports geometrical or material based `search_for_keff`. + Parameters + ---------- + val : float + Previous result value + Returns + ------- + root : float + Estimated value of the variable parameter where keff is the + targeted value + """ + #make sure we don't modify original bracket and tol values + bracket = deepcopy(self.bracket) + + #search_for_keff tolerance should vary according to the first guess value + if abs(val) > 1.0: + tol = self.tol / abs(val) + else: + tol = self.tol + + # Run until a search_for_keff root is found or ouf ot limits + root = None + + while res == None: + search = search_for_keff(self._model_builder, + bracket = [bracket[0] + val, bracket[1] + val], + tol = tol, + bracketed_method = self.bracketed_method, + target = self.target, + print_iterations = self.print_iterations, + run_args = {'output': self.search_for_keff_output}) + + # if len(search) is 3 search_for_keff was successful + if len(search) == 3: + res,_,_ = search + + #Check if root is within bracket limits + if self.bracket_limit[0] < res < self.bracket_limit[1]: + root = res + + else: + # Set res with the closest limit and continue + arg_min = abs(np.array(self.bracket_limit) - res).argmin() + warn('WARNING: Search_for_keff returned root out of '\ + 'bracket limit. Set root to {:.2f} and continue.' + .format(self.bracket_limit[arg_min])) + root = self.bracket_limit[arg_min] + + elif len(search) == 2: + guesses, keffs = search + + #Check if all guesses are within bracket limits + if all(self.bracket_limit[0] < guess < self.bracket_limit[1] \ + for guess in guesses): + #Simple method to iteratively adapt the bracket + print('INFO: Function returned values below or above ' \ + 'target. Adapt bracket...') + + # if the bracket ends up being smaller than the std of the + # keff's closer value to target, no need to continue- + if all(keff <= max(keffs).s for keff in keffs): + arg_min = abs(self.target - np.array(guesses)).argmin() + root = guesses[arg_min] + + # Calculate gradient as ratio of delta bracket and delta keffs + grad = abs(np.diff(bracket) / np.diff(keffs))[0].n + # Move the bracket closer to presumed keff root. + + # Two cases: both keffs are below or above target + if np.mean(keffs) < self.target: + # direction of moving bracket: +1 is up, -1 is down + if guess[np.argmax(keffs)] > guess[np.argmin(keffs)]: + dir = 1 + else: + dir = -1 + bracket[np.argmin(keffs)] = bracket[np.argmax(keffs)] + bracket[np.argmax(keffs)] += grad * (self.target - \ + max(keffs).n) * dir + else: + if guess[np.argmax(keffs)] > guess[np.argmin(keffs)]: + dir = -1 + else: + dir = 1 + bracket[np.argmax(keffs)] = bracket[np.argmin(keffs)] + bracket[np.argmin(keffs)] += grad * (min(keffs).n - \ + self.target) * dir + + else: + # Set res with closest limit and continue + arg_min = abs(np.array(self.bracket_limit) - guesses).argmin() + warn('WARNING: Adaptive iterative bracket went off '\ + 'bracket limits. Set root to {:.2f} and continue.' + .format(self.bracket_limit[arg_min])) + root = self.bracket_limit[arg_min] + + else: + raise ValueError(f'ERROR: Search_for_keff output is not valid') + + return root + + def _save_res(self, type, step_index, root): + """ + Save results to msr_results.h5 file. + Parameters + ---------- + type : str + String to characterize geometry and material results + step_index : int + depletion time step index + root : float or dict + Root of the search_for_keff function + """ + filename = 'msr_results.h5' + kwargs = {'mode': "a" if os.path.isfile(filename) else "w"} + + if comm.rank == 0: + with h5py.File(filename, **kwargs) as h5: + name = '_'.join([type, str(step_index)]) + if name in list(h5.keys()): + last = sorted([int(re.split('_',i)[1]) for i in h5.keys()])[-1] + step_index = last + 1 + h5.create_dataset('_'.join([type, str(step_index)]), data=root) + + def _update_volumes_after_depletion(self, x): + """ + After a depletion step, both materials volume and density change, due to + decay, transmutation reactions and transfer rates, if set. + At present we lack an implementation to calculate density and volume + changes due to the different molecules speciation. Therefore, the assumption + is to consider the density constant and let the material volume + vary with the change in nuclides concentrations. + The method uses the nuclides concentrations coming from the previous Bateman + solution and calculates a new volume, keeping the mass density of the material + constant. It will then assign the volumes to the AtomNumber instance. + + Parameters + ---------- + x : list of numpy.ndarray + Total atom concentrations + """ + self.operator.number.set_density(x) + + for rank in range(comm.size): + number_i = comm.bcast(self.operator.number, root=rank) + + for i, mat in enumerate(number_i.materials): + # Total nuclides density + density = 0 + for nuc in number_i.nuclides: + # total number of atoms + val = number_i[mat, nuc] + # obtain nuclide density in atoms-g/mol + density += val * atomic_mass(nuc) + # Get mass dens from beginning, intended to be held constant + rho = openmc.lib.materials[int(mat)].get_density('g/cm3') + number_i.volume[i] = density / AVOGADRO / rho + +class BatchwiseCell(Batchwise): + + def __init__(self, operator, model, cell, attrib_name, axis, bracket, + bracket_limit, bracketed_method='brentq', tol=0.01, target=1.0, + print_iterations=True, search_for_keff_output=True): + + super().__init__(operator, model, bracket, bracket_limit, + bracketed_method, tol, target, print_iterations, + search_for_keff_output) + + self.cell_id = super()._get_cell_id(cell_id_or_name) + check_value('attrib_name', attrib_name, + ('rotation', 'translation')) + self.attrib_name = attrib_name + + #index of cell directionnal axis + check_value('axis', axis, (0,1,2)) + self.axis = axis + + # Initialize vector + self.vector = np.zeros(3) + + # materials that fill the attribute cell, if depletables + self.cell_material_ids = [cell.fill.id for cell in \ + self.geometry.get_all_cells()[self.cell_id].fill.cells.values() \ + if cell.fill.depletable] + + def _get_cell_attrib(self): + """ + Get cell attribute coefficient. + Returns + ------- + coeff : float + cell coefficient + """ + for cell in openmc.lib.cells.values(): + if cell.id == self.cell_id: + if self.attrib_name == 'translation': + return cell.translation[self.axis] + elif self.attrib_name == 'rotation': + return cell.rotation[self.axis] + + def _set_cell_attrib(self, val): + """ + Set cell attribute to the cell instance. + Attributes are only applied to cells filled with a universe + Parameters + ---------- + var : float + Surface coefficient to set + geometry : openmc.model.geometry + OpenMC geometry model + attrib_name : str + Currently only translation is implemented + """ + self.vector[self.axis] = val + for cell in openmc.lib.cells.values(): + if cell.id == self.cell_id or cell.name == self.cell_id: + setattr(cell, self.attrib_name, self.vector) + + def _update_materials(self, x): + """ + Assign concentration vectors from Bateman solution at previous + timestep to the in-memory model materials, after having recalculated the + material volume. + + Parameters + ---------- + x : list of numpy.ndarray + Total atom concentrations + """ + super()._update_volumes_after_depletion(x) + + for rank in range(comm.size): + number_i = comm.bcast(self.operator.number, root=rank) + + for mat in number_i.materials: + nuclides = [] + densities = [] + + for nuc in number_i.nuclides: + # get atom density in atoms/b-cm + val = 1.0e-24 * number_i.get_atom_density(mat, nuc) + if nuc in self.operator.nuclides_with_data: + if val > self.atom_density_limit: + nuclides.append(nuc) + densities.append(val) + + #set nuclide densities to model in memory (C-API) + openmc.lib.materials[int(mat)].set_densities(nuclides, densities) + + def _update_volumes(self): + openmc.lib.calculate_volumes() + res = openmc.VolumeCalculation.from_hdf5('volume_1.h5') + + number_i = self.operator.number + for mat_idx, mat_id in enumerate(self.local_mats): + if mat_id in self.cell_material_ids: + number_i.volume[mat_idx] = res.volumes[int(mat_id)].n + + def _update_x(self): + number_i = self.operator.number + for mat_idx, mat_id in enumerate(self.local_mats): + if mat_id in self.cell_material_ids: + for nuc_idx, nuc in enumerate(number_i.burnable_nuclides): + x[mat_idx][nuc_idx] = number_i.volume[mat_idx] * \ + number_i.get_atom_density(mat_idx, nuc) + return x + + def _model_builder(self, param): + """ + Builds the parametric model that is passed to the `msr_search_for_keff` + function by setting the parametric variable to the geoemetrical cell. + Parameters + ---------- + param : model parametricl variable + for examlple: cell translation coefficient + Returns + ------- + _model : openmc.model.Model + OpenMC parametric model + """ + self._set_cell_attrib(param) + #Calulate new volume and update if materials filling the cell are + # depletable + if self.cell_material_ids: + self._update_volumes() + return self.model + + def search_for_keff(self, x, step_index): + """ + Perform the criticality search on the parametric geometrical model. + Will set the root of the `search_for_keff` function to the cell + attribute. + Parameters + ---------- + x : list of numpy.ndarray + Total atoms concentrations + Returns + ------- + x : list of numpy.ndarray + Updated total atoms concentrations + """ + # Get cell attribute from previous iteration + val = self._get_cell_attrib() + check_type('Cell coeff', val, Real) + + # Update volume and concentration vectors before performing the search_for_keff + self._update_materials(x) + + # Calculate new cell attribute + root = super().search_for_keff(val) + + # set results value as attribute in the geometry + self._set_cell_attrib(root) + print('UPDATE: old value: {:.2f} cm --> ' \ + 'new value: {:.2f} cm'.format(val, root)) + + # x needs to be updated after the search with new volumes if materials cell + # depletable + if self.cell_material_ids: + self._update_x() + + #Store results + super()._save_res('geometry', step_index, root) + return x From 1d3f0caf4550521a3204e3dd303c4e0b5d93149c Mon Sep 17 00:00:00 2001 From: church89 Date: Mon, 4 Sep 2023 17:03:34 +0200 Subject: [PATCH 02/59] batchwise first commits --- openmc/deplete/__init__.py | 1 + openmc/deplete/abc.py | 53 +- openmc/deplete/batchwise.py | 1062 ++++++++++++++++---- openmc/deplete/stepresult.py | 17 +- openmc/search.py | 30 +- tests/unit_tests/test_deplete_batchwise.py | 135 +++ 6 files changed, 1098 insertions(+), 200 deletions(-) create mode 100644 tests/unit_tests/test_deplete_batchwise.py diff --git a/openmc/deplete/__init__.py b/openmc/deplete/__init__.py index 8a9509e9009..c86a93c8a19 100644 --- a/openmc/deplete/__init__.py +++ b/openmc/deplete/__init__.py @@ -17,6 +17,7 @@ from .results import * from .integrators import * from .transfer_rates import * +from .batchwise import * from . import abc from . import cram from . import helpers diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py index 1acfe6b1a80..d14355947f5 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -18,14 +18,16 @@ from numpy import nonzero, empty, asarray from uncertainties import ufloat -from openmc.checkvalue import check_type, check_greater_than +from openmc.checkvalue import check_type, check_greater_than, check_value from openmc.mpi import comm from .stepresult import StepResult from .chain import Chain from .results import Results from .pool import deplete from .transfer_rates import TransferRates - +from openmc import Material, Cell, Universe +from .batchwise import (BatchwiseCellGeometrical, BatchwiseCellTemperature, + BatchwiseMaterialRefuel) __all__ = [ "OperatorResult", "TransportOperator", @@ -635,6 +637,7 @@ def __init__(self, operator, timesteps, power=None, power_density=None, self.source_rates = asarray(source_rates) self.transfer_rates = None + self.batchwise = None if isinstance(solver, str): # Delay importing of cram module, which requires this file @@ -762,6 +765,14 @@ def _get_start_data(self): return (self.operator.prev_res[-1].time[-1], len(self.operator.prev_res) - 1) + def _get_bos_from_batchwise(self, step_index, bos_conc): + """Get BOS from criticality batch-wise control + """ + x = deepcopy(bos_conc) + # Get new vector after keff criticality control + x, root = self.batchwise.search_for_keff(x, step_index) + return x, root + def integrate(self, final_step=True, output=True): """Perform the entire depletion process across all steps @@ -787,6 +798,11 @@ def integrate(self, final_step=True, output=True): # Solve transport equation (or obtain result from restart) if i > 0 or self.operator.prev_res is None: + # Update geometry/material according to batchwise definition + if self.batchwise and source_rate != 0.0: + n, root = self._get_bos_from_batchwise(i, n) + else: + root = None n, res = self._get_bos_data_from_operator(i, source_rate, n) else: n, res = self._get_bos_data_from_restart(i, source_rate, n) @@ -800,9 +816,8 @@ def integrate(self, final_step=True, output=True): # Remove actual EOS concentration for next step n = n_list.pop() - StepResult.save(self.operator, n_list, res_list, [t, t + dt], - source_rate, self._i_res + i, proc_time) + source_rate, self._i_res + i, proc_time, root) t += dt @@ -812,9 +827,13 @@ def integrate(self, final_step=True, output=True): # solve) if output and final_step and comm.rank == 0: print(f"[openmc.deplete] t={t} (final operator evaluation)") + if self.batchwise and source_rate != 0.0: + n, root = self._get_bos_from_batchwise(i+1, n) + else: + root = None res_list = [self.operator(n, source_rate if final_step else 0.0)] StepResult.save(self.operator, [n], res_list, [t, t], - source_rate, self._i_res + len(self), proc_time) + source_rate, self._i_res + len(self), proc_time, root) self.operator.write_bos_data(len(self) + self._i_res) self.operator.finalize() @@ -847,6 +866,30 @@ def add_transfer_rate(self, material, components, transfer_rate, self.transfer_rates.set_transfer_rate(material, components, transfer_rate, transfer_rate_units, destination_material) + def add_batchwise(self, obj, attr, **kwargs): + """Add batchwise operation to integrator scheme. + + Parameters + ---------- + attr : str + Type of batchwise operation to add. `Trans` stands for geometrical + translation, `refuel` for material refueling and `dilute` for material + dilute. + **kwargs + keyword arguments that are passed to the batchwise class. + + """ + check_value('attribute', attr, ('translation', 'rotation', 'temperature', 'refuel')) + if attr in ('translation', 'rotation'): + batchwise = BatchwiseCellGeometrical + elif attr == 'temperature': + batchwise = BatchwiseCellTemperature + elif attr == 'refuel': + batchwise = BatchwiseMaterialRefuel + + self.batchwise = batchwise.from_params(obj, attr, self.operator, + self.operator.model, **kwargs) + @add_params class SIIntegrator(Integrator): r"""Abstract class for the Stochastic Implicit Euler integrators diff --git a/openmc/deplete/batchwise.py b/openmc/deplete/batchwise.py index 03dfcf9f046..7136f7e6b21 100644 --- a/openmc/deplete/batchwise.py +++ b/openmc/deplete/batchwise.py @@ -1,18 +1,77 @@ from abc import ABC, abstractmethod -from collections.abc import Iterable -from openmc.search import _SCALAR_BRACKETED_METHODS, search_for_keff -from openmc import Materials, Material, Cell -from openmc.data import atomic_mass, AVOGADRO, ELEMENT_SYMBOL - +from copy import deepcopy +from numbers import Real +from warnings import warn import numpy as np -import os -import h5py -class Batchwise(ABC): +import openmc.lib +from openmc.mpi import comm +from openmc.search import _SCALAR_BRACKETED_METHODS, search_for_keff +from openmc import Material, Cell +from openmc.data import atomic_mass, AVOGADRO +from openmc.checkvalue import (check_type, check_value, check_less_than, + check_iterable_type, check_length) +class Batchwise(ABC): + """Abstract class defining a generalized batchwise scheme. + + In between transport and depletion steps batchwise operations can be added + with the aim of maintain a system critical. These operations act on OpenMC + Cell or Material, parametrizing one or more coefficients while running a + search_for_kef algorithm. + + Specific classes for running batchwise depletion calculations are + implemented as derived class of Batchwise + . + .. versionadded:: 0.13.4 + + Parameters + ---------- + operator : openmc.deplete.Operator + OpenMC operator object + model : openmc.model.Model + OpenMC model object + bracket : list of float + Bracketing interval to search for the solution, always relative to the + solution at previous step. + bracket_limit : list of float + Absolute bracketing interval lower and upper; if during the adaptive + algorithm the search_for_keff solution lies off these limits the closest + limit will be set as new result. + density_treatment : str + Wether ot not to keep contant volume or density after a depletion step + before the next one. + Default to 'constant-volume' + bracketed_method : {'brentq', 'brenth', 'ridder', 'bisect'}, optional + Solution method to use. + This is equivalent to the `bracket_method` parameter of the + `search_for_keff`. + Defaults to 'brentq'. + tol : float + Tolerance for search_for_keff method. + This is equivalent to the `tol` parameter of the `search_for_keff`. + Default to 0.01 + target : Real, optional + This is equivalent to the `target` parameter of the `search_for_keff`. + Default to 1.0. + print_iterations : Bool, Optional + Wheter or not to print `search_for_keff` iterations. + Default to True + search_for_keff_output : Bool, Optional + Wheter or not to print transport iterations during `search_for_keff`. + Default to False + Attributes + ---------- + burn_mats : list of str + List of burnable materials ids + local_mats : list of str + All burnable material IDs being managed by a single process + List of burnable materials ids + """ def __init__(self, operator, model, bracket, bracket_limit, - bracketed_method='brentq', tol=0.01, target=1.0, - print_iterations=True, search_for_keff_output=True): + density_treatment = 'constant-volume', bracketed_method='brentq', + tol=0.01, target=1.0, print_iterations=True, + search_for_keff_output=True): self.operator = operator self.burn_mats = operator.burnable_mats @@ -20,6 +79,10 @@ def __init__(self, operator, model, bracket, bracket_limit, self.model = model self.geometry = model.geometry + check_value('density_treatment', density_treatment, + ('constant-density','constant-volume')) + self.density_treatment = density_treatment + check_iterable_type('bracket', bracket, Real) check_length('bracket', bracket, 2) check_less_than('bracket values', bracket[0], bracket[1]) @@ -29,12 +92,18 @@ def __init__(self, operator, model, bracket, bracket_limit, check_length('bracket_limit', bracket_limit, 2) check_less_than('bracket limit values', bracket_limit[0], bracket_limit[1]) - self.bracket_limit = bracket_limit + check_value('bracketed_method', bracketed_method, + _SCALAR_BRACKETED_METHODS) self.bracketed_method = bracketed_method + + check_type('tol', tol, Real) self.tol = tol + + check_type('target', target, Real) self.target = target + self.print_iterations = print_iterations self.search_for_keff_output = search_for_keff_output @@ -46,7 +115,7 @@ def bracketed_method(self): def bracketed_method(self, value): check_value('bracketed_method', value, _SCALAR_BRACKETED_METHODS) if value != 'brentq': - warn('brentq bracketed method is recomended here') + warn('WARNING: brentq bracketed method is recommended') self._bracketed_method = value @property @@ -69,48 +138,33 @@ def target(self, value): @abstractmethod def _model_builder(self, param): - - def _get_cell_id(self, val): - """Helper method for getting cell id from cell instance or cell name. + """ + Builds the parametric model to be passed to the `search_for_keff` + algorithm. + Callable function which builds a model according to a passed + parameter. This function must return an openmc.model.Model object. Parameters ---------- - val : Openmc.Cell or str or int representing Cell + param : parameter + model function variable Returns ------- - id : str - Cell id + _model : openmc.model.Model + OpenMC parametric model """ - if isinstance(val, Cell): - check_value('Cell id', val.id, [cell.id for cell in \ - self.geometry.get_all_cells().values()]) - val = val.id - - elif isinstance(val, str): - if val.isnumeric(): - check_value('Cell id', val, [str(cell.id) for cell in \ - self.geometry.get_all_cells().values()]) - val = int(val) - else: - check_value('Cell name', val, [cell.name for cell in \ - self.geometry.get_all_cells().values()]) - - val = [cell.id for cell in \ - self.geometry.get_all_cells().values() \ - if cell.name == val][0] - - elif isinstance(val, int): - check_value('Cell id', val, [cell.id for cell in \ - self.geometry.get_all_cells().values()]) - - else: - ValueError(f'Cell: {val} is not recognized') - - return val def _search_for_keff(self, val): """ Perform the criticality search for a given parametric model. - It supports geometrical or material based `search_for_keff`. + It supports cell or material based `search_for_keff`. + If the solution lies off the initial bracket, the method will iteratively + adapt the bracket to be able to run `search_for_keff` effectively. + The ratio between the bracket and the returned keffs values will be + the scaling factor to iteratively adapt the brackt unitl a valid solution + is found. + If the adapted bracket is moved too far off, i.e. above or below user + inputted bracket limits interval, the algorithm will stop and the closest + limit value will be used. Parameters ---------- val : float @@ -133,14 +187,15 @@ def _search_for_keff(self, val): # Run until a search_for_keff root is found or ouf ot limits root = None - while res == None: + while root == None: search = search_for_keff(self._model_builder, - bracket = [bracket[0] + val, bracket[1] + val], - tol = tol, - bracketed_method = self.bracketed_method, - target = self.target, - print_iterations = self.print_iterations, - run_args = {'output': self.search_for_keff_output}) + bracket = np.array(bracket) + val, + tol = tol, + bracketed_method = self.bracketed_method, + target = self.target, + print_iterations = self.print_iterations, + run_args = {'output': self.search_for_keff_output}, + run_in_memory = True) # if len(search) is 3 search_for_keff was successful if len(search) == 3: @@ -153,8 +208,8 @@ def _search_for_keff(self, val): else: # Set res with the closest limit and continue arg_min = abs(np.array(self.bracket_limit) - res).argmin() - warn('WARNING: Search_for_keff returned root out of '\ - 'bracket limit. Set root to {:.2f} and continue.' + warn("WARNING: Search_for_keff returned root out of " + "bracket limit. Set root to {:.2f} and continue." .format(self.bracket_limit[arg_min])) root = self.bracket_limit[arg_min] @@ -165,8 +220,8 @@ def _search_for_keff(self, val): if all(self.bracket_limit[0] < guess < self.bracket_limit[1] \ for guess in guesses): #Simple method to iteratively adapt the bracket - print('INFO: Function returned values below or above ' \ - 'target. Adapt bracket...') + print("Search_for_keff returned values below or above " + "target. Trying to iteratively adapt bracket...") # if the bracket ends up being smaller than the std of the # keff's closer value to target, no need to continue- @@ -181,7 +236,7 @@ def _search_for_keff(self, val): # Two cases: both keffs are below or above target if np.mean(keffs) < self.target: # direction of moving bracket: +1 is up, -1 is down - if guess[np.argmax(keffs)] > guess[np.argmin(keffs)]: + if guesses[np.argmax(keffs)] > guesses[np.argmin(keffs)]: dir = 1 else: dir = -1 @@ -189,7 +244,7 @@ def _search_for_keff(self, val): bracket[np.argmax(keffs)] += grad * (self.target - \ max(keffs).n) * dir else: - if guess[np.argmax(keffs)] > guess[np.argmin(keffs)]: + if guesses[np.argmax(keffs)] > guesses[np.argmin(keffs)]: dir = -1 else: dir = 1 @@ -200,51 +255,50 @@ def _search_for_keff(self, val): else: # Set res with closest limit and continue arg_min = abs(np.array(self.bracket_limit) - guesses).argmin() - warn('WARNING: Adaptive iterative bracket went off '\ - 'bracket limits. Set root to {:.2f} and continue.' + warn("WARNING: Adaptive iterative bracket went off " + "bracket limits. Set root to {:.2f} and continue." .format(self.bracket_limit[arg_min])) root = self.bracket_limit[arg_min] else: - raise ValueError(f'ERROR: Search_for_keff output is not valid') + raise ValueError('ERROR: Search_for_keff output is not valid') return root - def _save_res(self, type, step_index, root): + def _update_volumes(self): """ - Save results to msr_results.h5 file. - Parameters - ---------- - type : str - String to characterize geometry and material results - step_index : int - depletion time step index - root : float or dict - Root of the search_for_keff function - """ - filename = 'msr_results.h5' - kwargs = {'mode': "a" if os.path.isfile(filename) else "w"} - - if comm.rank == 0: - with h5py.File(filename, **kwargs) as h5: - name = '_'.join([type, str(step_index)]) - if name in list(h5.keys()): - last = sorted([int(re.split('_',i)[1]) for i in h5.keys()])[-1] - step_index = last + 1 - h5.create_dataset('_'.join([type, str(step_index)]), data=root) - - def _update_volumes_after_depletion(self, x): - """ - After a depletion step, both materials volume and density change, due to + Update number volume. + After a depletion step, both material volume and density change, due to decay, transmutation reactions and transfer rates, if set. At present we lack an implementation to calculate density and volume - changes due to the different molecules speciation. Therefore, the assumption - is to consider the density constant and let the material volume - vary with the change in nuclides concentrations. - The method uses the nuclides concentrations coming from the previous Bateman - solution and calculates a new volume, keeping the mass density of the material - constant. It will then assign the volumes to the AtomNumber instance. + changes due to the different molecules speciation. Therefore, OpenMC + assumes by default that the depletable volume does not change and only + updates the nuclide densities and consequently the total material density. + This method, vice-versa, assumes that the total material density does not + change and updates the materials volume in AtomNumber dataset. + Parameters + ---------- + x : list of numpy.ndarray + Total atom concentrations + """ + number_i = self.operator.number + for mat_idx, mat in enumerate(self.local_mats): + # Total number of atoms-gram per mol + agpm = 0 + for nuc in number_i.nuclides: + agpm += number_i[mat, nuc] * atomic_mass(nuc) + # Get mass dens from beginning, intended to be held constant + density = openmc.lib.materials[int(mat)].get_density('g/cm3') + number_i.volume[mat_idx] = agpm / AVOGADRO / density + + def _update_materials(self, x): + """ + Update number density and material compositions in OpenMC on all processes. + If density_treatment is set to 'constant-density', "_update_volumes" will + update material volumes, keeping the material total density constant, in + AtomNumber to renormalize the atom densities before assign them to the + model in memory. Parameters ---------- x : list of numpy.ndarray @@ -252,36 +306,336 @@ def _update_volumes_after_depletion(self, x): """ self.operator.number.set_density(x) + if self.density_treatment == 'constant-density': + self._update_volumes() + for rank in range(comm.size): number_i = comm.bcast(self.operator.number, root=rank) - for i, mat in enumerate(number_i.materials): - # Total nuclides density - density = 0 + for mat in number_i.materials: + nuclides = [] + densities = [] + for nuc in number_i.nuclides: - # total number of atoms - val = number_i[mat, nuc] - # obtain nuclide density in atoms-g/mol - density += val * atomic_mass(nuc) - # Get mass dens from beginning, intended to be held constant - rho = openmc.lib.materials[int(mat)].get_density('g/cm3') - number_i.volume[i] = density / AVOGADRO / rho + # get atom density in atoms/b-cm + val = 1.0e-24 * number_i.get_atom_density(mat, nuc) + if nuc in self.operator.nuclides_with_data: + if val > 0.0: + nuclides.append(nuc) + densities.append(val) -class BatchwiseCell(Batchwise): + # Update densities on C API side + openmc.lib.materials[int(mat)].set_densities(nuclides, densities) - def __init__(self, operator, model, cell, attrib_name, axis, bracket, - bracket_limit, bracketed_method='brentq', tol=0.01, target=1.0, - print_iterations=True, search_for_keff_output=True): + def _update_x_and_set_volumes(self, x, volumes): + """ + Update x with volumes, before assign them to AtomNumber materials. + Parameters + ---------- + x : list of numpy.ndarray + Total atoms concentrations + volumes : dict - super().__init__(operator, model, bracket, bracket_limit, + Returns + ------- + x : list of numpy.ndarray + Updated total atoms concentrations + """ + number_i = self.operator.number + + for mat_idx, mat in enumerate(self.local_mats): + + if mat in volumes: + res_vol = volumes[mat] + # Normalize burbable nuclides in x vector without cross section data + for nuc_idx, nuc in enumerate(number_i.burnable_nuclides): + if nuc not in self.operator.nuclides_with_data: + # normalzie with new volume + x[mat_idx][nuc_idx] *= number_i.get_mat_volume(mat) / \ + res_vol + + # update all concentration data with the new updated volumes + for nuc, dens in zip(openmc.lib.materials[int(mat)].nuclides, + openmc.lib.materials[int(mat)].densities): + nuc_idx = number_i.index_nuc[nuc] + n_of_atoms = dens / 1.0e-24 * res_vol + + if nuc in number_i.burnable_nuclides: + # convert [#atoms/b-cm] into [#atoms] + x[mat_idx][nuc_idx] = n_of_atoms + # when the nuclide is not in depletion chain update the AtomNumber + else: + #Atom density needs to be in [#atoms/cm3] + number_i[mat, nuc] = n_of_atoms + + #Now we can update the new volume in AtomNumber + number_i.volume[mat_idx] = res_vol + return x + +class BatchwiseCell(Batchwise): + """Abstract class holding batchwise cell-based functions. + + Specific classes for running batchwise depletion calculations are + implemented as derived class of BatchwiseCell. + + .. versionadded:: 0.13.4 + + Parameters + ---------- + cell : openmc.Cell or int or str + OpenMC Cell identifier to where apply batchwise scheme + operator : openmc.deplete.Operator + OpenMC operator object + model : openmc.model.Model + OpenMC model object + bracket : list of float + Bracketing interval to search for the solution, always relative to the + solution at previous step. + bracket_limit : list of float + Absolute bracketing interval lower and upper; if during the adaptive + algorithm the search_for_keff solution lies off these limits the closest + limit will be set as new result. + density_treatment : str + Wether ot not to keep contant volume or density after a depletion step + before the next one. + Default to 'constant-volume' + bracketed_method : {'brentq', 'brenth', 'ridder', 'bisect'}, optional + Solution method to use. + This is equivalent to the `bracket_method` parameter of the + `search_for_keff`. + Defaults to 'brentq'. + tol : float + Tolerance for search_for_keff method. + This is equivalent to the `tol` parameter of the `search_for_keff`. + Default to 0.01 + target : Real, optional + This is equivalent to the `target` parameter of the `search_for_keff`. + Default to 1.0. + print_iterations : Bool, Optional + Wheter or not to print `search_for_keff` iterations. + Default to True + search_for_keff_output : Bool, Optional + Wheter or not to print transport iterations during `search_for_keff`. + Default to False + Attributes + ---------- + cell : openmc.Cell or int or str + OpenMC Cell identifier to where apply batchwise scheme + cell_materials : list of openmc.Material + Depletable materials that fill the Cell Universe. Only valid for + translation or rotation atttributes + """ + def __init__(self, cell, operator, model, bracket, bracket_limit, + density_treatment='constant-volume', bracketed_method='brentq', + tol=0.01, target=1.0, print_iterations=True, + search_for_keff_output=True): + + super().__init__(operator, model, bracket, bracket_limit, density_treatment, bracketed_method, tol, target, print_iterations, search_for_keff_output) - self.cell_id = super()._get_cell_id(cell_id_or_name) + self.cell = self._get_cell(cell) + + # list of material fill the attribute cell, if depletables + self.cell_materials = None + + @abstractmethod + def _get_cell_attrib(self): + """ + Get cell attribute coefficient. + Returns + ------- + coeff : float + cell coefficient + """ + + @abstractmethod + def _set_cell_attrib(self, val): + """ + Set cell attribute to the cell instance. + Parameters + ---------- + val : float + cell coefficient to set + """ + + def _get_cell(self, val): + """Helper method for getting cell from cell instance or cell name or id. + Parameters + ---------- + val : Openmc.Cell or str or int representing Cell + Returns + ------- + val : openmc.Cell + Openmc Cell + """ + if isinstance(val, Cell): + check_value('Cell exists', val, [cell for cell in \ + self.geometry.get_all_cells().values()]) + + elif isinstance(val, str): + if val.isnumeric(): + check_value('Cell id exists', int(val), [cell.id for cell in \ + self.geometry.get_all_cells().values()]) + val = [cell for cell in \ + self.geometry.get_all_cells().values() \ + if cell.id == int(val)][0] + else: + check_value('Cell name exists', val, [cell.name for cell in \ + self.geometry.get_all_cells().values()]) + + val = [cell for cell in \ + self.geometry.get_all_cells().values() \ + if cell.name == val][0] + + elif isinstance(val, int): + check_value('Cell id exists', val, [cell.id for cell in \ + self.geometry.get_all_cells().values()]) + val = [cell for cell in \ + self.geometry.get_all_cells().values() \ + if cell.id == val][0] + else: + ValueError(f'Cell: {val} is not supported') + + return val + + def _model_builder(self, param): + """ + Builds the parametric model that is passed to the `search_for_keff` + function by setting the parametric variable to the cell. + Parameters + ---------- + param : model parametricl variable + for examlple: cell translation coefficient + Returns + ------- + self.model : openmc.model.Model + OpenMC parametric model + """ + self._set_cell_attrib(param) + # At this stage it not important to reassign the new volume, as the + # nuclide densities remain constant. However, if at least one of the cell + # materials is set as depletable, the volume change needs to be accounted for + # as an increase or reduction of number of atoms, i.e. vector x, before + # solving the depletion equations + return self.model + + def search_for_keff(self, x, step_index): + """ + Perform the criticality search parametrizing the cell coefficient. + The `search_for_keff` solution is then set as the new cell attribute + Parameters + ---------- + x : list of numpy.ndarray + Total atoms concentrations + Returns + ------- + x : list of numpy.ndarray + Updated total atoms concentrations + """ + # Get cell attribute from previous iteration + val = self._get_cell_attrib() + check_type('Cell coeff', val, Real) + + # Update all material densities from concentration vectors + #before performing the search_for_keff. This is needed before running + # the transport equations in the search_for_keff algorithm + super()._update_materials(x) + + # Calculate new cell attribute + root = super()._search_for_keff(val) + + # set results value as attribute in the geometry + self._set_cell_attrib(root) + + # if at least one of the cell materials is depletable, calculate new + # volume and update x and number accordingly + # new volume + if self.cell_materials: + volumes = self._calculate_volumes() + x = super()._update_x_and_set_volumes(x, volumes) + + return x, root + + +class BatchwiseCellGeometrical(BatchwiseCell): + """ + Batchwise cell-based with geometrical-attribute class. + + A user doesn't need to call this class directly. + Instead an instance of this class is automatically created by calling + :meth:`openmc.deplete.Integrator.add_batchwise` method from an integrator + class, such as :class:`openmc.deplete.CECMIntegrator`. + + .. versionadded:: 0.13.4 + + Parameters + ---------- + cell : openmc.Cell or int or str + OpenMC Cell identifier to where apply batchwise scheme + attrib_name : str {'translation', 'rotation'} + Cell attribute type + operator : openmc.deplete.Operator + OpenMC operator object + model : openmc.model.Model + OpenMC model object + axis : int {0,1,2} + Directional axis for geometrical parametrization, where 0, 1 and 2 stand + for 'x', 'y' and 'z', respectively. + bracket : list of float + Bracketing interval to search for the solution, always relative to the + solution at previous step. + bracket_limit : list of float + Absolute bracketing interval lower and upper; if during the adaptive + algorithm the search_for_keff solution lies off these limits the closest + limit will be set as new result. + density_treatment : str + Wether ot not to keep contant volume or density after a depletion step + before the next one. + Default to 'constant-volume' + bracketed_method : {'brentq', 'brenth', 'ridder', 'bisect'}, optional + Solution method to use. + This is equivalent to the `bracket_method` parameter of the + `search_for_keff`. + Defaults to 'brentq'. + tol : float + Tolerance for search_for_keff method. + This is equivalent to the `tol` parameter of the `search_for_keff`. + Default to 0.01 + target : Real, optional + This is equivalent to the `target` parameter of the `search_for_keff`. + Default to 1.0. + print_iterations : Bool, Optional + Wheter or not to print `search_for_keff` iterations. + Default to True + search_for_keff_output : Bool, Optional + Wheter or not to print transport iterations during `search_for_keff`. + Default to False + Attributes + ---------- + cell : openmc.Cell or int or str + OpenMC Cell identifier to where apply batchwise scheme + attrib_name : str {'translation', 'rotation'} + Cell attribute type + axis : int {0,1,2} + Directional axis for geometrical parametrization, where 0, 1 and 2 stand + for 'x', 'y' and 'z', respectively. + """ + def __init__(self, cell, attrib_name, operator, model, axis, bracket, + bracket_limit, density_treatment='constant-volume', + bracketed_method='brentq', tol=0.01, target=1.0, + print_iterations=True, search_for_keff_output=True): + + super().__init__(cell, operator, model, bracket, bracket_limit, + density_treatment, bracketed_method, tol, target, + print_iterations, search_for_keff_output) + check_value('attrib_name', attrib_name, ('rotation', 'translation')) self.attrib_name = attrib_name + # check if cell is filled with 2 cells + check_length('fill materials', self.cell.fill.cells, 2) #index of cell directionnal axis check_value('axis', axis, (0,1,2)) self.axis = axis @@ -289,10 +643,15 @@ def __init__(self, operator, model, cell, attrib_name, axis, bracket, # Initialize vector self.vector = np.zeros(3) - # materials that fill the attribute cell, if depletables - self.cell_material_ids = [cell.fill.id for cell in \ - self.geometry.get_all_cells()[self.cell_id].fill.cells.values() \ - if cell.fill.depletable] + self.cell_materials = [cell.fill for cell in \ + self.cell.fill.cells.values() if cell.fill.depletable] + + if self.cell_materials: + self._initialize_volume_calc() + + @classmethod + def from_params(cls, obj, attr, operator, model, **kwargs): + return cls(obj, attr, operator, model, **kwargs) def _get_cell_attrib(self): """ @@ -303,7 +662,7 @@ def _get_cell_attrib(self): cell coefficient """ for cell in openmc.lib.cells.values(): - if cell.id == self.cell_id: + if cell.id == self.cell.id: if self.attrib_name == 'translation': return cell.translation[self.axis] elif self.attrib_name == 'rotation': @@ -315,92 +674,279 @@ def _set_cell_attrib(self, val): Attributes are only applied to cells filled with a universe Parameters ---------- - var : float - Surface coefficient to set - geometry : openmc.model.geometry - OpenMC geometry model - attrib_name : str - Currently only translation is implemented + val : float + Cell coefficient to set in cm for translation and def for rotation """ self.vector[self.axis] = val for cell in openmc.lib.cells.values(): - if cell.id == self.cell_id or cell.name == self.cell_id: + if cell.id == self.cell.id: setattr(cell, self.attrib_name, self.vector) - def _update_materials(self, x): + def _initialize_volume_calc(self): + """ + Set volume calculation model settings of depletable materials filling + the parametric Cell. + """ + ll, ur = self.geometry.bounding_box + mat_vol = openmc.VolumeCalculation(self.cell_materials, 100000, ll, ur) + self.model.settings.volume_calculations = mat_vol + + def _calculate_volumes(self): + """ + Perform stochastic volume calculation + Returns + ------- + volumes : dict + Dictionary of calculate volumes, where key is the mat id """ - Assign concentration vectors from Bateman solution at previous - timestep to the in-memory model materials, after having recalculated the - material volume. + openmc.lib.calculate_volumes() + volumes = {} + res = openmc.VolumeCalculation.from_hdf5('volume_1.h5') + for mat_idx, mat in enumerate(self.local_mats): + if int(mat) in [mat.id for mat in self.cell_materials]: + volumes[mat] = res.volumes[int(mat)].n + return volumes + +class BatchwiseCellTemperature(BatchwiseCell): + """ + Batchwise cell-based with temperature-attribute class. + + A user doesn't need to call this class directly. + Instead an instance of this class is automatically created by calling + :meth:`openmc.deplete.Integrator.add_batchwise` method from an integrator + class, such as :class:`openmc.deplete.CECMIntegrator`. + + .. versionadded:: 0.13.4 + + Parameters + ---------- + cell : openmc.Cell or int or str + OpenMC Cell identifier to where apply batchwise scheme + operator : openmc.deplete.Operator + OpenMC operator object + model : openmc.model.Model + OpenMC model object + bracket : list of float + Bracketing interval to search for the solution, always relative to the + solution at previous step. + bracket_limit : list of float + Absolute bracketing interval lower and upper; if during the adaptive + algorithm the search_for_keff solution lies off these limits the closest + limit will be set as new result. + density_treatment : str + Wether ot not to keep contant volume or density after a depletion step + before the next one. + Default to 'constant-volume' + bracketed_method : {'brentq', 'brenth', 'ridder', 'bisect'}, optional + Solution method to use. + This is equivalent to the `bracket_method` parameter of the + `search_for_keff`. + Defaults to 'brentq'. + tol : float + Tolerance for search_for_keff method. + This is equivalent to the `tol` parameter of the `search_for_keff`. + Default to 0.01 + target : Real, optional + This is equivalent to the `target` parameter of the `search_for_keff`. + Default to 1.0. + print_iterations : Bool, Optional + Wheter or not to print `search_for_keff` iterations. + Default to True + search_for_keff_output : Bool, Optional + Wheter or not to print transport iterations during `search_for_keff`. + Default to False + Attributes + ---------- + cell : openmc.Cell or int or str + OpenMC Cell identifier to where apply batchwise scheme + """ + def __init__(self, cell, operator, model, bracket, bracket_limit, + density_treatment='constant-volume', bracketed_method='brentq', + tol=0.01, target=1.0, print_iterations=True, + search_for_keff_output=True): + + super().__init__(cell, operator, model, bracket, bracket_limit, + density_treatment, bracketed_method, tol, target, + print_iterations, search_for_keff_output) + + # check if initial temperature has been set to right cell material + if isinstance(self.cell.fill, openmc.Universe): + cells = [cell for cell in self.cell.fill.cells.values() \ + if cell.fill.temperature] + check_length('Only one cell with temperature',cells,1) + self.cell = cells[0] + + check_type('temperature cell real', self.cell.fill.temperature, Real) + + @classmethod + def from_params(cls, obj, attr, operator, model, **kwargs): + return cls(obj, operator, model, **kwargs) + def _get_cell_attrib(self): + """ + Get cell attribute coefficient. + Returns + ------- + coeff : float + cell temperature in Kelvin + """ + for cell in openmc.lib.cells.values(): + if cell.id == self.cell.id: + return cell.get_temperature() + + def _set_cell_attrib(self, val): + """ + Set cell attribute to the cell instance. Parameters ---------- - x : list of numpy.ndarray - Total atom concentrations + val : float + Cell temperature to set in Kelvin """ - super()._update_volumes_after_depletion(x) + for cell in openmc.lib.cells.values(): + if cell.id == self.cell.id: + cell.set_temperature(val) + +class BatchwiseMaterial(Batchwise): + """Abstract class holding batchwise material-based functions. + + Specific classes for running batchwise depletion calculations are + implemented as derived class of BatchwiseMaterial. + + .. versionadded:: 0.13.4 + + Parameters + ---------- + material : openmc.Material or int or str + OpenMC Material identifier to where apply batchwise scheme + operator : openmc.deplete.Operator + OpenMC operator object + model : openmc.model.Model + OpenMC model object + mat_vector : dict + Dictionary of material composition to paremetrize, where a pair key, value + represents a nuclide and its weight fraction. + bracket : list of float + Bracketing interval to search for the solution, always relative to the + solution at previous step. + bracket_limit : list of float + Absolute bracketing interval lower and upper; if during the adaptive + algorithm the search_for_keff solution lies off these limits the closest + limit will be set as new result. + density_treatment : str + Wether ot not to keep contant volume or density after a depletion step + before the next one. + Default to 'constant-volume' + bracketed_method : {'brentq', 'brenth', 'ridder', 'bisect'}, optional + Solution method to use. + This is equivalent to the `bracket_method` parameter of the + `search_for_keff`. + Defaults to 'brentq'. + tol : float + Tolerance for search_for_keff method. + This is equivalent to the `tol` parameter of the `search_for_keff`. + Default to 0.01 + target : Real, optional + This is equivalent to the `target` parameter of the `search_for_keff`. + Default to 1.0. + print_iterations : Bool, Optional + Wheter or not to print `search_for_keff` iterations. + Default to True + search_for_keff_output : Bool, Optional + Wheter or not to print transport iterations during `search_for_keff`. + Default to False + Attributes + ---------- + material : openmc.Material or int or str + OpenMC Material identifier to where apply batchwise scheme + mat_vector : dict + Dictionary of material composition to paremetrize, where a pair key, value + represents a nuclide and its weight fraction. + """ + + def __init__(self, material, operator, model, mat_vector, bracket, + bracket_limit, density_treatment='constant-volume', + bracketed_method='brentq', tol=0.01, target=1.0, + print_iterations=True, search_for_keff_output=True): - for rank in range(comm.size): - number_i = comm.bcast(self.operator.number, root=rank) + super().__init__(operator, model, bracket, bracket_limit, + density_treatment, bracketed_method, tol, target, + print_iterations, search_for_keff_output) - for mat in number_i.materials: - nuclides = [] - densities = [] + self.material = self._get_material(material) - for nuc in number_i.nuclides: - # get atom density in atoms/b-cm - val = 1.0e-24 * number_i.get_atom_density(mat, nuc) - if nuc in self.operator.nuclides_with_data: - if val > self.atom_density_limit: - nuclides.append(nuc) - densities.append(val) + check_type("material vector", mat_vector, dict, str) + for nuc in mat_vector.keys(): + check_value("check nuclide exists", nuc, self.operator.nuclides_with_data) - #set nuclide densities to model in memory (C-API) - openmc.lib.materials[int(mat)].set_densities(nuclides, densities) + if round(sum(mat_vector.values()), 2) != 1.0: + # Normalize material elements vector + sum_values = sum(mat_vector.values()) + for elm in mat_vector: + mat_vector[elm] /= sum_values + self.mat_vector = mat_vector - def _update_volumes(self): - openmc.lib.calculate_volumes() - res = openmc.VolumeCalculation.from_hdf5('volume_1.h5') + @classmethod + def from_params(cls, obj, attr, operator, model, **kwargs): + return cls(obj, operator, model, **kwargs) - number_i = self.operator.number - for mat_idx, mat_id in enumerate(self.local_mats): - if mat_id in self.cell_material_ids: - number_i.volume[mat_idx] = res.volumes[int(mat_id)].n + def _get_material(self, val): + """Helper method for getting openmc material from Material instance or + material name or id. + Parameters + ---------- + val : Openmc.Material or str or int representing material name/id + Returns + ------- + val : openmc.Material + Openmc Material + """ + if isinstance(val, Material): + check_value('Material', str(val.id), self.burn_mats) - def _update_x(self): - number_i = self.operator.number - for mat_idx, mat_id in enumerate(self.local_mats): - if mat_id in self.cell_material_ids: - for nuc_idx, nuc in enumerate(number_i.burnable_nuclides): - x[mat_idx][nuc_idx] = number_i.volume[mat_idx] * \ - number_i.get_atom_density(mat_idx, nuc) - return x + elif isinstance(val, str): + if val.isnumeric(): + check_value('Material id', val, self.burn_mats) + val = [mat for mat in self.model.materials \ + if mat.id == int(val)][0] + + else: + check_value('Material name', val, + [mat.name for mat in self.model.materials if mat.depletable]) + val = [mat for mat in self.model.materials \ + if mat.name == val][0] + + elif isinstance(val, int): + check_value('Material id', str(val), self.burn_mats) + val = [mat for mat in self.model.materials \ + if mat.id == val][0] + else: + ValueError(f'Material: {val} is not supported') + + return val + + @abstractmethod def _model_builder(self, param): """ Builds the parametric model that is passed to the `msr_search_for_keff` - function by setting the parametric variable to the geoemetrical cell. + function by updating the material densities and setting the parametric + variable as function of the nuclides vector. Since this is a paramteric + material addition (or removal), we can parametrize the volume as well. Parameters ---------- - param : model parametricl variable - for examlple: cell translation coefficient + param : + Model material function variable Returns ------- _model : openmc.model.Model - OpenMC parametric model + Openmc parametric model """ - self._set_cell_attrib(param) - #Calulate new volume and update if materials filling the cell are - # depletable - if self.cell_material_ids: - self._update_volumes() - return self.model def search_for_keff(self, x, step_index): """ - Perform the criticality search on the parametric geometrical model. - Will set the root of the `search_for_keff` function to the cell - attribute. + Perform the criticality search on the parametric material model. + Will set the root of the `search_for_keff` function to the atoms + concentrations vector. Parameters ---------- x : list of numpy.ndarray @@ -410,26 +956,168 @@ def search_for_keff(self, x, step_index): x : list of numpy.ndarray Updated total atoms concentrations """ - # Get cell attribute from previous iteration - val = self._get_cell_attrib() - check_type('Cell coeff', val, Real) + # Update AtomNumber with new conc vectors x. Materials are also updated + # even though they are also re-calculated when running the search_for_kef + super()._update_materials(x) + + # Solve search_for_keff and find new value + root = super()._search_for_keff(0) + + #Update concentration vector and volumes with new value + volumes = self._calculate_volumes(root) + x = super()._update_x_and_set_volumes(x, volumes) + + return x, root + +class BatchwiseMaterialRefuel(BatchwiseMaterial): + """ + Batchwise material-based class for refueling (addition or removal) scheme. + + A user doesn't need to call this class directly. + Instead an instance of this class is automatically created by calling + :meth:`openmc.deplete.Integrator.add_batchwise` method from an integrator + class, such as :class:`openmc.deplete.CECMIntegrator`. + + .. versionadded:: 0.13.4 + + Parameters + ---------- + material : openmc.Material or int or str + OpenMC Material identifier to where apply batchwise scheme + operator : openmc.deplete.Operator + OpenMC operator object + model : openmc.model.Model + OpenMC model object + mat_vector : dict + Dictionary of material composition to paremetrize, where a pair key, value + represents a nuclide and its weight fraction. + bracket : list of float + Bracketing interval to search for the solution, always relative to the + solution at previous step. + bracket_limit : list of float + Absolute bracketing interval lower and upper; if during the adaptive + algorithm the search_for_keff solution lies off these limits the closest + limit will be set as new result. + density_treatment : str + Wether ot not to keep contant volume or density after a depletion step + before the next one. + Default to 'constant-volume' + bracketed_method : {'brentq', 'brenth', 'ridder', 'bisect'}, optional + Solution method to use. + This is equivalent to the `bracket_method` parameter of the + `search_for_keff`. + Defaults to 'brentq'. + tol : float + Tolerance for search_for_keff method. + This is equivalent to the `tol` parameter of the `search_for_keff`. + Default to 0.01 + target : Real, optional + This is equivalent to the `target` parameter of the `search_for_keff`. + Default to 1.0. + print_iterations : Bool, Optional + Wheter or not to print `search_for_keff` iterations. + Default to True + search_for_keff_output : Bool, Optional + Wheter or not to print transport iterations during `search_for_keff`. + Default to False + Attributes + ---------- + material : openmc.Material or int or str + OpenMC Material identifier to where apply batchwise scheme + mat_vector : dict + Dictionary of material composition to paremetrize, where a pair key, value + represents a nuclide and its weight fraction. + """ + + def __init__(self, material, operator, model, mat_vector, bracket, + bracket_limit, density_treatment='constant-volume', + bracketed_method='brentq', tol=0.01, target=1.0, + print_iterations=True, search_for_keff_output=True): - # Update volume and concentration vectors before performing the search_for_keff - self._update_materials(x) + super().__init__(material, operator, model, mat_vector, bracket, + bracket_limit, density_treatment, bracketed_method, tol, + target, print_iterations, search_for_keff_output) - # Calculate new cell attribute - root = super().search_for_keff(val) + @classmethod + def from_params(cls, obj, attr, operator, model, **kwargs): + return cls(obj, operator, model, **kwargs) - # set results value as attribute in the geometry - self._set_cell_attrib(root) - print('UPDATE: old value: {:.2f} cm --> ' \ - 'new value: {:.2f} cm'.format(val, root)) + def _model_builder(self, param): + """ + Builds the parametric model that is passed to the `msr_search_for_keff` + function by updating the material densities and setting the parametric + variable to the material nuclides to add. We can either fix the total + volume or the material density according to user input. + Parameters + ---------- + param : + Model function variable, total grams of material to add or remove + Returns + ------- + model : openmc.model.Model + OpenMC parametric model + """ + for rank in range(comm.size): + number_i = comm.bcast(self.operator.number, root=rank) - # x needs to be updated after the search with new volumes if materials cell - # depletable - if self.cell_material_ids: - self._update_x() + for mat in number_i.materials: + nuclides = [] + densities = [] - #Store results - super()._save_res('geometry', step_index, root) - return x + if int(mat) == self.material.id: + + if self.density_treatment == 'constant-density': + vol = number_i.get_mat_volume(mat) + \ + (param / self.material.get_mass_density()) + + elif self.density_treatment == 'constant-volume': + vol = number_i.get_mat_volume(mat) + + for nuc in number_i.index_nuc: + # check only nuclides with cross sections data + if nuc in self.operator.nuclides_with_data: + if nuc in self.mat_vector: + # units [#atoms/cm-b] + val = 1.0e-24 * (number_i.get_atom_density(mat, + nuc) + param / atomic_mass(nuc) * \ + AVOGADRO * self.mat_vector[nuc] / vol) + + else: + # get normalized atoms density in [atoms/b-cm] + val = 1.0e-24 * number_i[mat, nuc] / vol + + if val > 0.0: + nuclides.append(nuc) + densities.append(val) + + else: + # for all other materials, still check atom density limits + for nuc in number_i.nuclides: + if nuc in self.operator.nuclides_with_data: + # get normalized atoms density in [atoms/b-cm] + val = 1.0e-24 * number_i.get_atom_density(mat, nuc) + + if val > 0.0: + nuclides.append(nuc) + densities.append(val) + + #set nuclides and densities to the in-memory model + openmc.lib.materials[int(mat)].set_densities(nuclides, densities) + + # alwyas need to return a model + return self.model + + def _calculate_volumes(self, res): + """ + """ + number_i = self.operator.number + volumes = {} + + for mat in self.local_mats: + if int(mat) == self.material.id: + if self.density_treatment == 'constant-density': + volumes[mat] = number_i.get_mat_volume(mat) + \ + (res / self.material.get_mass_density()) + elif self.density_treatment == 'constant-volume': + volumes[mat] = number_i.get_mat_volume(mat) + return volumes diff --git a/openmc/deplete/stepresult.py b/openmc/deplete/stepresult.py index 9af6b5fee76..b8648fb0ba4 100644 --- a/openmc/deplete/stepresult.py +++ b/openmc/deplete/stepresult.py @@ -74,7 +74,7 @@ def __init__(self): self.mat_to_hdf5_ind = None self.data = None - + self.batchwise = None def __repr__(self): t = self.time[0] dt = self.time[1] - self.time[0] @@ -337,6 +337,10 @@ def _write_hdf5_metadata(self, handle): "depletion time", (1,), maxshape=(None,), dtype="float64") + handle.create_dataset( + "batchwise_root", (1,), maxshape=(None,), + dtype="float64") + def _to_hdf5(self, handle, index, parallel=False): """Converts results object into an hdf5 object. @@ -367,6 +371,7 @@ def _to_hdf5(self, handle, index, parallel=False): time_dset = handle["/time"] source_rate_dset = handle["/source_rate"] proc_time_dset = handle["/depletion time"] + root_dset = handle["/batchwise_root"] # Get number of results stored number_shape = list(number_dset.shape) @@ -400,6 +405,10 @@ def _to_hdf5(self, handle, index, parallel=False): proc_shape[0] = new_shape proc_time_dset.resize(proc_shape) + root_shape = list(root_dset.shape) + root_shape[0] = new_shape + root_dset.resize(root_shape) + # If nothing to write, just return if len(self.index_mat) == 0: return @@ -423,6 +432,7 @@ def _to_hdf5(self, handle, index, parallel=False): proc_time_dset[index] = ( self.proc_time / (comm.size * self.n_hdf5_mats) ) + root_dset[index] = self.batchwise @classmethod def from_hdf5(cls, handle, step): @@ -446,11 +456,13 @@ def from_hdf5(cls, handle, step): else: # Older versions used "power" instead of "source_rate" source_rate_dset = handle["/power"] + root_dset = handle["/batchwise_root"] results.data = number_dset[step, :, :, :] results.k = eigenvalues_dset[step, :] results.time = time_dset[step, :] results.source_rate = source_rate_dset[step, 0] + results.batchwise = root_dset[step] if "depletion time" in handle: proc_time_dset = handle["/depletion time"] @@ -497,7 +509,7 @@ def from_hdf5(cls, handle, step): @staticmethod def save(op, x, op_results, t, source_rate, step_ind, proc_time=None, - path: PathLike = "depletion_results.h5"): + root=None, path: PathLike = "depletion_results.h5"): """Creates and writes depletion results to disk Parameters @@ -552,6 +564,7 @@ def save(op, x, op_results, t, source_rate, step_ind, proc_time=None, results.proc_time = proc_time if results.proc_time is not None: results.proc_time = comm.reduce(proc_time, op=MPI.SUM) + results.batchwise = root if not Path(path).is_file(): Path(path).parent.mkdir(parents=True, exist_ok=True) diff --git a/openmc/search.py b/openmc/search.py index 7f6b3b5b9a4..4818fbe10bd 100644 --- a/openmc/search.py +++ b/openmc/search.py @@ -12,7 +12,7 @@ def _search_keff(guess, target, model_builder, model_args, print_iterations, - run_args, guesses, results): + run_args, guesses, results, run_in_memory): """Function which will actually create our model, run the calculation, and obtain the result. This function will be passed to the root finding algorithm @@ -51,7 +51,11 @@ def _search_keff(guess, target, model_builder, model_args, print_iterations, model = model_builder(guess, **model_args) # Run the model and obtain keff - sp_filepath = model.run(**run_args) + if run_in_memory: + openmc.lib.run(**run_args) + sp_filepath = f'statepoint.{model.settings.batches}.h5' + else: + sp_filepath = model.run(**run_args) with openmc.StatePoint(sp_filepath) as sp: keff = sp.keff @@ -70,7 +74,7 @@ def _search_keff(guess, target, model_builder, model_args, print_iterations, def search_for_keff(model_builder, initial_guess=None, target=1.0, bracket=None, model_args=None, tol=None, bracketed_method='bisect', print_iterations=False, - run_args=None, **kwargs): + run_args=None, run_in_memory=False, **kwargs): """Function to perform a keff search by modifying a model parametrized by a single independent variable. @@ -193,12 +197,26 @@ def search_for_keff(model_builder, initial_guess=None, target=1.0, # Add information to be passed to the searching function args['args'] = (target, model_builder, model_args, print_iterations, - run_args, guesses, results) + run_args, guesses, results, run_in_memory) # Create a new dictionary with the arguments from args and kwargs args.update(kwargs) # Perform the search - zero_value = root_finder(**args) + if not run_in_memory: + zero_value = root_finder(**args) + return zero_value - return zero_value, guesses, results + else: + try: + zero_value, root_res = root_finder(**args, full_output=True, disp=False) + if root_res.converged: + return zero_value, guesses, results + + else: + print(f'WARNING: {root_res.flag}') + return guesses, results + # In case the root finder is not successful + except Exception as e: + print(f'WARNING: {e}') + return guesses, results diff --git a/tests/unit_tests/test_deplete_batchwise.py b/tests/unit_tests/test_deplete_batchwise.py new file mode 100644 index 00000000000..7c05786fa5f --- /dev/null +++ b/tests/unit_tests/test_deplete_batchwise.py @@ -0,0 +1,135 @@ +""" Tests for Batchwise class """ + +from pathlib import Path +from math import exp + +import pytest +import numpy as np + +import openmc +import openmc.lib +from openmc.deplete import CoupledOperator +from openmc.deplete import (BatchwiseCellGeometrical, BatchwiseCellTemperature, + BatchwiseMaterialRefuel) + +CHAIN_PATH = Path(__file__).parents[1] / "chain_simple.xml" + +@pytest.fixture +def model(): + f = openmc.Material(name="f") + f.add_element("U", 1, percent_type="ao", enrichment=4.25) + f.add_element("O", 2) + f.set_density("g/cc", 10.4) + f.temperature = 293.15 + w = openmc.Material(name="w") + w.add_element("O", 1) + w.add_element("H", 2) + w.set_density("g/cc", 1.0) + w.depletable = True + h = openmc.Material(name='h') + h.set_density('g/cm3', 0.001598) + h.add_element('He', 2.4044e-4) + radii = [0.42, 0.45] + f.volume = np.pi * radii[0] ** 2 + w.volume = np.pi * (radii[1]**2 - radii[0]**2) + materials = openmc.Materials([f, w, h]) + surf_wh = openmc.ZPlane(z0=0) + surf_f = openmc.Sphere(r=radii[0]) + surf_w = openmc.Sphere(r=radii[1], boundary_type='vacuum') + cell_w = openmc.Cell(fill=w, region=-surf_wh) + cell_h = openmc.Cell(fill=h, region=+surf_wh) + uni_wh = openmc.Universe(cells=(cell_w, cell_h)) + cell_f = openmc.Cell(name='f',fill=f, region=-surf_f) + cell_wh = openmc.Cell(name='wh',fill=uni_wh, region=+surf_f & -surf_w) + geometry = openmc.Geometry([cell_f, cell_wh]) + settings = openmc.Settings() + settings.particles = 1000 + settings.inactive = 10 + settings.batches = 50 + return openmc.Model(geometry, materials, settings) + +@pytest.mark.parametrize("object_name, attribute", [ + ('wh', 'translation'), + ('wh', 'rotation'), + ('f', 'temperature'), + ('f', 'refuel'), + ]) +def test_attributes(model, object_name, attribute): + """ + Test classes attributes are set correctly + """ + op = CoupledOperator(model, CHAIN_PATH) + + bracket = [-1,1] + bracket_limit = [-10,10] + axis = 2 + mat_vector = {'U235':0.1, 'U238':0.9} + if attribute in ('translation','rotation'): + bw = BatchwiseCellGeometrical(object_name, attribute, op, model, axis, bracket, bracket_limit) + assert bw.cell_materials == [cell.fill for cell in \ + model.geometry.get_cells_by_name(object_name)[0].fill.cells.values() \ + if cell.fill.depletable] + assert bw.attrib_name == attribute + assert bw.axis == axis + + elif attribute == 'temperature': + bw = BatchwiseCellTemperature(object_name, op, model, bracket, bracket_limit) + elif attribute == 'refuel': + bw = BatchwiseMaterialRefuel(object_name, op, model, mat_vector, bracket, bracket_limit) + assert bw.mat_vector == mat_vector + + assert bw.bracket == bracket + assert bw.bracket_limit == bracket_limit + assert bw.burn_mats == op.burnable_mats + assert bw.local_mats == op.local_mats + +@pytest.mark.parametrize("attribute", [ + ('translation'), + ('rotation') + ]) +def test_cell_methods(run_in_tmpdir, model, attribute): + """ + Test cell base class internal method + """ + bracket = [-1,1] + bracket_limit = [-10,10] + axis = 2 + op = CoupledOperator(model, CHAIN_PATH) + integrator = openmc.deplete.PredictorIntegrator( + op, [1,1], 0.0, timestep_units = 'd') + integrator.add_batchwise('wh', attribute, axis=axis, bracket=bracket, + bracket_limit=bracket_limit) + + model.export_to_xml() + openmc.lib.init() + integrator.batchwise._set_cell_attrib(0) + assert integrator.batchwise._get_cell_attrib() == 0 + vol = integrator.batchwise._calculate_volumes() + for cell in integrator.batchwise.cell_materials: + assert vol[str(cell.id)] == pytest.approx([mat.volume for mat in model.materials if mat.id == cell.id]) + openmc.lib.finalize() + +def test_update_materials_methods(run_in_tmpdir): + """ + It's the same methods for all classe so one class is enough + """ + bracket = [-1,1] + bracket_limit = [-10,10] + axis = 2 + op = CoupledOperator(model, CHAIN_PATH) + integrator = openmc.deplete.PredictorIntegrator( + op, [1,1], 0.0, timestep_units = 'd') + integrator.add_batchwise('wh', 'translation', axis=axis, bracket=bracket, + bracket_limit=bracket_limit) + + model.export_to_xml() + openmc.lib.init() + x = op.number.number + #Double number of atoms of U238 in fuel + mat_index = op.number.index_mat['1'] + nuc_index = op.number.index_nuc['U238'] + vol = op.number.get_mat_volume('1') + op.number.number[mat_index][nuc_idx] *= 2 + integrator.batchwise._update_volumes(op.number.number) + + vol = integrator.batchwise._calculate_volumes() From f561da13113a69d477b53c8fdb9353b07acea36e Mon Sep 17 00:00:00 2001 From: church89 Date: Tue, 5 Sep 2023 10:57:06 +0200 Subject: [PATCH 03/59] update unit test --- openmc/deplete/batchwise.py | 2 +- tests/unit_tests/test_deplete_batchwise.py | 87 ++++++++++++++++++++-- 2 files changed, 80 insertions(+), 9 deletions(-) diff --git a/openmc/deplete/batchwise.py b/openmc/deplete/batchwise.py index 7136f7e6b21..b3a2b69f0e4 100644 --- a/openmc/deplete/batchwise.py +++ b/openmc/deplete/batchwise.py @@ -291,7 +291,7 @@ def _update_volumes(self): # Get mass dens from beginning, intended to be held constant density = openmc.lib.materials[int(mat)].get_density('g/cm3') number_i.volume[mat_idx] = agpm / AVOGADRO / density - + def _update_materials(self, x): """ Update number density and material compositions in OpenMC on all processes. diff --git a/tests/unit_tests/test_deplete_batchwise.py b/tests/unit_tests/test_deplete_batchwise.py index 7c05786fa5f..37e14009852 100644 --- a/tests/unit_tests/test_deplete_batchwise.py +++ b/tests/unit_tests/test_deplete_batchwise.py @@ -106,12 +106,13 @@ def test_cell_methods(run_in_tmpdir, model, attribute): assert integrator.batchwise._get_cell_attrib() == 0 vol = integrator.batchwise._calculate_volumes() for cell in integrator.batchwise.cell_materials: - assert vol[str(cell.id)] == pytest.approx([mat.volume for mat in model.materials if mat.id == cell.id]) + assert vol[str(cell.id)] == pytest.approx([mat.volume for mat in model.materials if mat.id == cell.id][0]) openmc.lib.finalize() -def test_update_materials_methods(run_in_tmpdir): +def test_update_volumes(run_in_tmpdir): """ - It's the same methods for all classe so one class is enough + Method to update volume in AtomNumber after depletion step. + Method inheritated by all derived classes so one check is enough. """ bracket = [-1,1] bracket_limit = [-10,10] @@ -124,12 +125,82 @@ def test_update_materials_methods(run_in_tmpdir): model.export_to_xml() openmc.lib.init() - x = op.number.number - #Double number of atoms of U238 in fuel + + #Increase number of atoms of U238 in fuel by fix amount and check the + # volume increase at constant-density mat_index = op.number.index_mat['1'] nuc_index = op.number.index_nuc['U238'] vol = op.number.get_mat_volume('1') - op.number.number[mat_index][nuc_idx] *= 2 - integrator.batchwise._update_volumes(op.number.number) + atoms_to_add = 1e22 + op.number.number[mat_index][nuc_idx] += atoms_to_add + integrator.batchwise._update_volumes() - vol = integrator.batchwise._calculate_volumes() + atoms_to_vol = atoms_to_add * openmc.data.atomic_mass('U238') \ + /openmc.data.AVOGADRO / 10.4 + assert op.number.get_mat_volume('1') == vol + atoms_to_vol + + openmc.lib.finalize() + +# def test_update_materials(run_in_tmpdir): +# """ +# Method to update volume in AtomNumber after depletion step if 'constant-density' +# is passed to the batchwise instance. +# Method inheritated by all derived classes so one check is enough. +# """ +# bracket = [-1,1] +# bracket_limit = [-10,10] +# axis = 2 +# op = CoupledOperator(model, CHAIN_PATH) +# integrator = openmc.deplete.PredictorIntegrator( +# op, [1,1], 0.0, timestep_units = 'd') +# integrator.add_batchwise('wh', 'translation', axis=axis, bracket=bracket, +# bracket_limit=bracket_limit) +# +# model.export_to_xml() +# openmc.lib.init() +# +# #Increase number of atoms of U238 in fuel by fix amount and check that the +# # densities in openmc.lib have beeen updated +# mat_index = op.number.index_mat['1'] +# nuc_index = op.number.index_nuc['U238'] +# vol = op.number.get_mat_volume('1') +# atoms_to_add = 1e20 +# op.number.number[mat_index][nuc_idx] += atoms_to_add +# integrator.batchwise._update_materials(op.number.number) +# +# assert openmc.lib.materials[1][] == op.number.get_atom_density('1','U238') +# +# openmc.lib.finalize() +# +# def test_update_x_and_set_volumes_method(run_in_tmpdir): +# """ +# Method to update volume in AtomNumber after depletion step if 'constant-density' +# is passed to the batchwise instance. +# Method inheritated by all derived classes so one check is enough. +# """ +# bracket = [-1,1] +# bracket_limit = [-10,10] +# axis = 2 +# op = CoupledOperator(model, CHAIN_PATH) +# integrator = openmc.deplete.PredictorIntegrator( +# op, [1,1], 0.0, timestep_units = 'd') +# integrator.add_batchwise('wh', 'translation', axis=axis, bracket=bracket, +# bracket_limit=bracket_limit) +# +# model.export_to_xml() +# openmc.lib.init() +# +# #Increase number of atoms of U238 in fuel by fix amount and check that the +# # densities in openmc.lib have beeen updated +# mat_index = op.number.index_mat['1'] +# nuc_index = op.number.index_nuc['U238'] +# vol = op.number.get_mat_volume('1') +# atoms_to_add = 1e20 +# vol_to_add = 10 +# op.number.number[mat_index][nuc_idx] += atoms_to_add +# op.number.volumes[mat_index] += vol_to_add +# integrator.batchwise._update_x_and_set_volumes(op.number.number, op.number.volumes) +# +# assert +# +# openmc.lib.finalize() From 652df2b158613f5b73a473584b876f9848b81e09 Mon Sep 17 00:00:00 2001 From: church89 Date: Tue, 5 Sep 2023 14:04:43 +0200 Subject: [PATCH 04/59] add test unit --- tests/unit_tests/test_deplete_batchwise.py | 74 +++++++++++----------- 1 file changed, 38 insertions(+), 36 deletions(-) diff --git a/tests/unit_tests/test_deplete_batchwise.py b/tests/unit_tests/test_deplete_batchwise.py index 37e14009852..6e3cce7854c 100644 --- a/tests/unit_tests/test_deplete_batchwise.py +++ b/tests/unit_tests/test_deplete_batchwise.py @@ -116,11 +116,10 @@ def test_update_volumes(run_in_tmpdir): """ bracket = [-1,1] bracket_limit = [-10,10] - axis = 2 op = CoupledOperator(model, CHAIN_PATH) integrator = openmc.deplete.PredictorIntegrator( op, [1,1], 0.0, timestep_units = 'd') - integrator.add_batchwise('wh', 'translation', axis=axis, bracket=bracket, + integrator.add_batchwise('f', 'refuel', mat_vector={}, bracket=bracket, bracket_limit=bracket_limit) model.export_to_xml() @@ -128,49 +127,52 @@ def test_update_volumes(run_in_tmpdir): #Increase number of atoms of U238 in fuel by fix amount and check the # volume increase at constant-density - mat_index = op.number.index_mat['1'] + #extract fuel material from model materials + mat = integrator.batchwise.material + mat_index = op.number.index_mat[str(mat.id)] nuc_index = op.number.index_nuc['U238'] - vol = op.number.get_mat_volume('1') + vol = op.number.get_mat_volume(str(mat.id)) atoms_to_add = 1e22 op.number.number[mat_index][nuc_idx] += atoms_to_add integrator.batchwise._update_volumes() - + atoms_to_vol = atoms_to_add * openmc.data.atomic_mass('U238') \ - /openmc.data.AVOGADRO / 10.4 + /openmc.data.AVOGADRO / mat.density assert op.number.get_mat_volume('1') == vol + atoms_to_vol openmc.lib.finalize() -# def test_update_materials(run_in_tmpdir): -# """ -# Method to update volume in AtomNumber after depletion step if 'constant-density' -# is passed to the batchwise instance. -# Method inheritated by all derived classes so one check is enough. -# """ -# bracket = [-1,1] -# bracket_limit = [-10,10] -# axis = 2 -# op = CoupledOperator(model, CHAIN_PATH) -# integrator = openmc.deplete.PredictorIntegrator( -# op, [1,1], 0.0, timestep_units = 'd') -# integrator.add_batchwise('wh', 'translation', axis=axis, bracket=bracket, -# bracket_limit=bracket_limit) -# -# model.export_to_xml() -# openmc.lib.init() -# -# #Increase number of atoms of U238 in fuel by fix amount and check that the -# # densities in openmc.lib have beeen updated -# mat_index = op.number.index_mat['1'] -# nuc_index = op.number.index_nuc['U238'] -# vol = op.number.get_mat_volume('1') -# atoms_to_add = 1e20 -# op.number.number[mat_index][nuc_idx] += atoms_to_add -# integrator.batchwise._update_materials(op.number.number) -# -# assert openmc.lib.materials[1][] == op.number.get_atom_density('1','U238') -# -# openmc.lib.finalize() +def test_update_materials(run_in_tmpdir): + """ + Method to update volume in AtomNumber after depletion step if 'constant-density' + is passed to the batchwise instance. + Method inheritated by all derived classes so one check is enough. + """ + bracket = [-1,1] + bracket_limit = [-10,10] + op = CoupledOperator(model, CHAIN_PATH) + integrator = openmc.deplete.PredictorIntegrator( + op, [1,1], 0.0, timestep_units = 'd') + integrator.add_batchwise('f', 'refuel', mat_vector={}, bracket=bracket, + bracket_limit=bracket_limit) + + model.export_to_xml() + openmc.lib.init() + + #Increase number of atoms of U238 in fuel by fix amount and check that the + # densities in openmc.lib have beeen updated + mat = integrator.batchwise.material + nuc_index = op.number.index_nuc['U238'] + vol = op.number.get_mat_volume(str(mat.id)) + op.number.number[mat_index][nuc_idx] += 1e22 + x= [op.number.number[i][op.number.number[i].nonzero()] for i in range(len(op.number.number))] + integrator.batchwise._update_materials(x) + + nuc_index_lib = openmc.lib.materials[mat.id].nuclides.index('U238') + assert openmc.lib.materials[mat.id].densities[nuc_index_lib] == \ + 1e-24 * op.number.get_atom_density('1','U238') + + openmc.lib.finalize() # # def test_update_x_and_set_volumes_method(run_in_tmpdir): # """ From c08b762d8fa366cab6218c94965b414399c043b1 Mon Sep 17 00:00:00 2001 From: church89 Date: Wed, 6 Sep 2023 09:13:14 +0200 Subject: [PATCH 05/59] unit test update --- openmc/deplete/batchwise.py | 6 +- tests/unit_tests/test_deplete_batchwise.py | 110 ++++++++++++--------- 2 files changed, 68 insertions(+), 48 deletions(-) diff --git a/openmc/deplete/batchwise.py b/openmc/deplete/batchwise.py index b3a2b69f0e4..e5eff7647b6 100644 --- a/openmc/deplete/batchwise.py +++ b/openmc/deplete/batchwise.py @@ -335,7 +335,8 @@ def _update_x_and_set_volumes(self, x, volumes): x : list of numpy.ndarray Total atoms concentrations volumes : dict - + Updated volumes, where key is the material id and value the material + volume in cm3 Returns ------- x : list of numpy.ndarray @@ -697,7 +698,8 @@ def _calculate_volumes(self): Returns ------- volumes : dict - Dictionary of calculate volumes, where key is the mat id + Dictionary of calculated volumes, where key is the mat id and value + the material volume in cm3 """ openmc.lib.calculate_volumes() volumes = {} diff --git a/tests/unit_tests/test_deplete_batchwise.py b/tests/unit_tests/test_deplete_batchwise.py index 6e3cce7854c..cf71986ded4 100644 --- a/tests/unit_tests/test_deplete_batchwise.py +++ b/tests/unit_tests/test_deplete_batchwise.py @@ -21,26 +21,31 @@ def model(): f.add_element("O", 2) f.set_density("g/cc", 10.4) f.temperature = 293.15 + w = openmc.Material(name="w") w.add_element("O", 1) w.add_element("H", 2) w.set_density("g/cc", 1.0) w.depletable = True + h = openmc.Material(name='h') h.set_density('g/cm3', 0.001598) h.add_element('He', 2.4044e-4) radii = [0.42, 0.45] - f.volume = np.pi * radii[0] ** 2 - w.volume = np.pi * (radii[1]**2 - radii[0]**2) + height = 0.5 + f.volume = np.pi * radii[0] ** 2 * height + w.volume = np.pi * (radii[1]**2 - radii[0]**2) * height/2 materials = openmc.Materials([f, w, h]) surf_wh = openmc.ZPlane(z0=0) surf_f = openmc.Sphere(r=radii[0]) surf_w = openmc.Sphere(r=radii[1], boundary_type='vacuum') + surf_top = openmc.ZPlane(z0=height/2) + surf_bot = openmc.ZPlane(z0=-height/2) cell_w = openmc.Cell(fill=w, region=-surf_wh) cell_h = openmc.Cell(fill=h, region=+surf_wh) uni_wh = openmc.Universe(cells=(cell_w, cell_h)) - cell_f = openmc.Cell(name='f',fill=f, region=-surf_f) - cell_wh = openmc.Cell(name='wh',fill=uni_wh, region=+surf_f & -surf_w) + cell_f = openmc.Cell(name='f',fill=f, region=-surf_f & -surf_top & +surf_bot) + cell_wh = openmc.Cell(name='wh',fill=uni_wh, region=+surf_f & -surf_w & -surf_top & +surf_bot) geometry = openmc.Geometry([cell_f, cell_wh]) settings = openmc.Settings() settings.particles = 1000 @@ -106,10 +111,13 @@ def test_cell_methods(run_in_tmpdir, model, attribute): assert integrator.batchwise._get_cell_attrib() == 0 vol = integrator.batchwise._calculate_volumes() for cell in integrator.batchwise.cell_materials: - assert vol[str(cell.id)] == pytest.approx([mat.volume for mat in model.materials if mat.id == cell.id][0]) + assert vol[str(cell.id)] == pytest.approx([ + mat.volume for mat in model.materials \ + if mat.id == cell.id][0], rel=1e-1) + openmc.lib.finalize() -def test_update_volumes(run_in_tmpdir): +def test_update_volumes(run_in_tmpdir, model): """ Method to update volume in AtomNumber after depletion step. Method inheritated by all derived classes so one check is enough. @@ -133,16 +141,18 @@ def test_update_volumes(run_in_tmpdir): nuc_index = op.number.index_nuc['U238'] vol = op.number.get_mat_volume(str(mat.id)) atoms_to_add = 1e22 - op.number.number[mat_index][nuc_idx] += atoms_to_add + op.number.number[mat_index][nuc_index] += atoms_to_add integrator.batchwise._update_volumes() - atoms_to_vol = atoms_to_add * openmc.data.atomic_mass('U238') \ + vol_to_compare = vol + atoms_to_add * openmc.data.atomic_mass('U238') \ /openmc.data.AVOGADRO / mat.density - assert op.number.get_mat_volume('1') == vol + atoms_to_vol + + + assert op.number.get_mat_volume(str(mat.id)) == pytest.approx(vol_to_compare) openmc.lib.finalize() -def test_update_materials(run_in_tmpdir): +def test_update_materials(run_in_tmpdir, model): """ Method to update volume in AtomNumber after depletion step if 'constant-density' is passed to the batchwise instance. @@ -162,47 +172,55 @@ def test_update_materials(run_in_tmpdir): #Increase number of atoms of U238 in fuel by fix amount and check that the # densities in openmc.lib have beeen updated mat = integrator.batchwise.material + mat_index = op.number.index_mat[str(mat.id)] nuc_index = op.number.index_nuc['U238'] vol = op.number.get_mat_volume(str(mat.id)) - op.number.number[mat_index][nuc_idx] += 1e22 - x= [op.number.number[i][op.number.number[i].nonzero()] for i in range(len(op.number.number))] + op.number.number[mat_index][nuc_index] += 1e22 + x = [i[:op.number.n_nuc_burn] for i in op.number.number] integrator.batchwise._update_materials(x) nuc_index_lib = openmc.lib.materials[mat.id].nuclides.index('U238') assert openmc.lib.materials[mat.id].densities[nuc_index_lib] == \ - 1e-24 * op.number.get_atom_density('1','U238') + 1e-24 * op.number.get_atom_density(str(mat.id),'U238') + + openmc.lib.finalize() + +def test_update_x_and_set_volumes_method(run_in_tmpdir,model): + """ + Method to update volume in AtomNumber after depletion step if 'constant-density' + is passed to the batchwise instance. + Method inheritated by all derived classes so one check is enough. + """ + bracket = [-1,1] + bracket_limit = [-10,10] + op = CoupledOperator(model, CHAIN_PATH) + integrator = openmc.deplete.PredictorIntegrator( + op, [1,1], 0.0, timestep_units = 'd') + integrator.add_batchwise('f', 'refuel', mat_vector={}, bracket=bracket, + bracket_limit=bracket_limit) + + model.export_to_xml() + openmc.lib.init() + + #Increase number of atoms of U238 in fuel by fix amount and check that the + # densities in openmc.lib have beeen updated + mat = integrator.batchwise.material + mat_index = op.number.index_mat[str(mat.id)] + nuc_index = op.number.index_nuc['U238'] + vol = op.number.get_mat_volume(str(mat.id)) + # increase number of U238 atoms + op.number.number[mat_index][nuc_index] += 1e22 + x = [i[:op.number.n_nuc_burn] for i in op.number.number] + # Create new volume dict + volumes = {str(mat.id): vol + 1} + new_x = integrator.batchwise._update_x_and_set_volumes(x, volumes) + + # check new x vector is updated accordingly + nuc_index_lib = openmc.lib.materials[mat.id].nuclides.index('U238') + val_to_compare = 1.0e24 * volumes[str(mat.id)] \ + * openmc.lib.materials[mat.id].densities[nuc_index_lib] + assert new_x[mat_index][nuc_index] == pytest.approx(val_to_compare) + # assert volume in AtomNumber is set correctly + assert op.number.get_mat_volume(str(mat.id)) == volumes[str(mat.id)] openmc.lib.finalize() -# -# def test_update_x_and_set_volumes_method(run_in_tmpdir): -# """ -# Method to update volume in AtomNumber after depletion step if 'constant-density' -# is passed to the batchwise instance. -# Method inheritated by all derived classes so one check is enough. -# """ -# bracket = [-1,1] -# bracket_limit = [-10,10] -# axis = 2 -# op = CoupledOperator(model, CHAIN_PATH) -# integrator = openmc.deplete.PredictorIntegrator( -# op, [1,1], 0.0, timestep_units = 'd') -# integrator.add_batchwise('wh', 'translation', axis=axis, bracket=bracket, -# bracket_limit=bracket_limit) -# -# model.export_to_xml() -# openmc.lib.init() -# -# #Increase number of atoms of U238 in fuel by fix amount and check that the -# # densities in openmc.lib have beeen updated -# mat_index = op.number.index_mat['1'] -# nuc_index = op.number.index_nuc['U238'] -# vol = op.number.get_mat_volume('1') -# atoms_to_add = 1e20 -# vol_to_add = 10 -# op.number.number[mat_index][nuc_idx] += atoms_to_add -# op.number.volumes[mat_index] += vol_to_add -# integrator.batchwise._update_x_and_set_volumes(op.number.number, op.number.volumes) -# -# assert -# -# openmc.lib.finalize() From 7b270028631368a0e002626268df8669fe0489ad Mon Sep 17 00:00:00 2001 From: church89 Date: Tue, 12 Sep 2023 14:49:07 +0200 Subject: [PATCH 06/59] further improvements to the module --- openmc/deplete/batchwise.py | 317 ++++++++++++++++++++---------------- 1 file changed, 175 insertions(+), 142 deletions(-) diff --git a/openmc/deplete/batchwise.py b/openmc/deplete/batchwise.py index e5eff7647b6..e9568e777aa 100644 --- a/openmc/deplete/batchwise.py +++ b/openmc/deplete/batchwise.py @@ -13,14 +13,18 @@ check_iterable_type, check_length) class Batchwise(ABC): - """Abstract class defining a generalized batchwise scheme. + """Abstract class defining a generalized batch wise scheme. - In between transport and depletion steps batchwise operations can be added - with the aim of maintain a system critical. These operations act on OpenMC - Cell or Material, parametrizing one or more coefficients while running a - search_for_kef algorithm. + Almost any type of nuclear reactors adopt batchwise schemes to control + reactivity and maintain keff constant and equal to 1, such as control rod + adjustment or material refueling. - Specific classes for running batchwise depletion calculations are + A batch wise scheme can be added here to an integrator instance, + such as :class:`openmc.deplete.CECMIntegrator`, to parametrize one system + variable with the aim of fulfilling a design criteria, such as keeping + keff equal to 1, while running transport-depletion caculations. + + Specific classes for running batch wise depletion calculations are implemented as derived class of Batchwise . .. versionadded:: 0.13.4 @@ -39,7 +43,7 @@ class Batchwise(ABC): algorithm the search_for_keff solution lies off these limits the closest limit will be set as new result. density_treatment : str - Wether ot not to keep contant volume or density after a depletion step + Whether or not to keep constant volume or density after a depletion step before the next one. Default to 'constant-volume' bracketed_method : {'brentq', 'brenth', 'ridder', 'bisect'}, optional @@ -55,10 +59,10 @@ class Batchwise(ABC): This is equivalent to the `target` parameter of the `search_for_keff`. Default to 1.0. print_iterations : Bool, Optional - Wheter or not to print `search_for_keff` iterations. + Whether or not to print `search_for_keff` iterations. Default to True search_for_keff_output : Bool, Optional - Wheter or not to print transport iterations during `search_for_keff`. + Whether or not to print transport iterations during `search_for_keff`. Default to False Attributes ---------- @@ -136,11 +140,15 @@ def target(self, value): check_type("target", value, Real) self._target = value + @classmethod + def from_params(cls, obj, attr, operator, model, **kwargs): + return cls(obj, attr, operator, model, **kwargs) + @abstractmethod def _model_builder(self, param): """ - Builds the parametric model to be passed to the `search_for_keff` - algorithm. + Builds the parametric model to be passed to the + :meth:`openmc.search.search_for_keff` method. Callable function which builds a model according to a passed parameter. This function must return an openmc.model.Model object. Parameters @@ -156,14 +164,14 @@ def _model_builder(self, param): def _search_for_keff(self, val): """ Perform the criticality search for a given parametric model. - It supports cell or material based `search_for_keff`. - If the solution lies off the initial bracket, the method will iteratively - adapt the bracket to be able to run `search_for_keff` effectively. - The ratio between the bracket and the returned keffs values will be - the scaling factor to iteratively adapt the brackt unitl a valid solution + If the solution lies off the initial bracket, this method iteratively + adapt it until :meth:`openmc.search.search_for_keff` return a valid + solution. + The adapting bracket algorithm takes the ratio between the bracket and + the returned keffs values to scale the bracket until a valid solution is found. - If the adapted bracket is moved too far off, i.e. above or below user - inputted bracket limits interval, the algorithm will stop and the closest + A bracket limit pose the upper and lower boundaries to the adapting + bracket. If one limit is hit, the algorithm will stop and the closest limit value will be used. Parameters ---------- @@ -184,7 +192,7 @@ def _search_for_keff(self, val): else: tol = self.tol - # Run until a search_for_keff root is found or ouf ot limits + # Run until a search_for_keff root is found or out of limits root = None while root == None: @@ -267,20 +275,15 @@ def _search_for_keff(self, val): def _update_volumes(self): """ - Update number volume. + Update volumes stored in AtomNumber. After a depletion step, both material volume and density change, due to - decay, transmutation reactions and transfer rates, if set. + change in nuclides composition. At present we lack an implementation to calculate density and volume changes due to the different molecules speciation. Therefore, OpenMC assumes by default that the depletable volume does not change and only updates the nuclide densities and consequently the total material density. This method, vice-versa, assumes that the total material density does not - change and updates the materials volume in AtomNumber dataset. - - Parameters - ---------- - x : list of numpy.ndarray - Total atom concentrations + change and update the material volumes instead. """ number_i = self.operator.number for mat_idx, mat in enumerate(self.local_mats): @@ -295,10 +298,11 @@ def _update_volumes(self): def _update_materials(self, x): """ Update number density and material compositions in OpenMC on all processes. - If density_treatment is set to 'constant-density', "_update_volumes" will - update material volumes, keeping the material total density constant, in - AtomNumber to renormalize the atom densities before assign them to the - model in memory. + If density_treatment is set to 'constant-density' + :meth:`openmc.deplete.batchwise._update_volumes` is called to update + material volumes in AtomNumber, keeping the material total density + constant, before re-normalizing the atom densities and assigning them + to the model in memory. Parameters ---------- x : list of numpy.ndarray @@ -329,14 +333,14 @@ def _update_materials(self, x): def _update_x_and_set_volumes(self, x, volumes): """ - Update x with volumes, before assign them to AtomNumber materials. + Update x vector with new volumes, before assign them to AtomNumber. Parameters ---------- x : list of numpy.ndarray Total atoms concentrations volumes : dict - Updated volumes, where key is the material id and value the material - volume in cm3 + Updated volumes, where key is material id and value material + volume, in cm3 Returns ------- x : list of numpy.ndarray @@ -348,10 +352,10 @@ def _update_x_and_set_volumes(self, x, volumes): if mat in volumes: res_vol = volumes[mat] - # Normalize burbable nuclides in x vector without cross section data + # Normalize burnable nuclides in x vector without cross section data for nuc_idx, nuc in enumerate(number_i.burnable_nuclides): if nuc not in self.operator.nuclides_with_data: - # normalzie with new volume + # normalize with new volume x[mat_idx][nuc_idx] *= number_i.get_mat_volume(mat) / \ res_vol @@ -374,9 +378,9 @@ def _update_x_and_set_volumes(self, x, volumes): return x class BatchwiseCell(Batchwise): - """Abstract class holding batchwise cell-based functions. + """Abstract class holding batch wise cell-based functions. - Specific classes for running batchwise depletion calculations are + Specific classes for running batch wise depletion calculations are implemented as derived class of BatchwiseCell. .. versionadded:: 0.13.4 @@ -384,7 +388,7 @@ class BatchwiseCell(Batchwise): Parameters ---------- cell : openmc.Cell or int or str - OpenMC Cell identifier to where apply batchwise scheme + OpenMC Cell identifier to where apply batch wise scheme operator : openmc.deplete.Operator OpenMC operator object model : openmc.model.Model @@ -397,7 +401,7 @@ class BatchwiseCell(Batchwise): algorithm the search_for_keff solution lies off these limits the closest limit will be set as new result. density_treatment : str - Wether ot not to keep contant volume or density after a depletion step + Whether or not to keep constant volume or density after a depletion step before the next one. Default to 'constant-volume' bracketed_method : {'brentq', 'brenth', 'ridder', 'bisect'}, optional @@ -413,20 +417,23 @@ class BatchwiseCell(Batchwise): This is equivalent to the `target` parameter of the `search_for_keff`. Default to 1.0. print_iterations : Bool, Optional - Wheter or not to print `search_for_keff` iterations. + Whether or not to print `search_for_keff` iterations. Default to True search_for_keff_output : Bool, Optional - Wheter or not to print transport iterations during `search_for_keff`. + Whether or not to print transport iterations during `search_for_keff`. Default to False Attributes ---------- cell : openmc.Cell or int or str - OpenMC Cell identifier to where apply batchwise scheme + OpenMC Cell identifier to where apply batch wise scheme cell_materials : list of openmc.Material Depletable materials that fill the Cell Universe. Only valid for - translation or rotation atttributes + translation or rotation attributes + axis : int {0,1,2} + Directional axis for geometrical parametrization, where 0, 1 and 2 stand + for 'x', 'y' and 'z', respectively. """ - def __init__(self, cell, operator, model, bracket, bracket_limit, + def __init__(self, cell, operator, model, bracket, bracket_limit, axis=None, density_treatment='constant-volume', bracketed_method='brentq', tol=0.01, target=1.0, print_iterations=True, search_for_keff_output=True): @@ -437,6 +444,11 @@ def __init__(self, cell, operator, model, bracket, bracket_limit, self.cell = self._get_cell(cell) + if axis is not None: + #index of cell directional axis + check_value('axis', axis, (0,1,2)) + self.axis = axis + # list of material fill the attribute cell, if depletables self.cell_materials = None @@ -502,16 +514,18 @@ def _get_cell(self, val): def _model_builder(self, param): """ - Builds the parametric model that is passed to the `search_for_keff` - function by setting the parametric variable to the cell. + Builds the parametric model to be passed to the + :meth:`openmc.search.search_for_keff` method. + Callable function which builds a model according to a passed + parameter. This function must return an openmc.model.Model object. Parameters ---------- - param : model parametricl variable - for examlple: cell translation coefficient + param : model parametric variable + cell translation coefficient or cell rotation coefficient Returns ------- self.model : openmc.model.Model - OpenMC parametric model + Openmc parametric model """ self._set_cell_attrib(param) # At this stage it not important to reassign the new volume, as the @@ -523,8 +537,10 @@ def _model_builder(self, param): def search_for_keff(self, x, step_index): """ - Perform the criticality search parametrizing the cell coefficient. - The `search_for_keff` solution is then set as the new cell attribute + Perform the criticality search on the parametric cell coefficient and + update materials accordingly. + The :meth:`openmc.search.search_for_keff` solution is then set as the + new cell attribute. Parameters ---------- x : list of numpy.ndarray @@ -561,7 +577,7 @@ def search_for_keff(self, x, step_index): class BatchwiseCellGeometrical(BatchwiseCell): """ - Batchwise cell-based with geometrical-attribute class. + Batch wise cell-based with geometrical-attribute class. A user doesn't need to call this class directly. Instead an instance of this class is automatically created by calling @@ -573,8 +589,8 @@ class BatchwiseCellGeometrical(BatchwiseCell): Parameters ---------- cell : openmc.Cell or int or str - OpenMC Cell identifier to where apply batchwise scheme - attrib_name : str {'translation', 'rotation'} + OpenMC Cell identifier to where apply batch wise scheme + attrib_name : str Cell attribute type operator : openmc.deplete.Operator OpenMC operator object @@ -591,7 +607,7 @@ class BatchwiseCellGeometrical(BatchwiseCell): algorithm the search_for_keff solution lies off these limits the closest limit will be set as new result. density_treatment : str - Wether ot not to keep contant volume or density after a depletion step + Whether or not to keep constant volume or density after a depletion step before the next one. Default to 'constant-volume' bracketed_method : {'brentq', 'brenth', 'ridder', 'bisect'}, optional @@ -607,27 +623,30 @@ class BatchwiseCellGeometrical(BatchwiseCell): This is equivalent to the `target` parameter of the `search_for_keff`. Default to 1.0. print_iterations : Bool, Optional - Wheter or not to print `search_for_keff` iterations. + Whether or not to print `search_for_keff` iterations. Default to True search_for_keff_output : Bool, Optional - Wheter or not to print transport iterations during `search_for_keff`. + Whether or not to print transport iterations during `search_for_keff`. Default to False Attributes ---------- cell : openmc.Cell or int or str - OpenMC Cell identifier to where apply batchwise scheme + OpenMC Cell identifier to where apply batch wise scheme attrib_name : str {'translation', 'rotation'} Cell attribute type axis : int {0,1,2} Directional axis for geometrical parametrization, where 0, 1 and 2 stand for 'x', 'y' and 'z', respectively. + samples : int + Number of samples used to generate volume estimates for stochastic + volume calculations. """ - def __init__(self, cell, attrib_name, operator, model, axis, bracket, - bracket_limit, density_treatment='constant-volume', - bracketed_method='brentq', tol=0.01, target=1.0, + def __init__(self, cell, attrib_name, operator, model, bracket, + bracket_limit, axis, density_treatment='constant-volume', + bracketed_method='brentq', tol=0.01, target=1.0, samples=1000000, print_iterations=True, search_for_keff_output=True): - super().__init__(cell, operator, model, bracket, bracket_limit, + super().__init__(cell, operator, model, bracket, bracket_limit, axis, density_treatment, bracketed_method, tol, target, print_iterations, search_for_keff_output) @@ -637,9 +656,6 @@ def __init__(self, cell, attrib_name, operator, model, axis, bracket, # check if cell is filled with 2 cells check_length('fill materials', self.cell.fill.cells, 2) - #index of cell directionnal axis - check_value('axis', axis, (0,1,2)) - self.axis = axis # Initialize vector self.vector = np.zeros(3) @@ -647,13 +663,12 @@ def __init__(self, cell, attrib_name, operator, model, axis, bracket, self.cell_materials = [cell.fill for cell in \ self.cell.fill.cells.values() if cell.fill.depletable] + check_type('samples', samples, int) + self.samples = samples + if self.cell_materials: self._initialize_volume_calc() - @classmethod - def from_params(cls, obj, attr, operator, model, **kwargs): - return cls(obj, attr, operator, model, **kwargs) - def _get_cell_attrib(self): """ Get cell attribute coefficient. @@ -672,11 +687,12 @@ def _get_cell_attrib(self): def _set_cell_attrib(self, val): """ Set cell attribute to the cell instance. - Attributes are only applied to cells filled with a universe + Attributes are only applied to a cell filled with a universe containing + two cells itself. Parameters ---------- val : float - Cell coefficient to set in cm for translation and def for rotation + Cell coefficient to set, in cm for translation and deg for rotation """ self.vector[self.axis] = val for cell in openmc.lib.cells.values(): @@ -689,7 +705,8 @@ def _initialize_volume_calc(self): the parametric Cell. """ ll, ur = self.geometry.bounding_box - mat_vol = openmc.VolumeCalculation(self.cell_materials, 100000, ll, ur) + mat_vol = openmc.VolumeCalculation(self.cell_materials, self.samples, + ll, ur) self.model.settings.volume_calculations = mat_vol def _calculate_volumes(self): @@ -698,20 +715,22 @@ def _calculate_volumes(self): Returns ------- volumes : dict - Dictionary of calculated volumes, where key is the mat id and value - the material volume in cm3 + Dictionary of calculated volumes, where key is mat id and value + material volume, in cm3 """ openmc.lib.calculate_volumes() volumes = {} - res = openmc.VolumeCalculation.from_hdf5('volume_1.h5') - for mat_idx, mat in enumerate(self.local_mats): - if int(mat) in [mat.id for mat in self.cell_materials]: - volumes[mat] = res.volumes[int(mat)].n + if comm.rank == 0: + res = openmc.VolumeCalculation.from_hdf5('volume_1.h5') + for mat_idx, mat in enumerate(self.local_mats): + if int(mat) in [mat.id for mat in self.cell_materials]: + volumes[mat] = res.volumes[int(mat)].n + volumes = comm.bcast(volumes) return volumes class BatchwiseCellTemperature(BatchwiseCell): """ - Batchwise cell-based with temperature-attribute class. + Batch wise cell-based with temperature-attribute class. A user doesn't need to call this class directly. Instead an instance of this class is automatically created by calling @@ -723,11 +742,13 @@ class BatchwiseCellTemperature(BatchwiseCell): Parameters ---------- cell : openmc.Cell or int or str - OpenMC Cell identifier to where apply batchwise scheme + OpenMC Cell identifier to where apply batch wise scheme operator : openmc.deplete.Operator OpenMC operator object model : openmc.model.Model OpenMC model object + attrib_name : str + Cell attribute type bracket : list of float Bracketing interval to search for the solution, always relative to the solution at previous step. @@ -736,7 +757,7 @@ class BatchwiseCellTemperature(BatchwiseCell): algorithm the search_for_keff solution lies off these limits the closest limit will be set as new result. density_treatment : str - Wether ot not to keep contant volume or density after a depletion step + Whether or not to keep constant volume or density after a depletion step before the next one. Default to 'constant-volume' bracketed_method : {'brentq', 'brenth', 'ridder', 'bisect'}, optional @@ -752,25 +773,28 @@ class BatchwiseCellTemperature(BatchwiseCell): This is equivalent to the `target` parameter of the `search_for_keff`. Default to 1.0. print_iterations : Bool, Optional - Wheter or not to print `search_for_keff` iterations. + Whether or not to print `search_for_keff` iterations. Default to True search_for_keff_output : Bool, Optional - Wheter or not to print transport iterations during `search_for_keff`. + Whether or not to print transport iterations during `search_for_keff`. Default to False Attributes ---------- cell : openmc.Cell or int or str - OpenMC Cell identifier to where apply batchwise scheme + OpenMC Cell identifier to where apply batch wise scheme """ - def __init__(self, cell, operator, model, bracket, bracket_limit, - density_treatment='constant-volume', bracketed_method='brentq', - tol=0.01, target=1.0, print_iterations=True, - search_for_keff_output=True): + def __init__(self, cell, attrib_name, operator, model, bracket, bracket_limit, + axis=None, density_treatment='constant-volume', + bracketed_method='brentq', tol=0.01, target=1.0, + print_iterations=True, search_for_keff_output=True): - super().__init__(cell, operator, model, bracket, bracket_limit, + super().__init__(cell, operator, model, bracket, bracket_limit, axis, density_treatment, bracketed_method, tol, target, print_iterations, search_for_keff_output) + # Not needed but used for consistency with other classes + check_value('attrib_name', attrib_name, 'temperature') + self.attrib_name = attrib_name # check if initial temperature has been set to right cell material if isinstance(self.cell.fill, openmc.Universe): cells = [cell for cell in self.cell.fill.cells.values() \ @@ -780,17 +804,13 @@ def __init__(self, cell, operator, model, bracket, bracket_limit, check_type('temperature cell real', self.cell.fill.temperature, Real) - @classmethod - def from_params(cls, obj, attr, operator, model, **kwargs): - return cls(obj, operator, model, **kwargs) - def _get_cell_attrib(self): """ - Get cell attribute coefficient. + Get cell temperature. Returns ------- coeff : float - cell temperature in Kelvin + cell temperature, in Kelvin """ for cell in openmc.lib.cells.values(): if cell.id == self.cell.id: @@ -798,20 +818,20 @@ def _get_cell_attrib(self): def _set_cell_attrib(self, val): """ - Set cell attribute to the cell instance. + Set temperature value to the cell instance. Parameters ---------- val : float - Cell temperature to set in Kelvin + Cell temperature to set, in Kelvin """ for cell in openmc.lib.cells.values(): if cell.id == self.cell.id: cell.set_temperature(val) class BatchwiseMaterial(Batchwise): - """Abstract class holding batchwise material-based functions. + """Abstract class holding batch wise material-based functions. - Specific classes for running batchwise depletion calculations are + Specific classes for running batch wise depletion calculations are implemented as derived class of BatchwiseMaterial. .. versionadded:: 0.13.4 @@ -819,14 +839,14 @@ class BatchwiseMaterial(Batchwise): Parameters ---------- material : openmc.Material or int or str - OpenMC Material identifier to where apply batchwise scheme + OpenMC Material identifier to where apply batch wise scheme operator : openmc.deplete.Operator OpenMC operator object model : openmc.model.Model OpenMC model object mat_vector : dict - Dictionary of material composition to paremetrize, where a pair key, value - represents a nuclide and its weight fraction. + Dictionary of material composition to parameterize, where a pair key value + represents a nuclide and its weight fraction, respectively. bracket : list of float Bracketing interval to search for the solution, always relative to the solution at previous step. @@ -835,7 +855,7 @@ class BatchwiseMaterial(Batchwise): algorithm the search_for_keff solution lies off these limits the closest limit will be set as new result. density_treatment : str - Wether ot not to keep contant volume or density after a depletion step + Whether or not to keep constant volume or density after a depletion step before the next one. Default to 'constant-volume' bracketed_method : {'brentq', 'brenth', 'ridder', 'bisect'}, optional @@ -851,18 +871,18 @@ class BatchwiseMaterial(Batchwise): This is equivalent to the `target` parameter of the `search_for_keff`. Default to 1.0. print_iterations : Bool, Optional - Wheter or not to print `search_for_keff` iterations. + Whether or not to print `search_for_keff` iterations. Default to True search_for_keff_output : Bool, Optional - Wheter or not to print transport iterations during `search_for_keff`. + Whether or not to print transport iterations during `search_for_keff`. Default to False Attributes ---------- material : openmc.Material or int or str - OpenMC Material identifier to where apply batchwise scheme + OpenMC Material identifier to where apply batch wise scheme mat_vector : dict - Dictionary of material composition to paremetrize, where a pair key, value - represents a nuclide and its weight fraction. + Dictionary of material composition to parameterize, where a pair key value + represents a nuclide and its weight fraction, respectively. """ def __init__(self, material, operator, model, mat_vector, bracket, @@ -887,16 +907,12 @@ def __init__(self, material, operator, model, mat_vector, bracket, mat_vector[elm] /= sum_values self.mat_vector = mat_vector - @classmethod - def from_params(cls, obj, attr, operator, model, **kwargs): - return cls(obj, operator, model, **kwargs) - def _get_material(self, val): """Helper method for getting openmc material from Material instance or material name or id. Parameters ---------- - val : Openmc.Material or str or int representing material name/id + val : Openmc.Material or str or int representing material name or id Returns ------- val : openmc.Material @@ -930,10 +946,10 @@ def _get_material(self, val): @abstractmethod def _model_builder(self, param): """ - Builds the parametric model that is passed to the `msr_search_for_keff` - function by updating the material densities and setting the parametric - variable as function of the nuclides vector. Since this is a paramteric - material addition (or removal), we can parametrize the volume as well. + Builds the parametric model to be passed to the + :meth:`openmc.search.search_for_keff` method. + Callable function which builds a model according to a passed + parameter. This function must return an openmc.model.Model object. Parameters ---------- param : @@ -946,9 +962,9 @@ def _model_builder(self, param): def search_for_keff(self, x, step_index): """ - Perform the criticality search on the parametric material model. - Will set the root of the `search_for_keff` function to the atoms - concentrations vector. + Perform the criticality search on parametric material variable. + The :meth:`openmc.search.search_for_keff` solution is then used to + calculate the new material volume and update the atoms concentrations. Parameters ---------- x : list of numpy.ndarray @@ -973,7 +989,7 @@ def search_for_keff(self, x, step_index): class BatchwiseMaterialRefuel(BatchwiseMaterial): """ - Batchwise material-based class for refueling (addition or removal) scheme. + Batch wise material-based class for refuelling (addition or removal) scheme. A user doesn't need to call this class directly. Instead an instance of this class is automatically created by calling @@ -985,14 +1001,16 @@ class BatchwiseMaterialRefuel(BatchwiseMaterial): Parameters ---------- material : openmc.Material or int or str - OpenMC Material identifier to where apply batchwise scheme + OpenMC Material identifier to where apply batch wise scheme operator : openmc.deplete.Operator OpenMC operator object + attrib_name : str + Material attribute model : openmc.model.Model OpenMC model object mat_vector : dict - Dictionary of material composition to paremetrize, where a pair key, value - represents a nuclide and its weight fraction. + Dictionary of material composition to parameterize, where a pair key value + represents a nuclide and its weight fraction, respectively. bracket : list of float Bracketing interval to search for the solution, always relative to the solution at previous step. @@ -1001,7 +1019,7 @@ class BatchwiseMaterialRefuel(BatchwiseMaterial): algorithm the search_for_keff solution lies off these limits the closest limit will be set as new result. density_treatment : str - Wether ot not to keep contant volume or density after a depletion step + Whether or not to keep constant volume or density after a depletion step before the next one. Default to 'constant-volume' bracketed_method : {'brentq', 'brenth', 'ridder', 'bisect'}, optional @@ -1017,21 +1035,21 @@ class BatchwiseMaterialRefuel(BatchwiseMaterial): This is equivalent to the `target` parameter of the `search_for_keff`. Default to 1.0. print_iterations : Bool, Optional - Wheter or not to print `search_for_keff` iterations. + Whether or not to print `search_for_keff` iterations. Default to True search_for_keff_output : Bool, Optional - Wheter or not to print transport iterations during `search_for_keff`. + Whether or not to print transport iterations during `search_for_keff`. Default to False Attributes ---------- material : openmc.Material or int or str - OpenMC Material identifier to where apply batchwise scheme + OpenMC Material identifier to where apply batch wise scheme mat_vector : dict - Dictionary of material composition to paremetrize, where a pair key, value - represents a nuclide and its weight fraction. + Dictionary of material composition to parameterize, where a pair key value + represents a nuclide and its weight fraction, respectively. """ - def __init__(self, material, operator, model, mat_vector, bracket, + def __init__(self, material, attrib_name, operator, model, mat_vector, bracket, bracket_limit, density_treatment='constant-volume', bracketed_method='brentq', tol=0.01, target=1.0, print_iterations=True, search_for_keff_output=True): @@ -1040,24 +1058,27 @@ def __init__(self, material, operator, model, mat_vector, bracket, bracket_limit, density_treatment, bracketed_method, tol, target, print_iterations, search_for_keff_output) - @classmethod - def from_params(cls, obj, attr, operator, model, **kwargs): - return cls(obj, operator, model, **kwargs) + # Not needed but used for consistency with other classes + check_value('attrib_name', attrib_name, 'refuel') + self.attrib_name = attrib_name def _model_builder(self, param): """ - Builds the parametric model that is passed to the `msr_search_for_keff` - function by updating the material densities and setting the parametric - variable to the material nuclides to add. We can either fix the total - volume or the material density according to user input. + Callable function which builds a model according to a passed + parameter. This function must return an openmc.model.Model object. + Builds the parametric model to be passed to the + :meth:`openmc.search.search_for_keff` method. + The parametrization can either be at constant volume or constant + density, according to user input. + Default is constant volume. Parameters ---------- - param : + param : float Model function variable, total grams of material to add or remove Returns ------- model : openmc.model.Model - OpenMC parametric model + Openmc parametric model """ for rank in range(comm.size): number_i = comm.bcast(self.operator.number, root=rank) @@ -1106,11 +1127,23 @@ def _model_builder(self, param): #set nuclides and densities to the in-memory model openmc.lib.materials[int(mat)].set_densities(nuclides, densities) - # alwyas need to return a model + # always need to return a model return self.model def _calculate_volumes(self, res): """ + Uses :meth:`openmc.batchwise._search_for_keff` solution as grams of + material to add or remove to calculate new material volume. + Parameters + ---------- + res : float + Solution in grams of material, coming from + :meth:`openmc.batchwise._search_for_keff` + Returns + ------- + volumes : dict + Dictionary of calculated volume, where key mat id and value + material volume, in cm3 """ number_i = self.operator.number volumes = {} From 0910bf2ae2d9a068a8901ed7409828f9980c69b5 Mon Sep 17 00:00:00 2001 From: church89 Date: Tue, 12 Sep 2023 14:49:30 +0200 Subject: [PATCH 07/59] add unit and regression tests --- .../deplete_with_batchwise/__init__.py | 0 .../deplete_with_batchwise/test.py | 122 +++++++++ tests/unit_tests/test_deplete_batchwise.py | 242 ++++++++---------- 3 files changed, 232 insertions(+), 132 deletions(-) create mode 100644 tests/regression_tests/deplete_with_batchwise/__init__.py create mode 100644 tests/regression_tests/deplete_with_batchwise/test.py diff --git a/tests/regression_tests/deplete_with_batchwise/__init__.py b/tests/regression_tests/deplete_with_batchwise/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/regression_tests/deplete_with_batchwise/test.py b/tests/regression_tests/deplete_with_batchwise/test.py new file mode 100644 index 00000000000..5d35282b602 --- /dev/null +++ b/tests/regression_tests/deplete_with_batchwise/test.py @@ -0,0 +1,122 @@ +""" Tests for Batchwise class """ + +from pathlib import Path +import shutil +import sys + +import pytest +import numpy as np + +import openmc +import openmc.lib +from openmc.deplete import CoupledOperator + +from tests.regression_tests import config + +@pytest.fixture +def model(): + f = openmc.Material(name='f') + f.set_density('g/cm3', 10.29769) + f.add_element('U', 1., enrichment=2.4) + f.add_element('O', 2.) + + h = openmc.Material(name='h') + h.set_density('g/cm3', 0.001598) + h.add_element('He', 2.4044e-4) + + w = openmc.Material(name='w') + w.set_density('g/cm3', 0.740582) + w.add_element('H', 2) + w.add_element('O', 1) + + # Define overall material + materials = openmc.Materials([f, h, w]) + + # Define surfaces + radii = [0.5, 0.8, 1] + height = 80 + surf_in = openmc.ZCylinder(r=radii[0]) + surf_mid = openmc.ZCylinder(r=radii[1]) + surf_out = openmc.ZCylinder(r=radii[2], boundary_type='reflective') + surf_top = openmc.ZPlane(z0=height/2, boundary_type='vacuum') + surf_bot = openmc.ZPlane(z0=-height/2, boundary_type='vacuum') + + surf_trans = openmc.ZPlane(z0=0) + surf_rot1 = openmc.XPlane(x0=0) + surf_rot2 = openmc.YPlane(y0=0) + + # Define cells + cell_f = openmc.Cell(name='fuel_cell', fill=f, + region=-surf_in & -surf_top & +surf_bot) + cell_g = openmc.Cell(fill=h, + region = +surf_in & -surf_mid & -surf_top & +surf_bot & +surf_rot2) + + # Define unbounded cells for rotation universe + cell_w = openmc.Cell(fill=w, region = -surf_rot1) + cell_h = openmc.Cell(fill=h, region = +surf_rot1) + universe_rot = openmc.Universe(cells=(cell_w, cell_h)) + cell_rot = openmc.Cell(name="rot_cell", fill=universe_rot, + region = +surf_in & -surf_mid & -surf_top & +surf_bot & -surf_rot2) + + # Define unbounded cells for translation universe + cell_w = openmc.Cell(fill=w, region=+surf_in & -surf_trans ) + cell_h = openmc.Cell(fill=h, region=+surf_in & +surf_trans) + universe_trans = openmc.Universe(cells=(cell_w, cell_h)) + cell_trans = openmc.Cell(name="trans_cell", fill=universe_trans, + region=+surf_mid & -surf_out & -surf_top & +surf_bot) + + # Define overall geometry + geometry = openmc.Geometry([cell_f, cell_g, cell_rot, cell_trans]) + + # Set material volume for depletion fuel. + f.volume = np.pi * radii[0]**2 * height + + settings = openmc.Settings() + settings.particles = 1000 + settings.inactive = 10 + settings.batches = 50 + + return openmc.Model(geometry, materials, settings) + +@pytest.mark.skipif(sys.version_info < (3, 9), reason="Requires Python 3.9+") +@pytest.mark.parametrize("obj, attribute, bracket_limit, axis, vec, ref_result", [ + ('trans_cell', 'translation', [-40,40], 2, None, 'depletion_with_translation'), + ('rot_cell', 'rotation', [-90,90], 2, None, 'depletion_with_rotation'), + ('f', 'refuel', [-100,100], None, {'U235':1}, 'depletion_with_refuel') + ]) +def test_batchwise(run_in_tmpdir, model, obj, attribute, bracket_limit, axis, + vec, ref_result): + chain_file = Path(__file__).parents[2] / 'chain_simple.xml' + op = CoupledOperator(model, chain_file) + + integrator = openmc.deplete.PredictorIntegrator( + op, [1], 174., timestep_units = 'd') + + kwargs = {'bracket': [-4,4], 'bracket_limit':bracket_limit, + 'tol': 0.1,} + + if vec is not None: + kwargs['mat_vector']=vec + + if axis is not None: + kwargs['axis'] = axis + + integrator.add_batchwise(obj, attribute, **kwargs) + integrator.integrate() + + # Get path to test and reference results + path_test = op.output_dir / 'depletion_results.h5' + path_reference = Path(__file__).with_name(f'ref_{ref_result}.h5') + + # If updating results, do so and return + if config['update']: + shutil.copyfile(str(path_test), str(path_reference)) + return + + # Load the reference/test results + res_test = openmc.deplete.Results(path_test) + res_ref = openmc.deplete.Results(path_reference) + + # Use hight tolerance here + assert [res.batchwise for res in res_test] == pytest.approx( + [res.batchwise for res in res_ref], rel=2) diff --git a/tests/unit_tests/test_deplete_batchwise.py b/tests/unit_tests/test_deplete_batchwise.py index cf71986ded4..98ae9a01ff3 100644 --- a/tests/unit_tests/test_deplete_batchwise.py +++ b/tests/unit_tests/test_deplete_batchwise.py @@ -1,7 +1,6 @@ """ Tests for Batchwise class """ from pathlib import Path -from math import exp import pytest import numpy as np @@ -16,119 +15,157 @@ @pytest.fixture def model(): - f = openmc.Material(name="f") + f = openmc.Material(name="fuel") f.add_element("U", 1, percent_type="ao", enrichment=4.25) f.add_element("O", 2) f.set_density("g/cc", 10.4) f.temperature = 293.15 - w = openmc.Material(name="w") + w = openmc.Material(name="water") w.add_element("O", 1) w.add_element("H", 2) w.set_density("g/cc", 1.0) + w.temperature = 273.15 w.depletable = True - h = openmc.Material(name='h') + h = openmc.Material(name='helium') + h.add_element('He', 1) h.set_density('g/cm3', 0.001598) - h.add_element('He', 2.4044e-4) + radii = [0.42, 0.45] height = 0.5 + f.volume = np.pi * radii[0] ** 2 * height w.volume = np.pi * (radii[1]**2 - radii[0]**2) * height/2 + materials = openmc.Materials([f, w, h]) - surf_wh = openmc.ZPlane(z0=0) - surf_f = openmc.Sphere(r=radii[0]) - surf_w = openmc.Sphere(r=radii[1], boundary_type='vacuum') + + surf_interface = openmc.ZPlane(z0=0) surf_top = openmc.ZPlane(z0=height/2) surf_bot = openmc.ZPlane(z0=-height/2) - cell_w = openmc.Cell(fill=w, region=-surf_wh) - cell_h = openmc.Cell(fill=h, region=+surf_wh) - uni_wh = openmc.Universe(cells=(cell_w, cell_h)) - cell_f = openmc.Cell(name='f',fill=f, region=-surf_f & -surf_top & +surf_bot) - cell_wh = openmc.Cell(name='wh',fill=uni_wh, region=+surf_f & -surf_w & -surf_top & +surf_bot) - geometry = openmc.Geometry([cell_f, cell_wh]) + surf_in = openmc.Sphere(r=radii[0]) + surf_out = openmc.Sphere(r=radii[1], boundary_type='vacuum') + + cell_water = openmc.Cell(fill=w, region=-surf_interface) + cell_helium = openmc.Cell(fill=h, region=+surf_interface) + universe = openmc.Universe(cells=(cell_water, cell_helium)) + cell_fuel = openmc.Cell(name='fuel_cell', fill=f, + region=-surf_in & -surf_top & +surf_bot) + cell_universe = openmc.Cell(name='universe_cell',fill=universe, + region=+surf_in & -surf_out & -surf_top & +surf_bot) + geometry = openmc.Geometry([cell_fuel, cell_universe]) + settings = openmc.Settings() settings.particles = 1000 settings.inactive = 10 settings.batches = 50 + return openmc.Model(geometry, materials, settings) -@pytest.mark.parametrize("object_name, attribute", [ - ('wh', 'translation'), - ('wh', 'rotation'), - ('f', 'temperature'), - ('f', 'refuel'), +@pytest.mark.parametrize("case_name, obj, attribute, bracket, limit, axis, vec", [ + ('cell translation','universe_cell', 'translation', [-1,1], [-10,10], 2, None), + ('cell rotation', 'universe_cell', 'rotation', [-1,1], [-10,10], 2, None), + ('single cell temperature', 'fuel_cell', 'temperature', [-1,1], [-10,10], 2, None ), + ('multi cell', 'universe_cell', 'temperature', [-1,1], [-10,10], 2, None ), + ('material refuel', 'fuel', 'refuel', [-1,1], [-10,10], None, {'U235':0.1, 'U238':0.9}), + ('invalid_1', 'universe_cell', 'refuel', [-1,1], [-10,10], None, {'U235':0.1, 'U238':0.9}), + ('invalid_2', 'fuel', 'temperature', [-1,1], [-10,10], 2, None ), + ('invalid_3', 'fuel', 'translation', [-1,1], [-10,10], 2, None), ]) -def test_attributes(model, object_name, attribute): +def test_attributes(case_name, model, obj, attribute, bracket, limit, axis, vec): """ Test classes attributes are set correctly """ op = CoupledOperator(model, CHAIN_PATH) - bracket = [-1,1] - bracket_limit = [-10,10] - axis = 2 - mat_vector = {'U235':0.1, 'U238':0.9} - if attribute in ('translation','rotation'): - bw = BatchwiseCellGeometrical(object_name, attribute, op, model, axis, bracket, bracket_limit) - assert bw.cell_materials == [cell.fill for cell in \ - model.geometry.get_cells_by_name(object_name)[0].fill.cells.values() \ - if cell.fill.depletable] - assert bw.attrib_name == attribute - assert bw.axis == axis - - elif attribute == 'temperature': - bw = BatchwiseCellTemperature(object_name, op, model, bracket, bracket_limit) - elif attribute == 'refuel': - bw = BatchwiseMaterialRefuel(object_name, op, model, mat_vector, bracket, bracket_limit) - assert bw.mat_vector == mat_vector - - assert bw.bracket == bracket - assert bw.bracket_limit == bracket_limit - assert bw.burn_mats == op.burnable_mats - assert bw.local_mats == op.local_mats - -@pytest.mark.parametrize("attribute", [ - ('translation'), - ('rotation') + int = openmc.deplete.PredictorIntegrator( + op, [1,1], 0.0, timestep_units = 'd') + + kwargs = {'bracket': bracket, 'bracket_limit':limit, 'tol': 0.1,} + + if vec is not None: + kwargs['mat_vector']=vec + + if axis is not None: + kwargs['axis'] = axis + + if case_name == "invalid_1": + with pytest.raises(ValueError) as e: + int.add_batchwise(obj, attribute, **kwargs) + assert str(e.value) == 'Unable to set "Material name" to "universe_cell" '\ + 'since it is not in "[\'fuel\', \'water\']"' + elif case_name == "invalid_2": + with pytest.raises(ValueError) as e: + int.add_batchwise(obj, attribute, **kwargs) + assert str(e.value) == 'Unable to set "Cell name exists" to "fuel" since '\ + 'it is not in "[\'fuel_cell\', \'universe_cell\', \'\', \'\']"' + + elif case_name == "invalid_3": + with pytest.raises(ValueError) as e: + int.add_batchwise(obj, attribute, **kwargs) + assert str(e.value) == 'Unable to set "Cell name exists" to "fuel" since '\ + 'it is not in "[\'fuel_cell\', \'universe_cell\', \'\', \'\']"' + else: + int.add_batchwise(obj, attribute, **kwargs) + if attribute in ('translation','rotation'): + assert int.batchwise.cell_materials == [cell.fill for cell in \ + model.geometry.get_cells_by_name(obj)[0].fill.cells.values() \ + if cell.fill.depletable] + assert int.batchwise.axis == axis + + elif attribute == 'refuel': + assert int.batchwise.mat_vector == vec + + assert int.batchwise.attrib_name == attribute + assert int.batchwise.bracket == bracket + assert int.batchwise.bracket_limit == limit + assert int.batchwise.burn_mats == op.burnable_mats + assert int.batchwise.local_mats == op.local_mats + +@pytest.mark.parametrize("obj, attribute, value_to_set", [ + ('universe_cell', 'translation', 0), + ('universe_cell', 'rotation', 0) ]) -def test_cell_methods(run_in_tmpdir, model, attribute): +def test_cell_methods(run_in_tmpdir, model, obj, attribute, value_to_set): """ Test cell base class internal method """ - bracket = [-1,1] - bracket_limit = [-10,10] - axis = 2 + kwargs = {'bracket':[-1,1], 'bracket_limit':[-10,10], 'axis':2, 'tol':0.1} + op = CoupledOperator(model, CHAIN_PATH) integrator = openmc.deplete.PredictorIntegrator( op, [1,1], 0.0, timestep_units = 'd') - integrator.add_batchwise('wh', attribute, axis=axis, bracket=bracket, - bracket_limit=bracket_limit) + integrator.add_batchwise(obj, attribute, **kwargs) model.export_to_xml() openmc.lib.init() - integrator.batchwise._set_cell_attrib(0) - assert integrator.batchwise._get_cell_attrib() == 0 + integrator.batchwise._set_cell_attrib(value_to_set) + assert integrator.batchwise._get_cell_attrib() == value_to_set + vol = integrator.batchwise._calculate_volumes() for cell in integrator.batchwise.cell_materials: assert vol[str(cell.id)] == pytest.approx([ - mat.volume for mat in model.materials \ - if mat.id == cell.id][0], rel=1e-1) + mat.volume for mat in model.materials \ + if mat.id == cell.id][0], rel=tolerance) openmc.lib.finalize() -def test_update_volumes(run_in_tmpdir, model): +@pytest.mark.parametrize("nuclide, atoms_to_add", [ + ('U238', 1.0e22), + ('Xe135', 1.0e21) + ]) +def test_internal_methods(run_in_tmpdir, model, nuclide, atoms_to_add): """ Method to update volume in AtomNumber after depletion step. Method inheritated by all derived classes so one check is enough. """ - bracket = [-1,1] - bracket_limit = [-10,10] + + kwargs = {'bracket':[-1,1], 'bracket_limit':[-10,10], 'mat_vector':{}} + op = CoupledOperator(model, CHAIN_PATH) - integrator = openmc.deplete.PredictorIntegrator( - op, [1,1], 0.0, timestep_units = 'd') - integrator.add_batchwise('f', 'refuel', mat_vector={}, bracket=bracket, - bracket_limit=bracket_limit) + integrator = openmc.deplete.PredictorIntegrator(op, [1,1], 0.0, + timestep_units = 'd') + integrator.add_batchwise('fuel', 'refuel', **kwargs) model.export_to_xml() openmc.lib.init() @@ -138,89 +175,30 @@ def test_update_volumes(run_in_tmpdir, model): #extract fuel material from model materials mat = integrator.batchwise.material mat_index = op.number.index_mat[str(mat.id)] - nuc_index = op.number.index_nuc['U238'] + nuc_index = op.number.index_nuc[nuclide] vol = op.number.get_mat_volume(str(mat.id)) - atoms_to_add = 1e22 op.number.number[mat_index][nuc_index] += atoms_to_add integrator.batchwise._update_volumes() - vol_to_compare = vol + atoms_to_add * openmc.data.atomic_mass('U238') \ - /openmc.data.AVOGADRO / mat.density - + vol_to_compare = vol + (atoms_to_add * openmc.data.atomic_mass(nuclide) /\ + openmc.data.AVOGADRO / mat.density) assert op.number.get_mat_volume(str(mat.id)) == pytest.approx(vol_to_compare) - openmc.lib.finalize() - -def test_update_materials(run_in_tmpdir, model): - """ - Method to update volume in AtomNumber after depletion step if 'constant-density' - is passed to the batchwise instance. - Method inheritated by all derived classes so one check is enough. - """ - bracket = [-1,1] - bracket_limit = [-10,10] - op = CoupledOperator(model, CHAIN_PATH) - integrator = openmc.deplete.PredictorIntegrator( - op, [1,1], 0.0, timestep_units = 'd') - integrator.add_batchwise('f', 'refuel', mat_vector={}, bracket=bracket, - bracket_limit=bracket_limit) - - model.export_to_xml() - openmc.lib.init() - - #Increase number of atoms of U238 in fuel by fix amount and check that the - # densities in openmc.lib have beeen updated - mat = integrator.batchwise.material - mat_index = op.number.index_mat[str(mat.id)] - nuc_index = op.number.index_nuc['U238'] - vol = op.number.get_mat_volume(str(mat.id)) - op.number.number[mat_index][nuc_index] += 1e22 x = [i[:op.number.n_nuc_burn] for i in op.number.number] integrator.batchwise._update_materials(x) + nuc_index_lib = openmc.lib.materials[mat.id].nuclides.index(nuclide) + dens_to_compare = 1.0e-24 * op.number.get_atom_density(str(mat.id), nuclide) - nuc_index_lib = openmc.lib.materials[mat.id].nuclides.index('U238') - assert openmc.lib.materials[mat.id].densities[nuc_index_lib] == \ - 1e-24 * op.number.get_atom_density(str(mat.id),'U238') - - openmc.lib.finalize() - -def test_update_x_and_set_volumes_method(run_in_tmpdir,model): - """ - Method to update volume in AtomNumber after depletion step if 'constant-density' - is passed to the batchwise instance. - Method inheritated by all derived classes so one check is enough. - """ - bracket = [-1,1] - bracket_limit = [-10,10] - op = CoupledOperator(model, CHAIN_PATH) - integrator = openmc.deplete.PredictorIntegrator( - op, [1,1], 0.0, timestep_units = 'd') - integrator.add_batchwise('f', 'refuel', mat_vector={}, bracket=bracket, - bracket_limit=bracket_limit) - - model.export_to_xml() - openmc.lib.init() + assert openmc.lib.materials[mat.id].densities[nuc_index_lib] == pytest.approx(dens_to_compare) - #Increase number of atoms of U238 in fuel by fix amount and check that the - # densities in openmc.lib have beeen updated - mat = integrator.batchwise.material - mat_index = op.number.index_mat[str(mat.id)] - nuc_index = op.number.index_nuc['U238'] - vol = op.number.get_mat_volume(str(mat.id)) - # increase number of U238 atoms - op.number.number[mat_index][nuc_index] += 1e22 - x = [i[:op.number.n_nuc_burn] for i in op.number.number] - # Create new volume dict volumes = {str(mat.id): vol + 1} new_x = integrator.batchwise._update_x_and_set_volumes(x, volumes) - - # check new x vector is updated accordingly - nuc_index_lib = openmc.lib.materials[mat.id].nuclides.index('U238') - val_to_compare = 1.0e24 * volumes[str(mat.id)] \ - * openmc.lib.materials[mat.id].densities[nuc_index_lib] - assert new_x[mat_index][nuc_index] == pytest.approx(val_to_compare) + dens_to_compare = 1.0e24 * volumes[str(mat.id)] *\ + openmc.lib.materials[mat.id].densities[nuc_index_lib] + assert new_x[mat_index][nuc_index] == pytest.approx(dens_to_compare) # assert volume in AtomNumber is set correctly assert op.number.get_mat_volume(str(mat.id)) == volumes[str(mat.id)] + openmc.lib.finalize() From 562baad628792dc2d126dc168ff1c2ae4b7c3782 Mon Sep 17 00:00:00 2001 From: church89 Date: Wed, 13 Sep 2023 16:52:51 +0200 Subject: [PATCH 08/59] add missing docstrings --- openmc/deplete/abc.py | 14 ++++++++++---- openmc/deplete/batchwise.py | 11 +++++------ 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py index d14355947f5..5a545749dd2 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -555,6 +555,9 @@ class Integrator(ABC): transfer_rates : openmc.deplete.TransferRates Instance of TransferRates class to perform continuous transfer during depletion + batchwise : openmc.deplete.Batchwise + Instance of Batchwise class to perform batch-wise scheme during + transport-depletion simulation. .. versionadded:: 0.13.4 @@ -871,15 +874,18 @@ def add_batchwise(self, obj, attr, **kwargs): Parameters ---------- + obj : openmc.Cell or openmc.Material object or id or str name + Cell or Materials identifier to where add batchwise scheme attr : str - Type of batchwise operation to add. `Trans` stands for geometrical - translation, `refuel` for material refueling and `dilute` for material - dilute. + Attribute to specify the type of batchwise scheme. Accepted values + are: 'translation', 'rotation', 'temperature' for an openmc.Cell + object; 'refuel' for an openmc.Material object. **kwargs keyword arguments that are passed to the batchwise class. """ - check_value('attribute', attr, ('translation', 'rotation', 'temperature', 'refuel')) + check_value('attribute', attr, ('translation', 'rotation', + 'temperature', 'refuel')) if attr in ('translation', 'rotation'): batchwise = BatchwiseCellGeometrical elif attr == 'temperature': diff --git a/openmc/deplete/batchwise.py b/openmc/deplete/batchwise.py index e9568e777aa..955d2878b78 100644 --- a/openmc/deplete/batchwise.py +++ b/openmc/deplete/batchwise.py @@ -15,14 +15,13 @@ class Batchwise(ABC): """Abstract class defining a generalized batch wise scheme. - Almost any type of nuclear reactors adopt batchwise schemes to control - reactivity and maintain keff constant and equal to 1, such as control rod - adjustment or material refueling. + Batchwise schemes, such as control rod adjustment or material refueling to + control reactivity and maintain keff constant and equal to one. A batch wise scheme can be added here to an integrator instance, such as :class:`openmc.deplete.CECMIntegrator`, to parametrize one system - variable with the aim of fulfilling a design criteria, such as keeping - keff equal to 1, while running transport-depletion caculations. + variable with the aim of satisfy certain design criteria, such as keeping + keff equal to one, while running transport-depletion caculations. Specific classes for running batch wise depletion calculations are implemented as derived class of Batchwise @@ -277,7 +276,7 @@ def _update_volumes(self): """ Update volumes stored in AtomNumber. After a depletion step, both material volume and density change, due to - change in nuclides composition. + changes in nuclides composition. At present we lack an implementation to calculate density and volume changes due to the different molecules speciation. Therefore, OpenMC assumes by default that the depletable volume does not change and only From 7643aafc7992cdc20831c03974fef886e09aec95 Mon Sep 17 00:00:00 2001 From: church89 Date: Wed, 13 Sep 2023 17:07:26 +0200 Subject: [PATCH 09/59] remove unused import --- openmc/deplete/abc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py index 5a545749dd2..6a4753bb6b5 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -25,7 +25,7 @@ from .results import Results from .pool import deplete from .transfer_rates import TransferRates -from openmc import Material, Cell, Universe +from openmc import Material, Cell from .batchwise import (BatchwiseCellGeometrical, BatchwiseCellTemperature, BatchwiseMaterialRefuel) From 1777cee15a7ba4fd353776bf197e14561bcfebf0 Mon Sep 17 00:00:00 2001 From: church89 Date: Wed, 13 Sep 2023 17:07:57 +0200 Subject: [PATCH 10/59] add missing returns --- openmc/search.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openmc/search.py b/openmc/search.py index 4818fbe10bd..7e2c5a1a19d 100644 --- a/openmc/search.py +++ b/openmc/search.py @@ -205,7 +205,7 @@ def search_for_keff(model_builder, initial_guess=None, target=1.0, # Perform the search if not run_in_memory: zero_value = root_finder(**args) - return zero_value + return zero_value, guesses, results else: try: From 6eb665137c8f8bc39733c6a8557cbda332af8a5a Mon Sep 17 00:00:00 2001 From: church89 Date: Wed, 13 Sep 2023 17:08:16 +0200 Subject: [PATCH 11/59] fix typo --- tests/regression_tests/deplete_with_batchwise/test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/regression_tests/deplete_with_batchwise/test.py b/tests/regression_tests/deplete_with_batchwise/test.py index 5d35282b602..c7fd59788bb 100644 --- a/tests/regression_tests/deplete_with_batchwise/test.py +++ b/tests/regression_tests/deplete_with_batchwise/test.py @@ -117,6 +117,6 @@ def test_batchwise(run_in_tmpdir, model, obj, attribute, bracket_limit, axis, res_test = openmc.deplete.Results(path_test) res_ref = openmc.deplete.Results(path_reference) - # Use hight tolerance here + # Use high tolerance here assert [res.batchwise for res in res_test] == pytest.approx( [res.batchwise for res in res_ref], rel=2) From 2d48f17edf980af17582a36d8033a15b11db96ee Mon Sep 17 00:00:00 2001 From: church89 Date: Thu, 14 Sep 2023 12:03:23 +0200 Subject: [PATCH 12/59] prevent to openmc batchwise_root when to there --- openmc/deplete/stepresult.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/openmc/deplete/stepresult.py b/openmc/deplete/stepresult.py index 5b4b8d2d3f9..584c036e766 100644 --- a/openmc/deplete/stepresult.py +++ b/openmc/deplete/stepresult.py @@ -468,19 +468,21 @@ def from_hdf5(cls, handle, step): else: # Older versions used "power" instead of "source_rate" source_rate_dset = handle["/power"] - root_dset = handle["/batchwise_root"] results.data = number_dset[step, :, :, :] results.k = eigenvalues_dset[step, :] results.time = time_dset[step, :] results.source_rate = source_rate_dset[step, 0] - results.batchwise = root_dset[step] if "depletion time" in handle: proc_time_dset = handle["/depletion time"] if step < proc_time_dset.shape[0]: results.proc_time = proc_time_dset[step] + if "batchwise_root" in handle: + root_dset = handle["/batchwise_root"] + results.batchwise = root_dset[step] + if results.proc_time is None: results.proc_time = np.array([np.nan]) From d56fb98e16513b5ff719abc151cba1a6e00303b7 Mon Sep 17 00:00:00 2001 From: Lorenzo Chierici Date: Mon, 5 Feb 2024 12:43:20 +0100 Subject: [PATCH 13/59] Apply suggestions from code review Co-authored-by: Andrew Johnson --- openmc/deplete/abc.py | 4 ++-- openmc/deplete/batchwise.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py index 6a4753bb6b5..3f926c4c99e 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -802,7 +802,7 @@ def integrate(self, final_step=True, output=True): # Solve transport equation (or obtain result from restart) if i > 0 or self.operator.prev_res is None: # Update geometry/material according to batchwise definition - if self.batchwise and source_rate != 0.0: + if self.batchwise is not None and source_rate != 0.0: n, root = self._get_bos_from_batchwise(i, n) else: root = None @@ -830,7 +830,7 @@ def integrate(self, final_step=True, output=True): # solve) if output and final_step and comm.rank == 0: print(f"[openmc.deplete] t={t} (final operator evaluation)") - if self.batchwise and source_rate != 0.0: + if self.batchwise is not None and source_rate != 0.0: n, root = self._get_bos_from_batchwise(i+1, n) else: root = None diff --git a/openmc/deplete/batchwise.py b/openmc/deplete/batchwise.py index 955d2878b78..4abda675bcc 100644 --- a/openmc/deplete/batchwise.py +++ b/openmc/deplete/batchwise.py @@ -58,11 +58,11 @@ class Batchwise(ABC): This is equivalent to the `target` parameter of the `search_for_keff`. Default to 1.0. print_iterations : Bool, Optional - Whether or not to print `search_for_keff` iterations. + Print a status message each iteration Default to True search_for_keff_output : Bool, Optional - Whether or not to print transport iterations during `search_for_keff`. - Default to False + Print full transport logs during iterations (e.g., inactive generations, active + generations). Default to False Attributes ---------- burn_mats : list of str From 5e2023adc6123ec8fb9b706c355c3a3b918ac81d Mon Sep 17 00:00:00 2001 From: church89 Date: Mon, 5 Feb 2024 16:23:49 +0100 Subject: [PATCH 14/59] address comments by @drewejohnson --- openmc/deplete/abc.py | 11 ++- openmc/deplete/batchwise.py | 132 +++++++++++++++++------------------- 2 files changed, 66 insertions(+), 77 deletions(-) diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py index 3f926c4c99e..6b3a07f4dd7 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -640,7 +640,7 @@ def __init__(self, operator, timesteps, power=None, power_density=None, self.source_rates = asarray(source_rates) self.transfer_rates = None - self.batchwise = None + self._batchwise = None if isinstance(solver, str): # Delay importing of cram module, which requires this file @@ -773,7 +773,7 @@ def _get_bos_from_batchwise(self, step_index, bos_conc): """ x = deepcopy(bos_conc) # Get new vector after keff criticality control - x, root = self.batchwise.search_for_keff(x, step_index) + x, root = self._batchwise.search_for_keff(x, step_index) return x, root def integrate(self, final_step=True, output=True): @@ -802,7 +802,7 @@ def integrate(self, final_step=True, output=True): # Solve transport equation (or obtain result from restart) if i > 0 or self.operator.prev_res is None: # Update geometry/material according to batchwise definition - if self.batchwise is not None and source_rate != 0.0: + if self._batchwise is not None and source_rate != 0.0: n, root = self._get_bos_from_batchwise(i, n) else: root = None @@ -830,7 +830,7 @@ def integrate(self, final_step=True, output=True): # solve) if output and final_step and comm.rank == 0: print(f"[openmc.deplete] t={t} (final operator evaluation)") - if self.batchwise is not None and source_rate != 0.0: + if self._batchwise is not None and source_rate != 0.0: n, root = self._get_bos_from_batchwise(i+1, n) else: root = None @@ -893,8 +893,7 @@ def add_batchwise(self, obj, attr, **kwargs): elif attr == 'refuel': batchwise = BatchwiseMaterialRefuel - self.batchwise = batchwise.from_params(obj, attr, self.operator, - self.operator.model, **kwargs) + self._batchwise = batchwise.from_params(obj, attr, self.operator,**kwargs) @add_params class SIIntegrator(Integrator): diff --git a/openmc/deplete/batchwise.py b/openmc/deplete/batchwise.py index 4abda675bcc..f716aa1b3dc 100644 --- a/openmc/deplete/batchwise.py +++ b/openmc/deplete/batchwise.py @@ -24,8 +24,8 @@ class Batchwise(ABC): keff equal to one, while running transport-depletion caculations. Specific classes for running batch wise depletion calculations are - implemented as derived class of Batchwise - . + implemented as derived class of Batchwise. + .. versionadded:: 0.13.4 Parameters @@ -71,7 +71,7 @@ class Batchwise(ABC): All burnable material IDs being managed by a single process List of burnable materials ids """ - def __init__(self, operator, model, bracket, bracket_limit, + def __init__(self, operator, bracket, bracket_limit, density_treatment = 'constant-volume', bracketed_method='brentq', tol=0.01, target=1.0, print_iterations=True, search_for_keff_output=True): @@ -79,16 +79,12 @@ def __init__(self, operator, model, bracket, bracket_limit, self.operator = operator self.burn_mats = operator.burnable_mats self.local_mats = operator.local_mats - self.model = model - self.geometry = model.geometry + self.model = operator.model + self.geometry = operator.model.geometry check_value('density_treatment', density_treatment, ('constant-density','constant-volume')) self.density_treatment = density_treatment - - check_iterable_type('bracket', bracket, Real) - check_length('bracket', bracket, 2) - check_less_than('bracket values', bracket[0], bracket[1]) self.bracket = bracket check_iterable_type('bracket_limit', bracket_limit, Real) @@ -97,14 +93,10 @@ def __init__(self, operator, model, bracket, bracket_limit, bracket_limit[0], bracket_limit[1]) self.bracket_limit = bracket_limit - check_value('bracketed_method', bracketed_method, - _SCALAR_BRACKETED_METHODS) self.bracketed_method = bracketed_method - check_type('tol', tol, Real) self.tol = tol - check_type('target', target, Real) self.target = target self.print_iterations = print_iterations @@ -140,8 +132,8 @@ def target(self, value): self._target = value @classmethod - def from_params(cls, obj, attr, operator, model, **kwargs): - return cls(obj, attr, operator, model, **kwargs) + def from_params(cls, obj, attr, operator, **kwargs): + return cls(obj, attr, operator, **kwargs) @abstractmethod def _model_builder(self, param): @@ -185,19 +177,13 @@ def _search_for_keff(self, val): #make sure we don't modify original bracket and tol values bracket = deepcopy(self.bracket) - #search_for_keff tolerance should vary according to the first guess value - if abs(val) > 1.0: - tol = self.tol / abs(val) - else: - tol = self.tol - # Run until a search_for_keff root is found or out of limits root = None while root == None: search = search_for_keff(self._model_builder, bracket = np.array(bracket) + val, - tol = tol, + tol = self.tol, bracketed_method = self.bracketed_method, target = self.target, print_iterations = self.print_iterations, @@ -432,17 +418,20 @@ class BatchwiseCell(Batchwise): Directional axis for geometrical parametrization, where 0, 1 and 2 stand for 'x', 'y' and 'z', respectively. """ - def __init__(self, cell, operator, model, bracket, bracket_limit, axis=None, + def __init__(self, cell, operator, bracket, bracket_limit, axis=None, density_treatment='constant-volume', bracketed_method='brentq', tol=0.01, target=1.0, print_iterations=True, search_for_keff_output=True): - super().__init__(operator, model, bracket, bracket_limit, density_treatment, + super().__init__(operator, bracket, bracket_limit, density_treatment, bracketed_method, tol, target, print_iterations, search_for_keff_output) self.cell = self._get_cell(cell) + # Initialize to None, as openmc.lib is not initialized yet here + self._cell = None + if axis is not None: #index of cell directional axis check_value('axis', axis, (0,1,2)) @@ -471,6 +460,13 @@ def _set_cell_attrib(self, val): cell coefficient to set """ + def _set_cell(self): + """ + Set _cell attribute to the corresponding openmc.lib.cell + """ + self._cell = [cell for cell in openmc.lib.cells.values() \ + if cell.id == self.cell.id][0] + def _get_cell(self, val): """Helper method for getting cell from cell instance or cell name or id. Parameters @@ -481,31 +477,24 @@ def _get_cell(self, val): val : openmc.Cell Openmc Cell """ + all_cells = [(cell.id, cell.name, cell) for cell in \ + self.geometry.get_all_cells().values()] + if isinstance(val, Cell): - check_value('Cell exists', val, [cell for cell in \ - self.geometry.get_all_cells().values()]) + check_value('Cell exists', val, [cell[2] for cell in all_cells]) elif isinstance(val, str): if val.isnumeric(): - check_value('Cell id exists', int(val), [cell.id for cell in \ - self.geometry.get_all_cells().values()]) - val = [cell for cell in \ - self.geometry.get_all_cells().values() \ - if cell.id == int(val)][0] - else: - check_value('Cell name exists', val, [cell.name for cell in \ - self.geometry.get_all_cells().values()]) + check_value('Cell id exists', int(val), [cell[0] for cell in all_cells]) + val = [cell[2] for cell in all_cells if cell[0] == int(val)][0] - val = [cell for cell in \ - self.geometry.get_all_cells().values() \ - if cell.name == val][0] + else: + check_value('Cell name exists', val, [cell[1] for cell in all_cells]) + val = [cell[2] for cell in all_cells if cell[1] == val][0] elif isinstance(val, int): - check_value('Cell id exists', val, [cell.id for cell in \ - self.geometry.get_all_cells().values()]) - val = [cell for cell in \ - self.geometry.get_all_cells().values() \ - if cell.id == val][0] + check_value('Cell id exists', val, [cell[0] for cell in all_cells]) + val = [cell[2] for cell in all_cells if cell[0] == val][0] else: ValueError(f'Cell: {val} is not supported') @@ -548,7 +537,13 @@ def search_for_keff(self, x, step_index): ------- x : list of numpy.ndarray Updated total atoms concentrations + root : float + Search_for_keff returned root value """ + # set _cell argument, once openmc.lib is intialized + if self._cell is None: + self._set_cell() + # Get cell attribute from previous iteration val = self._get_cell_attrib() check_type('Cell coeff', val, Real) @@ -556,10 +551,10 @@ def search_for_keff(self, x, step_index): # Update all material densities from concentration vectors #before performing the search_for_keff. This is needed before running # the transport equations in the search_for_keff algorithm - super()._update_materials(x) + self._update_materials(x) # Calculate new cell attribute - root = super()._search_for_keff(val) + root = self._search_for_keff(val) # set results value as attribute in the geometry self._set_cell_attrib(root) @@ -569,7 +564,7 @@ def search_for_keff(self, x, step_index): # new volume if self.cell_materials: volumes = self._calculate_volumes() - x = super()._update_x_and_set_volumes(x, volumes) + x = self._update_x_and_set_volumes(x, volumes) return x, root @@ -640,12 +635,12 @@ class BatchwiseCellGeometrical(BatchwiseCell): Number of samples used to generate volume estimates for stochastic volume calculations. """ - def __init__(self, cell, attrib_name, operator, model, bracket, + def __init__(self, cell, attrib_name, operator, bracket, bracket_limit, axis, density_treatment='constant-volume', bracketed_method='brentq', tol=0.01, target=1.0, samples=1000000, print_iterations=True, search_for_keff_output=True): - super().__init__(cell, operator, model, bracket, bracket_limit, axis, + super().__init__(cell, operator, bracket, bracket_limit, axis, density_treatment, bracketed_method, tol, target, print_iterations, search_for_keff_output) @@ -676,12 +671,10 @@ def _get_cell_attrib(self): coeff : float cell coefficient """ - for cell in openmc.lib.cells.values(): - if cell.id == self.cell.id: - if self.attrib_name == 'translation': - return cell.translation[self.axis] - elif self.attrib_name == 'rotation': - return cell.rotation[self.axis] + if self.attrib_name == 'translation': + return self._cell.translation[self.axis] + elif self.attrib_name == 'rotation': + return self._cell.rotation[self.axis] def _set_cell_attrib(self, val): """ @@ -694,9 +687,7 @@ def _set_cell_attrib(self, val): Cell coefficient to set, in cm for translation and deg for rotation """ self.vector[self.axis] = val - for cell in openmc.lib.cells.values(): - if cell.id == self.cell.id: - setattr(cell, self.attrib_name, self.vector) + setattr(self._cell, self.attrib_name, self.vector) def _initialize_volume_calc(self): """ @@ -782,18 +773,19 @@ class BatchwiseCellTemperature(BatchwiseCell): cell : openmc.Cell or int or str OpenMC Cell identifier to where apply batch wise scheme """ - def __init__(self, cell, attrib_name, operator, model, bracket, bracket_limit, + def __init__(self, cell, attrib_name, operator, bracket, bracket_limit, axis=None, density_treatment='constant-volume', bracketed_method='brentq', tol=0.01, target=1.0, print_iterations=True, search_for_keff_output=True): - super().__init__(cell, operator, model, bracket, bracket_limit, axis, + super().__init__(cell, operator, bracket, bracket_limit, axis, density_treatment, bracketed_method, tol, target, print_iterations, search_for_keff_output) # Not needed but used for consistency with other classes check_value('attrib_name', attrib_name, 'temperature') self.attrib_name = attrib_name + # check if initial temperature has been set to right cell material if isinstance(self.cell.fill, openmc.Universe): cells = [cell for cell in self.cell.fill.cells.values() \ @@ -811,9 +803,7 @@ def _get_cell_attrib(self): coeff : float cell temperature, in Kelvin """ - for cell in openmc.lib.cells.values(): - if cell.id == self.cell.id: - return cell.get_temperature() + return self._cell.get_temperature() def _set_cell_attrib(self, val): """ @@ -823,9 +813,7 @@ def _set_cell_attrib(self, val): val : float Cell temperature to set, in Kelvin """ - for cell in openmc.lib.cells.values(): - if cell.id == self.cell.id: - cell.set_temperature(val) + self._cell.set_temperature(val) class BatchwiseMaterial(Batchwise): """Abstract class holding batch wise material-based functions. @@ -884,12 +872,12 @@ class BatchwiseMaterial(Batchwise): represents a nuclide and its weight fraction, respectively. """ - def __init__(self, material, operator, model, mat_vector, bracket, + def __init__(self, material, operator, mat_vector, bracket, bracket_limit, density_treatment='constant-volume', bracketed_method='brentq', tol=0.01, target=1.0, print_iterations=True, search_for_keff_output=True): - super().__init__(operator, model, bracket, bracket_limit, + super().__init__(operator, bracket, bracket_limit, density_treatment, bracketed_method, tol, target, print_iterations, search_for_keff_output) @@ -972,17 +960,19 @@ def search_for_keff(self, x, step_index): ------- x : list of numpy.ndarray Updated total atoms concentrations + root : float + Search_for_keff returned root value """ # Update AtomNumber with new conc vectors x. Materials are also updated # even though they are also re-calculated when running the search_for_kef - super()._update_materials(x) + self._update_materials(x) # Solve search_for_keff and find new value - root = super()._search_for_keff(0) + root = self._search_for_keff(0) #Update concentration vector and volumes with new value volumes = self._calculate_volumes(root) - x = super()._update_x_and_set_volumes(x, volumes) + x = self._update_x_and_set_volumes(x, volumes) return x, root @@ -1048,12 +1038,12 @@ class BatchwiseMaterialRefuel(BatchwiseMaterial): represents a nuclide and its weight fraction, respectively. """ - def __init__(self, material, attrib_name, operator, model, mat_vector, bracket, + def __init__(self, material, attrib_name, operator, mat_vector, bracket, bracket_limit, density_treatment='constant-volume', bracketed_method='brentq', tol=0.01, target=1.0, print_iterations=True, search_for_keff_output=True): - super().__init__(material, operator, model, mat_vector, bracket, + super().__init__(material, operator, mat_vector, bracket, bracket_limit, density_treatment, bracketed_method, tol, target, print_iterations, search_for_keff_output) From 4a2788ea0feae3db54364bfe534e1cedc8344331 Mon Sep 17 00:00:00 2001 From: church89 Date: Tue, 6 Feb 2024 13:53:37 +0100 Subject: [PATCH 15/59] fix some docstrings --- openmc/deplete/batchwise.py | 59 +++++++++++++++++++------------------ 1 file changed, 30 insertions(+), 29 deletions(-) diff --git a/openmc/deplete/batchwise.py b/openmc/deplete/batchwise.py index f716aa1b3dc..9e6bc746b60 100644 --- a/openmc/deplete/batchwise.py +++ b/openmc/deplete/batchwise.py @@ -32,8 +32,6 @@ class Batchwise(ABC): ---------- operator : openmc.deplete.Operator OpenMC operator object - model : openmc.model.Model - OpenMC model object bracket : list of float Bracketing interval to search for the solution, always relative to the solution at previous step. @@ -92,13 +90,9 @@ def __init__(self, operator, bracket, bracket_limit, check_less_than('bracket limit values', bracket_limit[0], bracket_limit[1]) self.bracket_limit = bracket_limit - self.bracketed_method = bracketed_method - self.tol = tol - self.target = target - self.print_iterations = print_iterations self.search_for_keff_output = search_for_keff_output @@ -179,7 +173,6 @@ def _search_for_keff(self, val): # Run until a search_for_keff root is found or out of limits root = None - while root == None: search = search_for_keff(self._model_builder, bracket = np.array(bracket) + val, @@ -376,8 +369,6 @@ class BatchwiseCell(Batchwise): OpenMC Cell identifier to where apply batch wise scheme operator : openmc.deplete.Operator OpenMC operator object - model : openmc.model.Model - OpenMC model object bracket : list of float Bracketing interval to search for the solution, always relative to the solution at previous step. @@ -407,6 +398,7 @@ class BatchwiseCell(Batchwise): search_for_keff_output : Bool, Optional Whether or not to print transport iterations during `search_for_keff`. Default to False + Attributes ---------- cell : openmc.Cell or int or str @@ -414,9 +406,12 @@ class BatchwiseCell(Batchwise): cell_materials : list of openmc.Material Depletable materials that fill the Cell Universe. Only valid for translation or rotation attributes + lib_cell : openmc.lib.Cell + Corresponding openmc.lib.Cell once openmc.lib is initialized axis : int {0,1,2} Directional axis for geometrical parametrization, where 0, 1 and 2 stand for 'x', 'y' and 'z', respectively. + """ def __init__(self, cell, operator, bracket, bracket_limit, axis=None, density_treatment='constant-volume', bracketed_method='brentq', @@ -430,7 +425,7 @@ def __init__(self, cell, operator, bracket, bracket_limit, axis=None, self.cell = self._get_cell(cell) # Initialize to None, as openmc.lib is not initialized yet here - self._cell = None + self.lib_cell = None if axis is not None: #index of cell directional axis @@ -460,11 +455,11 @@ def _set_cell_attrib(self, val): cell coefficient to set """ - def _set_cell(self): + def _set_lib_cell(self): """ - Set _cell attribute to the corresponding openmc.lib.cell + Set openmc.lib.cell cell to self.lib_cell attribute """ - self._cell = [cell for cell in openmc.lib.cells.values() \ + self.lib_cell = [cell for cell in openmc.lib.cells.values() \ if cell.id == self.cell.id][0] def _get_cell(self, val): @@ -541,8 +536,8 @@ def search_for_keff(self, x, step_index): Search_for_keff returned root value """ # set _cell argument, once openmc.lib is intialized - if self._cell is None: - self._set_cell() + if self.lib_cell is None: + self._set_lib_cell() # Get cell attribute from previous iteration val = self._get_cell_attrib() @@ -588,8 +583,6 @@ class BatchwiseCellGeometrical(BatchwiseCell): Cell attribute type operator : openmc.deplete.Operator OpenMC operator object - model : openmc.model.Model - OpenMC model object axis : int {0,1,2} Directional axis for geometrical parametrization, where 0, 1 and 2 stand for 'x', 'y' and 'z', respectively. @@ -622,18 +615,22 @@ class BatchwiseCellGeometrical(BatchwiseCell): search_for_keff_output : Bool, Optional Whether or not to print transport iterations during `search_for_keff`. Default to False + Attributes ---------- cell : openmc.Cell or int or str OpenMC Cell identifier to where apply batch wise scheme attrib_name : str {'translation', 'rotation'} Cell attribute type + vector : numpy.ndarray + Array storing vector of translation or rotation axis : int {0,1,2} Directional axis for geometrical parametrization, where 0, 1 and 2 stand for 'x', 'y' and 'z', respectively. samples : int Number of samples used to generate volume estimates for stochastic volume calculations. + """ def __init__(self, cell, attrib_name, operator, bracket, bracket_limit, axis, density_treatment='constant-volume', @@ -672,9 +669,9 @@ def _get_cell_attrib(self): cell coefficient """ if self.attrib_name == 'translation': - return self._cell.translation[self.axis] + return self.lib_cell.translation[self.axis] elif self.attrib_name == 'rotation': - return self._cell.rotation[self.axis] + return self.lib_cell.rotation[self.axis] def _set_cell_attrib(self, val): """ @@ -687,7 +684,7 @@ def _set_cell_attrib(self, val): Cell coefficient to set, in cm for translation and deg for rotation """ self.vector[self.axis] = val - setattr(self._cell, self.attrib_name, self.vector) + setattr(self.lib_cell, self.attrib_name, self.vector) def _initialize_volume_calc(self): """ @@ -735,8 +732,6 @@ class BatchwiseCellTemperature(BatchwiseCell): OpenMC Cell identifier to where apply batch wise scheme operator : openmc.deplete.Operator OpenMC operator object - model : openmc.model.Model - OpenMC model object attrib_name : str Cell attribute type bracket : list of float @@ -768,10 +763,14 @@ class BatchwiseCellTemperature(BatchwiseCell): search_for_keff_output : Bool, Optional Whether or not to print transport iterations during `search_for_keff`. Default to False + Attributes ---------- cell : openmc.Cell or int or str OpenMC Cell identifier to where apply batch wise scheme + attrib_name : str {'temperature'} + Cell attribute type + """ def __init__(self, cell, attrib_name, operator, bracket, bracket_limit, axis=None, density_treatment='constant-volume', @@ -803,7 +802,7 @@ def _get_cell_attrib(self): coeff : float cell temperature, in Kelvin """ - return self._cell.get_temperature() + return self.lib_cell.get_temperature() def _set_cell_attrib(self, val): """ @@ -813,7 +812,7 @@ def _set_cell_attrib(self, val): val : float Cell temperature to set, in Kelvin """ - self._cell.set_temperature(val) + self.lib_cell.set_temperature(val) class BatchwiseMaterial(Batchwise): """Abstract class holding batch wise material-based functions. @@ -829,8 +828,6 @@ class BatchwiseMaterial(Batchwise): OpenMC Material identifier to where apply batch wise scheme operator : openmc.deplete.Operator OpenMC operator object - model : openmc.model.Model - OpenMC model object mat_vector : dict Dictionary of material composition to parameterize, where a pair key value represents a nuclide and its weight fraction, respectively. @@ -863,6 +860,7 @@ class BatchwiseMaterial(Batchwise): search_for_keff_output : Bool, Optional Whether or not to print transport iterations during `search_for_keff`. Default to False + Attributes ---------- material : openmc.Material or int or str @@ -870,6 +868,7 @@ class BatchwiseMaterial(Batchwise): mat_vector : dict Dictionary of material composition to parameterize, where a pair key value represents a nuclide and its weight fraction, respectively. + """ def __init__(self, material, operator, mat_vector, bracket, @@ -994,9 +993,7 @@ class BatchwiseMaterialRefuel(BatchwiseMaterial): operator : openmc.deplete.Operator OpenMC operator object attrib_name : str - Material attribute - model : openmc.model.Model - OpenMC model object + Material attribute name mat_vector : dict Dictionary of material composition to parameterize, where a pair key value represents a nuclide and its weight fraction, respectively. @@ -1029,6 +1026,7 @@ class BatchwiseMaterialRefuel(BatchwiseMaterial): search_for_keff_output : Bool, Optional Whether or not to print transport iterations during `search_for_keff`. Default to False + Attributes ---------- material : openmc.Material or int or str @@ -1036,6 +1034,9 @@ class BatchwiseMaterialRefuel(BatchwiseMaterial): mat_vector : dict Dictionary of material composition to parameterize, where a pair key value represents a nuclide and its weight fraction, respectively. + attrib_name : str + Material attribute name + """ def __init__(self, material, attrib_name, operator, mat_vector, bracket, From 3110e450b43b4cc3ef56e52cf2cbf8357cf606d4 Mon Sep 17 00:00:00 2001 From: church89 Date: Tue, 6 Feb 2024 14:11:10 +0100 Subject: [PATCH 16/59] reformatted with black --- openmc/deplete/batchwise.py | 543 +++++++++++++++++++++++------------- 1 file changed, 349 insertions(+), 194 deletions(-) diff --git a/openmc/deplete/batchwise.py b/openmc/deplete/batchwise.py index 9e6bc746b60..f0e21bc2258 100644 --- a/openmc/deplete/batchwise.py +++ b/openmc/deplete/batchwise.py @@ -9,8 +9,14 @@ from openmc.search import _SCALAR_BRACKETED_METHODS, search_for_keff from openmc import Material, Cell from openmc.data import atomic_mass, AVOGADRO -from openmc.checkvalue import (check_type, check_value, check_less_than, - check_iterable_type, check_length) +from openmc.checkvalue import ( + check_type, + check_value, + check_less_than, + check_iterable_type, + check_length, +) + class Batchwise(ABC): """Abstract class defining a generalized batch wise scheme. @@ -69,10 +75,19 @@ class Batchwise(ABC): All burnable material IDs being managed by a single process List of burnable materials ids """ - def __init__(self, operator, bracket, bracket_limit, - density_treatment = 'constant-volume', bracketed_method='brentq', - tol=0.01, target=1.0, print_iterations=True, - search_for_keff_output=True): + + def __init__( + self, + operator, + bracket, + bracket_limit, + density_treatment="constant-volume", + bracketed_method="brentq", + tol=0.01, + target=1.0, + print_iterations=True, + search_for_keff_output=True, + ): self.operator = operator self.burn_mats = operator.burnable_mats @@ -80,15 +95,17 @@ def __init__(self, operator, bracket, bracket_limit, self.model = operator.model self.geometry = operator.model.geometry - check_value('density_treatment', density_treatment, - ('constant-density','constant-volume')) + check_value( + "density_treatment", + density_treatment, + ("constant-density", "constant-volume"), + ) self.density_treatment = density_treatment self.bracket = bracket - check_iterable_type('bracket_limit', bracket_limit, Real) - check_length('bracket_limit', bracket_limit, 2) - check_less_than('bracket limit values', - bracket_limit[0], bracket_limit[1]) + check_iterable_type("bracket_limit", bracket_limit, Real) + check_length("bracket_limit", bracket_limit, 2) + check_less_than("bracket limit values", bracket_limit[0], bracket_limit[1]) self.bracket_limit = bracket_limit self.bracketed_method = bracketed_method self.tol = tol @@ -102,9 +119,9 @@ def bracketed_method(self): @bracketed_method.setter def bracketed_method(self, value): - check_value('bracketed_method', value, _SCALAR_BRACKETED_METHODS) - if value != 'brentq': - warn('WARNING: brentq bracketed method is recommended') + check_value("bracketed_method", value, _SCALAR_BRACKETED_METHODS) + if value != "brentq": + warn("WARNING: brentq bracketed method is recommended") self._bracketed_method = value @property @@ -131,15 +148,16 @@ def from_params(cls, obj, attr, operator, **kwargs): @abstractmethod def _model_builder(self, param): - """ - Builds the parametric model to be passed to the + """Builds the parametric model to be passed to the :meth:`openmc.search.search_for_keff` method. Callable function which builds a model according to a passed parameter. This function must return an openmc.model.Model object. + Parameters ---------- param : parameter model function variable + Returns ------- _model : openmc.model.Model @@ -147,8 +165,7 @@ def _model_builder(self, param): """ def _search_for_keff(self, val): - """ - Perform the criticality search for a given parametric model. + """Perform the criticality search for a given parametric model. If the solution lies off the initial bracket, this method iteratively adapt it until :meth:`openmc.search.search_for_keff` return a valid solution. @@ -158,56 +175,67 @@ def _search_for_keff(self, val): A bracket limit pose the upper and lower boundaries to the adapting bracket. If one limit is hit, the algorithm will stop and the closest limit value will be used. + Parameters ---------- val : float Previous result value + Returns ------- root : float Estimated value of the variable parameter where keff is the targeted value """ - #make sure we don't modify original bracket and tol values + # make sure we don't modify original bracket and tol values bracket = deepcopy(self.bracket) # Run until a search_for_keff root is found or out of limits root = None while root == None: - search = search_for_keff(self._model_builder, - bracket = np.array(bracket) + val, - tol = self.tol, - bracketed_method = self.bracketed_method, - target = self.target, - print_iterations = self.print_iterations, - run_args = {'output': self.search_for_keff_output}, - run_in_memory = True) + search = search_for_keff( + self._model_builder, + bracket=np.array(bracket) + val, + tol=self.tol, + bracketed_method=self.bracketed_method, + target=self.target, + print_iterations=self.print_iterations, + run_args={"output": self.search_for_keff_output}, + run_in_memory=True, + ) # if len(search) is 3 search_for_keff was successful if len(search) == 3: - res,_,_ = search + res, _, _ = search - #Check if root is within bracket limits - if self.bracket_limit[0] < res < self.bracket_limit[1]: + # Check if root is within bracket limits + if self.bracket_limit[0] < res < self.bracket_limit[1]: root = res else: # Set res with the closest limit and continue arg_min = abs(np.array(self.bracket_limit) - res).argmin() - warn("WARNING: Search_for_keff returned root out of " - "bracket limit. Set root to {:.2f} and continue." - .format(self.bracket_limit[arg_min])) + warn( + "WARNING: Search_for_keff returned root out of " + "bracket limit. Set root to {:.2f} and continue.".format( + self.bracket_limit[arg_min] + ) + ) root = self.bracket_limit[arg_min] elif len(search) == 2: guesses, keffs = search - #Check if all guesses are within bracket limits - if all(self.bracket_limit[0] < guess < self.bracket_limit[1] \ - for guess in guesses): - #Simple method to iteratively adapt the bracket - print("Search_for_keff returned values below or above " - "target. Trying to iteratively adapt bracket...") + # Check if all guesses are within bracket limits + if all( + self.bracket_limit[0] < guess < self.bracket_limit[1] + for guess in guesses + ): + # Simple method to iteratively adapt the bracket + print( + "Search_for_keff returned values below or above " + "target. Trying to iteratively adapt bracket..." + ) # if the bracket ends up being smaller than the std of the # keff's closer value to target, no need to continue- @@ -227,33 +255,37 @@ def _search_for_keff(self, val): else: dir = -1 bracket[np.argmin(keffs)] = bracket[np.argmax(keffs)] - bracket[np.argmax(keffs)] += grad * (self.target - \ - max(keffs).n) * dir + bracket[np.argmax(keffs)] += ( + grad * (self.target - max(keffs).n) * dir + ) else: if guesses[np.argmax(keffs)] > guesses[np.argmin(keffs)]: dir = -1 else: dir = 1 bracket[np.argmax(keffs)] = bracket[np.argmin(keffs)] - bracket[np.argmin(keffs)] += grad * (min(keffs).n - \ - self.target) * dir + bracket[np.argmin(keffs)] += ( + grad * (min(keffs).n - self.target) * dir + ) else: # Set res with closest limit and continue arg_min = abs(np.array(self.bracket_limit) - guesses).argmin() - warn("WARNING: Adaptive iterative bracket went off " - "bracket limits. Set root to {:.2f} and continue." - .format(self.bracket_limit[arg_min])) + warn( + "WARNING: Adaptive iterative bracket went off " + "bracket limits. Set root to {:.2f} and continue.".format( + self.bracket_limit[arg_min] + ) + ) root = self.bracket_limit[arg_min] else: - raise ValueError('ERROR: Search_for_keff output is not valid') + raise ValueError("ERROR: Search_for_keff output is not valid") return root def _update_volumes(self): - """ - Update volumes stored in AtomNumber. + """Update volumes stored in AtomNumber. After a depletion step, both material volume and density change, due to changes in nuclides composition. At present we lack an implementation to calculate density and volume @@ -268,19 +300,19 @@ def _update_volumes(self): # Total number of atoms-gram per mol agpm = 0 for nuc in number_i.nuclides: - agpm += number_i[mat, nuc] * atomic_mass(nuc) + agpm += number_i[mat, nuc] * atomic_mass(nuc) # Get mass dens from beginning, intended to be held constant - density = openmc.lib.materials[int(mat)].get_density('g/cm3') + density = openmc.lib.materials[int(mat)].get_density("g/cm3") number_i.volume[mat_idx] = agpm / AVOGADRO / density def _update_materials(self, x): - """ - Update number density and material compositions in OpenMC on all processes. + """Update number density and material compositions in OpenMC on all processes. If density_treatment is set to 'constant-density' :meth:`openmc.deplete.batchwise._update_volumes` is called to update material volumes in AtomNumber, keeping the material total density constant, before re-normalizing the atom densities and assigning them to the model in memory. + Parameters ---------- x : list of numpy.ndarray @@ -288,7 +320,7 @@ def _update_materials(self, x): """ self.operator.number.set_density(x) - if self.density_treatment == 'constant-density': + if self.density_treatment == "constant-density": self._update_volumes() for rank in range(comm.size): @@ -310,8 +342,8 @@ def _update_materials(self, x): openmc.lib.materials[int(mat)].set_densities(nuclides, densities) def _update_x_and_set_volumes(self, x, volumes): - """ - Update x vector with new volumes, before assign them to AtomNumber. + """Update x vector with new volumes, before assign them to AtomNumber. + Parameters ---------- x : list of numpy.ndarray @@ -319,6 +351,7 @@ def _update_x_and_set_volumes(self, x, volumes): volumes : dict Updated volumes, where key is material id and value material volume, in cm3 + Returns ------- x : list of numpy.ndarray @@ -334,12 +367,13 @@ def _update_x_and_set_volumes(self, x, volumes): for nuc_idx, nuc in enumerate(number_i.burnable_nuclides): if nuc not in self.operator.nuclides_with_data: # normalize with new volume - x[mat_idx][nuc_idx] *= number_i.get_mat_volume(mat) / \ - res_vol + x[mat_idx][nuc_idx] *= number_i.get_mat_volume(mat) / res_vol # update all concentration data with the new updated volumes - for nuc, dens in zip(openmc.lib.materials[int(mat)].nuclides, - openmc.lib.materials[int(mat)].densities): + for nuc, dens in zip( + openmc.lib.materials[int(mat)].nuclides, + openmc.lib.materials[int(mat)].densities, + ): nuc_idx = number_i.index_nuc[nuc] n_of_atoms = dens / 1.0e-24 * res_vol @@ -348,13 +382,14 @@ def _update_x_and_set_volumes(self, x, volumes): x[mat_idx][nuc_idx] = n_of_atoms # when the nuclide is not in depletion chain update the AtomNumber else: - #Atom density needs to be in [#atoms/cm3] + # Atom density needs to be in [#atoms/cm3] number_i[mat, nuc] = n_of_atoms - #Now we can update the new volume in AtomNumber + # Now we can update the new volume in AtomNumber number_i.volume[mat_idx] = res_vol return x + class BatchwiseCell(Batchwise): """Abstract class holding batch wise cell-based functions. @@ -413,14 +448,33 @@ class BatchwiseCell(Batchwise): for 'x', 'y' and 'z', respectively. """ - def __init__(self, cell, operator, bracket, bracket_limit, axis=None, - density_treatment='constant-volume', bracketed_method='brentq', - tol=0.01, target=1.0, print_iterations=True, - search_for_keff_output=True): - super().__init__(operator, bracket, bracket_limit, density_treatment, - bracketed_method, tol, target, print_iterations, - search_for_keff_output) + def __init__( + self, + cell, + operator, + bracket, + bracket_limit, + axis=None, + density_treatment="constant-volume", + bracketed_method="brentq", + tol=0.01, + target=1.0, + print_iterations=True, + search_for_keff_output=True, + ): + + super().__init__( + operator, + bracket, + bracket_limit, + density_treatment, + bracketed_method, + tol, + target, + print_iterations, + search_for_keff_output, + ) self.cell = self._get_cell(cell) @@ -428,8 +482,8 @@ def __init__(self, cell, operator, bracket, bracket_limit, axis=None, self.lib_cell = None if axis is not None: - #index of cell directional axis - check_value('axis', axis, (0,1,2)) + # index of cell directional axis + check_value("axis", axis, (0, 1, 2)) self.axis = axis # list of material fill the attribute cell, if depletables @@ -437,8 +491,8 @@ def __init__(self, cell, operator, bracket, bracket_limit, axis=None, @abstractmethod def _get_cell_attrib(self): - """ - Get cell attribute coefficient. + """Get cell attribute coefficient. + Returns ------- coeff : float @@ -447,8 +501,8 @@ def _get_cell_attrib(self): @abstractmethod def _set_cell_attrib(self, val): - """ - Set cell attribute to the cell instance. + """Set cell attribute to the cell instance. + Parameters ---------- val : float @@ -456,55 +510,59 @@ def _set_cell_attrib(self, val): """ def _set_lib_cell(self): - """ - Set openmc.lib.cell cell to self.lib_cell attribute - """ - self.lib_cell = [cell for cell in openmc.lib.cells.values() \ - if cell.id == self.cell.id][0] + """Set openmc.lib.cell cell to self.lib_cell attribute""" + self.lib_cell = [ + cell for cell in openmc.lib.cells.values() if cell.id == self.cell.id + ][0] def _get_cell(self, val): """Helper method for getting cell from cell instance or cell name or id. + Parameters ---------- val : Openmc.Cell or str or int representing Cell + Returns ------- val : openmc.Cell Openmc Cell """ - all_cells = [(cell.id, cell.name, cell) for cell in \ - self.geometry.get_all_cells().values()] + all_cells = [ + (cell.id, cell.name, cell) + for cell in self.geometry.get_all_cells().values() + ] if isinstance(val, Cell): - check_value('Cell exists', val, [cell[2] for cell in all_cells]) + check_value("Cell exists", val, [cell[2] for cell in all_cells]) elif isinstance(val, str): if val.isnumeric(): - check_value('Cell id exists', int(val), [cell[0] for cell in all_cells]) + check_value("Cell id exists", int(val), [cell[0] for cell in all_cells]) val = [cell[2] for cell in all_cells if cell[0] == int(val)][0] else: - check_value('Cell name exists', val, [cell[1] for cell in all_cells]) + check_value("Cell name exists", val, [cell[1] for cell in all_cells]) val = [cell[2] for cell in all_cells if cell[1] == val][0] elif isinstance(val, int): - check_value('Cell id exists', val, [cell[0] for cell in all_cells]) + check_value("Cell id exists", val, [cell[0] for cell in all_cells]) val = [cell[2] for cell in all_cells if cell[0] == val][0] else: - ValueError(f'Cell: {val} is not supported') + ValueError(f"Cell: {val} is not supported") return val def _model_builder(self, param): - """ - Builds the parametric model to be passed to the + """Builds the parametric model to be passed to the :meth:`openmc.search.search_for_keff` method. Callable function which builds a model according to a passed parameter. This function must return an openmc.model.Model object. + Parameters ---------- param : model parametric variable cell translation coefficient or cell rotation coefficient + Returns ------- self.model : openmc.model.Model @@ -519,15 +577,16 @@ def _model_builder(self, param): return self.model def search_for_keff(self, x, step_index): - """ - Perform the criticality search on the parametric cell coefficient and + """Perform the criticality search on the parametric cell coefficient and update materials accordingly. The :meth:`openmc.search.search_for_keff` solution is then set as the new cell attribute. + Parameters ---------- x : list of numpy.ndarray Total atoms concentrations + Returns ------- x : list of numpy.ndarray @@ -541,10 +600,10 @@ def search_for_keff(self, x, step_index): # Get cell attribute from previous iteration val = self._get_cell_attrib() - check_type('Cell coeff', val, Real) + check_type("Cell coeff", val, Real) # Update all material densities from concentration vectors - #before performing the search_for_keff. This is needed before running + # before performing the search_for_keff. This is needed before running # the transport equations in the search_for_keff algorithm self._update_materials(x) @@ -565,8 +624,7 @@ def search_for_keff(self, x, step_index): class BatchwiseCellGeometrical(BatchwiseCell): - """ - Batch wise cell-based with geometrical-attribute class. + """Batch wise cell-based with geometrical-attribute class. A user doesn't need to call this class directly. Instead an instance of this class is automatically created by calling @@ -632,52 +690,75 @@ class BatchwiseCellGeometrical(BatchwiseCell): volume calculations. """ - def __init__(self, cell, attrib_name, operator, bracket, - bracket_limit, axis, density_treatment='constant-volume', - bracketed_method='brentq', tol=0.01, target=1.0, samples=1000000, - print_iterations=True, search_for_keff_output=True): - super().__init__(cell, operator, bracket, bracket_limit, axis, - density_treatment, bracketed_method, tol, target, - print_iterations, search_for_keff_output) - - check_value('attrib_name', attrib_name, - ('rotation', 'translation')) + def __init__( + self, + cell, + attrib_name, + operator, + bracket, + bracket_limit, + axis, + density_treatment="constant-volume", + bracketed_method="brentq", + tol=0.01, + target=1.0, + samples=1000000, + print_iterations=True, + search_for_keff_output=True, + ): + + super().__init__( + cell, + operator, + bracket, + bracket_limit, + axis, + density_treatment, + bracketed_method, + tol, + target, + print_iterations, + search_for_keff_output, + ) + + check_value("attrib_name", attrib_name, ("rotation", "translation")) self.attrib_name = attrib_name # check if cell is filled with 2 cells - check_length('fill materials', self.cell.fill.cells, 2) + check_length("fill materials", self.cell.fill.cells, 2) # Initialize vector self.vector = np.zeros(3) - self.cell_materials = [cell.fill for cell in \ - self.cell.fill.cells.values() if cell.fill.depletable] + self.cell_materials = [ + cell.fill for cell in self.cell.fill.cells.values() if cell.fill.depletable + ] - check_type('samples', samples, int) + check_type("samples", samples, int) self.samples = samples if self.cell_materials: self._initialize_volume_calc() def _get_cell_attrib(self): - """ - Get cell attribute coefficient. + """Get cell attribute coefficient. + Returns ------- coeff : float cell coefficient """ - if self.attrib_name == 'translation': + if self.attrib_name == "translation": return self.lib_cell.translation[self.axis] - elif self.attrib_name == 'rotation': + elif self.attrib_name == "rotation": return self.lib_cell.rotation[self.axis] def _set_cell_attrib(self, val): - """ - Set cell attribute to the cell instance. + """Set cell attribute to the cell instance. Attributes are only applied to a cell filled with a universe containing two cells itself. + Parameters ---------- val : float @@ -687,18 +768,16 @@ def _set_cell_attrib(self, val): setattr(self.lib_cell, self.attrib_name, self.vector) def _initialize_volume_calc(self): - """ - Set volume calculation model settings of depletable materials filling + """Set volume calculation model settings of depletable materials filling the parametric Cell. """ ll, ur = self.geometry.bounding_box - mat_vol = openmc.VolumeCalculation(self.cell_materials, self.samples, - ll, ur) + mat_vol = openmc.VolumeCalculation(self.cell_materials, self.samples, ll, ur) self.model.settings.volume_calculations = mat_vol def _calculate_volumes(self): - """ - Perform stochastic volume calculation + """Perform stochastic volume calculation + Returns ------- volumes : dict @@ -708,16 +787,16 @@ def _calculate_volumes(self): openmc.lib.calculate_volumes() volumes = {} if comm.rank == 0: - res = openmc.VolumeCalculation.from_hdf5('volume_1.h5') + res = openmc.VolumeCalculation.from_hdf5("volume_1.h5") for mat_idx, mat in enumerate(self.local_mats): if int(mat) in [mat.id for mat in self.cell_materials]: volumes[mat] = res.volumes[int(mat)].n volumes = comm.bcast(volumes) return volumes + class BatchwiseCellTemperature(BatchwiseCell): - """ - Batch wise cell-based with temperature-attribute class. + """Batch wise cell-based with temperature-attribute class. A user doesn't need to call this class directly. Instead an instance of this class is automatically created by calling @@ -772,31 +851,54 @@ class BatchwiseCellTemperature(BatchwiseCell): Cell attribute type """ - def __init__(self, cell, attrib_name, operator, bracket, bracket_limit, - axis=None, density_treatment='constant-volume', - bracketed_method='brentq', tol=0.01, target=1.0, - print_iterations=True, search_for_keff_output=True): - super().__init__(cell, operator, bracket, bracket_limit, axis, - density_treatment, bracketed_method, tol, target, - print_iterations, search_for_keff_output) + def __init__( + self, + cell, + attrib_name, + operator, + bracket, + bracket_limit, + axis=None, + density_treatment="constant-volume", + bracketed_method="brentq", + tol=0.01, + target=1.0, + print_iterations=True, + search_for_keff_output=True, + ): + + super().__init__( + cell, + operator, + bracket, + bracket_limit, + axis, + density_treatment, + bracketed_method, + tol, + target, + print_iterations, + search_for_keff_output, + ) # Not needed but used for consistency with other classes - check_value('attrib_name', attrib_name, 'temperature') + check_value("attrib_name", attrib_name, "temperature") self.attrib_name = attrib_name # check if initial temperature has been set to right cell material if isinstance(self.cell.fill, openmc.Universe): - cells = [cell for cell in self.cell.fill.cells.values() \ - if cell.fill.temperature] - check_length('Only one cell with temperature',cells,1) + cells = [ + cell for cell in self.cell.fill.cells.values() if cell.fill.temperature + ] + check_length("Only one cell with temperature", cells, 1) self.cell = cells[0] - check_type('temperature cell real', self.cell.fill.temperature, Real) + check_type("temperature cell real", self.cell.fill.temperature, Real) def _get_cell_attrib(self): - """ - Get cell temperature. + """Get cell temperature. + Returns ------- coeff : float @@ -805,8 +907,8 @@ def _get_cell_attrib(self): return self.lib_cell.get_temperature() def _set_cell_attrib(self, val): - """ - Set temperature value to the cell instance. + """Set temperature value to the cell instance. + Parameters ---------- val : float @@ -814,6 +916,7 @@ def _set_cell_attrib(self, val): """ self.lib_cell.set_temperature(val) + class BatchwiseMaterial(Batchwise): """Abstract class holding batch wise material-based functions. @@ -871,14 +974,32 @@ class BatchwiseMaterial(Batchwise): """ - def __init__(self, material, operator, mat_vector, bracket, - bracket_limit, density_treatment='constant-volume', - bracketed_method='brentq', tol=0.01, target=1.0, - print_iterations=True, search_for_keff_output=True): - - super().__init__(operator, bracket, bracket_limit, - density_treatment, bracketed_method, tol, target, - print_iterations, search_for_keff_output) + def __init__( + self, + material, + operator, + mat_vector, + bracket, + bracket_limit, + density_treatment="constant-volume", + bracketed_method="brentq", + tol=0.01, + target=1.0, + print_iterations=True, + search_for_keff_output=True, + ): + + super().__init__( + operator, + bracket, + bracket_limit, + density_treatment, + bracketed_method, + tol, + target, + print_iterations, + search_for_keff_output, + ) self.material = self._get_material(material) @@ -896,50 +1017,53 @@ def __init__(self, material, operator, mat_vector, bracket, def _get_material(self, val): """Helper method for getting openmc material from Material instance or material name or id. + Parameters ---------- val : Openmc.Material or str or int representing material name or id + Returns ------- val : openmc.Material Openmc Material """ if isinstance(val, Material): - check_value('Material', str(val.id), self.burn_mats) + check_value("Material", str(val.id), self.burn_mats) elif isinstance(val, str): if val.isnumeric(): - check_value('Material id', val, self.burn_mats) - val = [mat for mat in self.model.materials \ - if mat.id == int(val)][0] + check_value("Material id", val, self.burn_mats) + val = [mat for mat in self.model.materials if mat.id == int(val)][0] else: - check_value('Material name', val, - [mat.name for mat in self.model.materials if mat.depletable]) - val = [mat for mat in self.model.materials \ - if mat.name == val][0] + check_value( + "Material name", + val, + [mat.name for mat in self.model.materials if mat.depletable], + ) + val = [mat for mat in self.model.materials if mat.name == val][0] elif isinstance(val, int): - check_value('Material id', str(val), self.burn_mats) - val = [mat for mat in self.model.materials \ - if mat.id == val][0] + check_value("Material id", str(val), self.burn_mats) + val = [mat for mat in self.model.materials if mat.id == val][0] else: - ValueError(f'Material: {val} is not supported') + ValueError(f"Material: {val} is not supported") return val @abstractmethod def _model_builder(self, param): - """ - Builds the parametric model to be passed to the + """Builds the parametric model to be passed to the :meth:`openmc.search.search_for_keff` method. Callable function which builds a model according to a passed parameter. This function must return an openmc.model.Model object. + Parameters ---------- param : Model material function variable + Returns ------- _model : openmc.model.Model @@ -947,14 +1071,15 @@ def _model_builder(self, param): """ def search_for_keff(self, x, step_index): - """ - Perform the criticality search on parametric material variable. + """Perform the criticality search on parametric material variable. The :meth:`openmc.search.search_for_keff` solution is then used to calculate the new material volume and update the atoms concentrations. + Parameters ---------- x : list of numpy.ndarray Total atoms concentrations + Returns ------- x : list of numpy.ndarray @@ -969,15 +1094,15 @@ def search_for_keff(self, x, step_index): # Solve search_for_keff and find new value root = self._search_for_keff(0) - #Update concentration vector and volumes with new value + # Update concentration vector and volumes with new value volumes = self._calculate_volumes(root) x = self._update_x_and_set_volumes(x, volumes) - return x, root + return x, root + class BatchwiseMaterialRefuel(BatchwiseMaterial): - """ - Batch wise material-based class for refuelling (addition or removal) scheme. + """Batch wise material-based class for refuelling (addition or removal) scheme. A user doesn't need to call this class directly. Instead an instance of this class is automatically created by calling @@ -1039,32 +1164,54 @@ class BatchwiseMaterialRefuel(BatchwiseMaterial): """ - def __init__(self, material, attrib_name, operator, mat_vector, bracket, - bracket_limit, density_treatment='constant-volume', - bracketed_method='brentq', tol=0.01, target=1.0, - print_iterations=True, search_for_keff_output=True): - - super().__init__(material, operator, mat_vector, bracket, - bracket_limit, density_treatment, bracketed_method, tol, - target, print_iterations, search_for_keff_output) + def __init__( + self, + material, + attrib_name, + operator, + mat_vector, + bracket, + bracket_limit, + density_treatment="constant-volume", + bracketed_method="brentq", + tol=0.01, + target=1.0, + print_iterations=True, + search_for_keff_output=True, + ): + + super().__init__( + material, + operator, + mat_vector, + bracket, + bracket_limit, + density_treatment, + bracketed_method, + tol, + target, + print_iterations, + search_for_keff_output, + ) # Not needed but used for consistency with other classes - check_value('attrib_name', attrib_name, 'refuel') + check_value("attrib_name", attrib_name, "refuel") self.attrib_name = attrib_name def _model_builder(self, param): - """ - Callable function which builds a model according to a passed + """Callable function which builds a model according to a passed parameter. This function must return an openmc.model.Model object. Builds the parametric model to be passed to the :meth:`openmc.search.search_for_keff` method. The parametrization can either be at constant volume or constant density, according to user input. Default is constant volume. + Parameters ---------- param : float Model function variable, total grams of material to add or remove + Returns ------- model : openmc.model.Model @@ -1079,11 +1226,12 @@ def _model_builder(self, param): if int(mat) == self.material.id: - if self.density_treatment == 'constant-density': - vol = number_i.get_mat_volume(mat) + \ - (param / self.material.get_mass_density()) + if self.density_treatment == "constant-density": + vol = number_i.get_mat_volume(mat) + ( + param / self.material.get_mass_density() + ) - elif self.density_treatment == 'constant-volume': + elif self.density_treatment == "constant-volume": vol = number_i.get_mat_volume(mat) for nuc in number_i.index_nuc: @@ -1091,9 +1239,14 @@ def _model_builder(self, param): if nuc in self.operator.nuclides_with_data: if nuc in self.mat_vector: # units [#atoms/cm-b] - val = 1.0e-24 * (number_i.get_atom_density(mat, - nuc) + param / atomic_mass(nuc) * \ - AVOGADRO * self.mat_vector[nuc] / vol) + val = 1.0e-24 * ( + number_i.get_atom_density(mat, nuc) + + param + / atomic_mass(nuc) + * AVOGADRO + * self.mat_vector[nuc] + / vol + ) else: # get normalized atoms density in [atoms/b-cm] @@ -1114,21 +1267,22 @@ def _model_builder(self, param): nuclides.append(nuc) densities.append(val) - #set nuclides and densities to the in-memory model + # set nuclides and densities to the in-memory model openmc.lib.materials[int(mat)].set_densities(nuclides, densities) # always need to return a model return self.model def _calculate_volumes(self, res): - """ - Uses :meth:`openmc.batchwise._search_for_keff` solution as grams of + """Uses :meth:`openmc.batchwise._search_for_keff` solution as grams of material to add or remove to calculate new material volume. + Parameters ---------- res : float Solution in grams of material, coming from :meth:`openmc.batchwise._search_for_keff` + Returns ------- volumes : dict @@ -1140,9 +1294,10 @@ def _calculate_volumes(self, res): for mat in self.local_mats: if int(mat) == self.material.id: - if self.density_treatment == 'constant-density': - volumes[mat] = number_i.get_mat_volume(mat) + \ - (res / self.material.get_mass_density()) - elif self.density_treatment == 'constant-volume': + if self.density_treatment == "constant-density": + volumes[mat] = number_i.get_mat_volume(mat) + ( + res / self.material.get_mass_density() + ) + elif self.density_treatment == "constant-volume": volumes[mat] = number_i.get_mat_volume(mat) return volumes From a4f1f168648a6b63806db94e8fe9ac16ed4c96ac Mon Sep 17 00:00:00 2001 From: church89 Date: Tue, 6 Feb 2024 16:09:22 +0100 Subject: [PATCH 17/59] refactoring of _search_for_keff method --- openmc/deplete/batchwise.py | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/openmc/deplete/batchwise.py b/openmc/deplete/batchwise.py index f0e21bc2258..643169ce039 100644 --- a/openmc/deplete/batchwise.py +++ b/openmc/deplete/batchwise.py @@ -237,10 +237,17 @@ def _search_for_keff(self, val): "target. Trying to iteratively adapt bracket..." ) - # if the bracket ends up being smaller than the std of the - # keff's closer value to target, no need to continue- - if all(keff <= max(keffs).s for keff in keffs): - arg_min = abs(self.target - np.array(guesses)).argmin() + # if the difference between keffs is smaller than 1 pcm, + # the grad calculation will be overshoot, so let's set the root + # to the closest guess value + if abs(np.diff(keffs)) < 1.0e-5: + arg_min = abs(self.target - np.array(keffs)).argmin() + print( + "Difference between keff values is " + "smaller than 1 pcm. " + "Set root to guess value with " + "closest keff to target and continue..." + ) root = guesses[arg_min] # Calculate gradient as ratio of delta bracket and delta keffs @@ -268,6 +275,12 @@ def _search_for_keff(self, val): grad * (min(keffs).n - self.target) * dir ) + #check if adapted bracket are within bracketing limits + if max(bracket) + val > self.bracket_limit[1]: + bracket[np.argmax(bracket)] = self.bracket_limit[1] - val + if min(bracket) + val < self.bracket_limit[0]: + bracket[np.argmin(bracket)] = self.bracket_limit[0] - val + else: # Set res with closest limit and continue arg_min = abs(np.array(self.bracket_limit) - guesses).argmin() From 0c804dd8a725f17e77b1244f361b55a72691977e Mon Sep 17 00:00:00 2001 From: church89 Date: Tue, 6 Feb 2024 16:39:36 +0100 Subject: [PATCH 18/59] fix some spelling typos --- openmc/deplete/batchwise.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openmc/deplete/batchwise.py b/openmc/deplete/batchwise.py index 643169ce039..672f76eaa50 100644 --- a/openmc/deplete/batchwise.py +++ b/openmc/deplete/batchwise.py @@ -21,13 +21,13 @@ class Batchwise(ABC): """Abstract class defining a generalized batch wise scheme. - Batchwise schemes, such as control rod adjustment or material refueling to + Batchwise schemes, such as control rod adjustment or material refuelling to control reactivity and maintain keff constant and equal to one. A batch wise scheme can be added here to an integrator instance, such as :class:`openmc.deplete.CECMIntegrator`, to parametrize one system variable with the aim of satisfy certain design criteria, such as keeping - keff equal to one, while running transport-depletion caculations. + keff equal to one, while running transport-depletion calculations. Specific classes for running batch wise depletion calculations are implemented as derived class of Batchwise. @@ -607,7 +607,7 @@ def search_for_keff(self, x, step_index): root : float Search_for_keff returned root value """ - # set _cell argument, once openmc.lib is intialized + # set _cell argument, once openmc.lib is initialized if self.lib_cell is None: self._set_lib_cell() From 38ed171cebafdd4c71e1a949995879085406fd28 Mon Sep 17 00:00:00 2001 From: church89 Date: Wed, 23 Aug 2023 18:03:07 +0200 Subject: [PATCH 19/59] first commit to new branch --- openmc/deplete/batchwise.py | 435 ++++++++++++++++++++++++++++++++++++ 1 file changed, 435 insertions(+) create mode 100644 openmc/deplete/batchwise.py diff --git a/openmc/deplete/batchwise.py b/openmc/deplete/batchwise.py new file mode 100644 index 00000000000..03dfcf9f046 --- /dev/null +++ b/openmc/deplete/batchwise.py @@ -0,0 +1,435 @@ +from abc import ABC, abstractmethod +from collections.abc import Iterable +from openmc.search import _SCALAR_BRACKETED_METHODS, search_for_keff +from openmc import Materials, Material, Cell +from openmc.data import atomic_mass, AVOGADRO, ELEMENT_SYMBOL + +import numpy as np +import os +import h5py + +class Batchwise(ABC): + + def __init__(self, operator, model, bracket, bracket_limit, + bracketed_method='brentq', tol=0.01, target=1.0, + print_iterations=True, search_for_keff_output=True): + + self.operator = operator + self.burn_mats = operator.burnable_mats + self.local_mats = operator.local_mats + self.model = model + self.geometry = model.geometry + + check_iterable_type('bracket', bracket, Real) + check_length('bracket', bracket, 2) + check_less_than('bracket values', bracket[0], bracket[1]) + self.bracket = bracket + + check_iterable_type('bracket_limit', bracket_limit, Real) + check_length('bracket_limit', bracket_limit, 2) + check_less_than('bracket limit values', + bracket_limit[0], bracket_limit[1]) + + self.bracket_limit = bracket_limit + + self.bracketed_method = bracketed_method + self.tol = tol + self.target = target + self.print_iterations = print_iterations + self.search_for_keff_output = search_for_keff_output + + @property + def bracketed_method(self): + return self._bracketed_method + + @bracketed_method.setter + def bracketed_method(self, value): + check_value('bracketed_method', value, _SCALAR_BRACKETED_METHODS) + if value != 'brentq': + warn('brentq bracketed method is recomended here') + self._bracketed_method = value + + @property + def tol(self): + return self._tol + + @tol.setter + def tol(self, value): + check_type("tol", value, Real) + self._tol = value + + @property + def target(self): + return self._target + + @target.setter + def target(self, value): + check_type("target", value, Real) + self._target = value + + @abstractmethod + def _model_builder(self, param): + + def _get_cell_id(self, val): + """Helper method for getting cell id from cell instance or cell name. + Parameters + ---------- + val : Openmc.Cell or str or int representing Cell + Returns + ------- + id : str + Cell id + """ + if isinstance(val, Cell): + check_value('Cell id', val.id, [cell.id for cell in \ + self.geometry.get_all_cells().values()]) + val = val.id + + elif isinstance(val, str): + if val.isnumeric(): + check_value('Cell id', val, [str(cell.id) for cell in \ + self.geometry.get_all_cells().values()]) + val = int(val) + else: + check_value('Cell name', val, [cell.name for cell in \ + self.geometry.get_all_cells().values()]) + + val = [cell.id for cell in \ + self.geometry.get_all_cells().values() \ + if cell.name == val][0] + + elif isinstance(val, int): + check_value('Cell id', val, [cell.id for cell in \ + self.geometry.get_all_cells().values()]) + + else: + ValueError(f'Cell: {val} is not recognized') + + return val + + def _search_for_keff(self, val): + """ + Perform the criticality search for a given parametric model. + It supports geometrical or material based `search_for_keff`. + Parameters + ---------- + val : float + Previous result value + Returns + ------- + root : float + Estimated value of the variable parameter where keff is the + targeted value + """ + #make sure we don't modify original bracket and tol values + bracket = deepcopy(self.bracket) + + #search_for_keff tolerance should vary according to the first guess value + if abs(val) > 1.0: + tol = self.tol / abs(val) + else: + tol = self.tol + + # Run until a search_for_keff root is found or ouf ot limits + root = None + + while res == None: + search = search_for_keff(self._model_builder, + bracket = [bracket[0] + val, bracket[1] + val], + tol = tol, + bracketed_method = self.bracketed_method, + target = self.target, + print_iterations = self.print_iterations, + run_args = {'output': self.search_for_keff_output}) + + # if len(search) is 3 search_for_keff was successful + if len(search) == 3: + res,_,_ = search + + #Check if root is within bracket limits + if self.bracket_limit[0] < res < self.bracket_limit[1]: + root = res + + else: + # Set res with the closest limit and continue + arg_min = abs(np.array(self.bracket_limit) - res).argmin() + warn('WARNING: Search_for_keff returned root out of '\ + 'bracket limit. Set root to {:.2f} and continue.' + .format(self.bracket_limit[arg_min])) + root = self.bracket_limit[arg_min] + + elif len(search) == 2: + guesses, keffs = search + + #Check if all guesses are within bracket limits + if all(self.bracket_limit[0] < guess < self.bracket_limit[1] \ + for guess in guesses): + #Simple method to iteratively adapt the bracket + print('INFO: Function returned values below or above ' \ + 'target. Adapt bracket...') + + # if the bracket ends up being smaller than the std of the + # keff's closer value to target, no need to continue- + if all(keff <= max(keffs).s for keff in keffs): + arg_min = abs(self.target - np.array(guesses)).argmin() + root = guesses[arg_min] + + # Calculate gradient as ratio of delta bracket and delta keffs + grad = abs(np.diff(bracket) / np.diff(keffs))[0].n + # Move the bracket closer to presumed keff root. + + # Two cases: both keffs are below or above target + if np.mean(keffs) < self.target: + # direction of moving bracket: +1 is up, -1 is down + if guess[np.argmax(keffs)] > guess[np.argmin(keffs)]: + dir = 1 + else: + dir = -1 + bracket[np.argmin(keffs)] = bracket[np.argmax(keffs)] + bracket[np.argmax(keffs)] += grad * (self.target - \ + max(keffs).n) * dir + else: + if guess[np.argmax(keffs)] > guess[np.argmin(keffs)]: + dir = -1 + else: + dir = 1 + bracket[np.argmax(keffs)] = bracket[np.argmin(keffs)] + bracket[np.argmin(keffs)] += grad * (min(keffs).n - \ + self.target) * dir + + else: + # Set res with closest limit and continue + arg_min = abs(np.array(self.bracket_limit) - guesses).argmin() + warn('WARNING: Adaptive iterative bracket went off '\ + 'bracket limits. Set root to {:.2f} and continue.' + .format(self.bracket_limit[arg_min])) + root = self.bracket_limit[arg_min] + + else: + raise ValueError(f'ERROR: Search_for_keff output is not valid') + + return root + + def _save_res(self, type, step_index, root): + """ + Save results to msr_results.h5 file. + Parameters + ---------- + type : str + String to characterize geometry and material results + step_index : int + depletion time step index + root : float or dict + Root of the search_for_keff function + """ + filename = 'msr_results.h5' + kwargs = {'mode': "a" if os.path.isfile(filename) else "w"} + + if comm.rank == 0: + with h5py.File(filename, **kwargs) as h5: + name = '_'.join([type, str(step_index)]) + if name in list(h5.keys()): + last = sorted([int(re.split('_',i)[1]) for i in h5.keys()])[-1] + step_index = last + 1 + h5.create_dataset('_'.join([type, str(step_index)]), data=root) + + def _update_volumes_after_depletion(self, x): + """ + After a depletion step, both materials volume and density change, due to + decay, transmutation reactions and transfer rates, if set. + At present we lack an implementation to calculate density and volume + changes due to the different molecules speciation. Therefore, the assumption + is to consider the density constant and let the material volume + vary with the change in nuclides concentrations. + The method uses the nuclides concentrations coming from the previous Bateman + solution and calculates a new volume, keeping the mass density of the material + constant. It will then assign the volumes to the AtomNumber instance. + + Parameters + ---------- + x : list of numpy.ndarray + Total atom concentrations + """ + self.operator.number.set_density(x) + + for rank in range(comm.size): + number_i = comm.bcast(self.operator.number, root=rank) + + for i, mat in enumerate(number_i.materials): + # Total nuclides density + density = 0 + for nuc in number_i.nuclides: + # total number of atoms + val = number_i[mat, nuc] + # obtain nuclide density in atoms-g/mol + density += val * atomic_mass(nuc) + # Get mass dens from beginning, intended to be held constant + rho = openmc.lib.materials[int(mat)].get_density('g/cm3') + number_i.volume[i] = density / AVOGADRO / rho + +class BatchwiseCell(Batchwise): + + def __init__(self, operator, model, cell, attrib_name, axis, bracket, + bracket_limit, bracketed_method='brentq', tol=0.01, target=1.0, + print_iterations=True, search_for_keff_output=True): + + super().__init__(operator, model, bracket, bracket_limit, + bracketed_method, tol, target, print_iterations, + search_for_keff_output) + + self.cell_id = super()._get_cell_id(cell_id_or_name) + check_value('attrib_name', attrib_name, + ('rotation', 'translation')) + self.attrib_name = attrib_name + + #index of cell directionnal axis + check_value('axis', axis, (0,1,2)) + self.axis = axis + + # Initialize vector + self.vector = np.zeros(3) + + # materials that fill the attribute cell, if depletables + self.cell_material_ids = [cell.fill.id for cell in \ + self.geometry.get_all_cells()[self.cell_id].fill.cells.values() \ + if cell.fill.depletable] + + def _get_cell_attrib(self): + """ + Get cell attribute coefficient. + Returns + ------- + coeff : float + cell coefficient + """ + for cell in openmc.lib.cells.values(): + if cell.id == self.cell_id: + if self.attrib_name == 'translation': + return cell.translation[self.axis] + elif self.attrib_name == 'rotation': + return cell.rotation[self.axis] + + def _set_cell_attrib(self, val): + """ + Set cell attribute to the cell instance. + Attributes are only applied to cells filled with a universe + Parameters + ---------- + var : float + Surface coefficient to set + geometry : openmc.model.geometry + OpenMC geometry model + attrib_name : str + Currently only translation is implemented + """ + self.vector[self.axis] = val + for cell in openmc.lib.cells.values(): + if cell.id == self.cell_id or cell.name == self.cell_id: + setattr(cell, self.attrib_name, self.vector) + + def _update_materials(self, x): + """ + Assign concentration vectors from Bateman solution at previous + timestep to the in-memory model materials, after having recalculated the + material volume. + + Parameters + ---------- + x : list of numpy.ndarray + Total atom concentrations + """ + super()._update_volumes_after_depletion(x) + + for rank in range(comm.size): + number_i = comm.bcast(self.operator.number, root=rank) + + for mat in number_i.materials: + nuclides = [] + densities = [] + + for nuc in number_i.nuclides: + # get atom density in atoms/b-cm + val = 1.0e-24 * number_i.get_atom_density(mat, nuc) + if nuc in self.operator.nuclides_with_data: + if val > self.atom_density_limit: + nuclides.append(nuc) + densities.append(val) + + #set nuclide densities to model in memory (C-API) + openmc.lib.materials[int(mat)].set_densities(nuclides, densities) + + def _update_volumes(self): + openmc.lib.calculate_volumes() + res = openmc.VolumeCalculation.from_hdf5('volume_1.h5') + + number_i = self.operator.number + for mat_idx, mat_id in enumerate(self.local_mats): + if mat_id in self.cell_material_ids: + number_i.volume[mat_idx] = res.volumes[int(mat_id)].n + + def _update_x(self): + number_i = self.operator.number + for mat_idx, mat_id in enumerate(self.local_mats): + if mat_id in self.cell_material_ids: + for nuc_idx, nuc in enumerate(number_i.burnable_nuclides): + x[mat_idx][nuc_idx] = number_i.volume[mat_idx] * \ + number_i.get_atom_density(mat_idx, nuc) + return x + + def _model_builder(self, param): + """ + Builds the parametric model that is passed to the `msr_search_for_keff` + function by setting the parametric variable to the geoemetrical cell. + Parameters + ---------- + param : model parametricl variable + for examlple: cell translation coefficient + Returns + ------- + _model : openmc.model.Model + OpenMC parametric model + """ + self._set_cell_attrib(param) + #Calulate new volume and update if materials filling the cell are + # depletable + if self.cell_material_ids: + self._update_volumes() + return self.model + + def search_for_keff(self, x, step_index): + """ + Perform the criticality search on the parametric geometrical model. + Will set the root of the `search_for_keff` function to the cell + attribute. + Parameters + ---------- + x : list of numpy.ndarray + Total atoms concentrations + Returns + ------- + x : list of numpy.ndarray + Updated total atoms concentrations + """ + # Get cell attribute from previous iteration + val = self._get_cell_attrib() + check_type('Cell coeff', val, Real) + + # Update volume and concentration vectors before performing the search_for_keff + self._update_materials(x) + + # Calculate new cell attribute + root = super().search_for_keff(val) + + # set results value as attribute in the geometry + self._set_cell_attrib(root) + print('UPDATE: old value: {:.2f} cm --> ' \ + 'new value: {:.2f} cm'.format(val, root)) + + # x needs to be updated after the search with new volumes if materials cell + # depletable + if self.cell_material_ids: + self._update_x() + + #Store results + super()._save_res('geometry', step_index, root) + return x From cea8801ded905e25f5b41620d372825e7963bfa5 Mon Sep 17 00:00:00 2001 From: church89 Date: Wed, 7 Feb 2024 12:52:16 +0100 Subject: [PATCH 20/59] resolve conflicts --- openmc/deplete/__init__.py | 1 + openmc/deplete/abc.py | 53 +- openmc/deplete/batchwise.py | 1062 ++++++++++++++++---- openmc/deplete/stepresult.py | 17 +- openmc/search.py | 30 +- tests/unit_tests/test_deplete_batchwise.py | 135 +++ 6 files changed, 1098 insertions(+), 200 deletions(-) create mode 100644 tests/unit_tests/test_deplete_batchwise.py diff --git a/openmc/deplete/__init__.py b/openmc/deplete/__init__.py index 8a9509e9009..c86a93c8a19 100644 --- a/openmc/deplete/__init__.py +++ b/openmc/deplete/__init__.py @@ -17,6 +17,7 @@ from .results import * from .integrators import * from .transfer_rates import * +from .batchwise import * from . import abc from . import cram from . import helpers diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py index 60774871c01..7f06fd332f9 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -18,14 +18,16 @@ from numpy import nonzero, empty, asarray from uncertainties import ufloat -from openmc.checkvalue import check_type, check_greater_than, PathLike +from openmc.checkvalue import checkvalue, check_type, check_greater_than, PathLike from openmc.mpi import comm from .stepresult import StepResult from .chain import Chain from .results import Results from .pool import deplete from .transfer_rates import TransferRates - +from openmc import Material, Cell, Universe +from .batchwise import (BatchwiseCellGeometrical, BatchwiseCellTemperature, + BatchwiseMaterialRefuel) __all__ = [ "OperatorResult", "TransportOperator", @@ -636,6 +638,7 @@ def __init__(self, operator, timesteps, power=None, power_density=None, self.source_rates = asarray(source_rates) self.transfer_rates = None + self.batchwise = None if isinstance(solver, str): # Delay importing of cram module, which requires this file @@ -763,6 +766,14 @@ def _get_start_data(self): return (self.operator.prev_res[-1].time[-1], len(self.operator.prev_res) - 1) + def _get_bos_from_batchwise(self, step_index, bos_conc): + """Get BOS from criticality batch-wise control + """ + x = deepcopy(bos_conc) + # Get new vector after keff criticality control + x, root = self.batchwise.search_for_keff(x, step_index) + return x, root + def integrate( self, final_step: bool = True, @@ -797,6 +808,11 @@ def integrate( # Solve transport equation (or obtain result from restart) if i > 0 or self.operator.prev_res is None: + # Update geometry/material according to batchwise definition + if self.batchwise and source_rate != 0.0: + n, root = self._get_bos_from_batchwise(i, n) + else: + root = None n, res = self._get_bos_data_from_operator(i, source_rate, n) else: n, res = self._get_bos_data_from_restart(i, source_rate, n) @@ -810,9 +826,8 @@ def integrate( # Remove actual EOS concentration for next step n = n_list.pop() - StepResult.save(self.operator, n_list, res_list, [t, t + dt], - source_rate, self._i_res + i, proc_time, path) + source_rate, self._i_res + i, proc_time, root, path) t += dt @@ -822,9 +837,13 @@ def integrate( # solve) if output and final_step and comm.rank == 0: print(f"[openmc.deplete] t={t} (final operator evaluation)") + if self.batchwise and source_rate != 0.0: + n, root = self._get_bos_from_batchwise(i+1, n) + else: + root = None res_list = [self.operator(n, source_rate if final_step else 0.0)] StepResult.save(self.operator, [n], res_list, [t, t], - source_rate, self._i_res + len(self), proc_time) + source_rate, self._i_res + len(self), proc_time, root) self.operator.write_bos_data(len(self) + self._i_res) self.operator.finalize() @@ -857,6 +876,30 @@ def add_transfer_rate(self, material, components, transfer_rate, self.transfer_rates.set_transfer_rate(material, components, transfer_rate, transfer_rate_units, destination_material) + def add_batchwise(self, obj, attr, **kwargs): + """Add batchwise operation to integrator scheme. + + Parameters + ---------- + attr : str + Type of batchwise operation to add. `Trans` stands for geometrical + translation, `refuel` for material refueling and `dilute` for material + dilute. + **kwargs + keyword arguments that are passed to the batchwise class. + + """ + check_value('attribute', attr, ('translation', 'rotation', 'temperature', 'refuel')) + if attr in ('translation', 'rotation'): + batchwise = BatchwiseCellGeometrical + elif attr == 'temperature': + batchwise = BatchwiseCellTemperature + elif attr == 'refuel': + batchwise = BatchwiseMaterialRefuel + + self.batchwise = batchwise.from_params(obj, attr, self.operator, + self.operator.model, **kwargs) + @add_params class SIIntegrator(Integrator): r"""Abstract class for the Stochastic Implicit Euler integrators diff --git a/openmc/deplete/batchwise.py b/openmc/deplete/batchwise.py index 03dfcf9f046..7136f7e6b21 100644 --- a/openmc/deplete/batchwise.py +++ b/openmc/deplete/batchwise.py @@ -1,18 +1,77 @@ from abc import ABC, abstractmethod -from collections.abc import Iterable -from openmc.search import _SCALAR_BRACKETED_METHODS, search_for_keff -from openmc import Materials, Material, Cell -from openmc.data import atomic_mass, AVOGADRO, ELEMENT_SYMBOL - +from copy import deepcopy +from numbers import Real +from warnings import warn import numpy as np -import os -import h5py -class Batchwise(ABC): +import openmc.lib +from openmc.mpi import comm +from openmc.search import _SCALAR_BRACKETED_METHODS, search_for_keff +from openmc import Material, Cell +from openmc.data import atomic_mass, AVOGADRO +from openmc.checkvalue import (check_type, check_value, check_less_than, + check_iterable_type, check_length) +class Batchwise(ABC): + """Abstract class defining a generalized batchwise scheme. + + In between transport and depletion steps batchwise operations can be added + with the aim of maintain a system critical. These operations act on OpenMC + Cell or Material, parametrizing one or more coefficients while running a + search_for_kef algorithm. + + Specific classes for running batchwise depletion calculations are + implemented as derived class of Batchwise + . + .. versionadded:: 0.13.4 + + Parameters + ---------- + operator : openmc.deplete.Operator + OpenMC operator object + model : openmc.model.Model + OpenMC model object + bracket : list of float + Bracketing interval to search for the solution, always relative to the + solution at previous step. + bracket_limit : list of float + Absolute bracketing interval lower and upper; if during the adaptive + algorithm the search_for_keff solution lies off these limits the closest + limit will be set as new result. + density_treatment : str + Wether ot not to keep contant volume or density after a depletion step + before the next one. + Default to 'constant-volume' + bracketed_method : {'brentq', 'brenth', 'ridder', 'bisect'}, optional + Solution method to use. + This is equivalent to the `bracket_method` parameter of the + `search_for_keff`. + Defaults to 'brentq'. + tol : float + Tolerance for search_for_keff method. + This is equivalent to the `tol` parameter of the `search_for_keff`. + Default to 0.01 + target : Real, optional + This is equivalent to the `target` parameter of the `search_for_keff`. + Default to 1.0. + print_iterations : Bool, Optional + Wheter or not to print `search_for_keff` iterations. + Default to True + search_for_keff_output : Bool, Optional + Wheter or not to print transport iterations during `search_for_keff`. + Default to False + Attributes + ---------- + burn_mats : list of str + List of burnable materials ids + local_mats : list of str + All burnable material IDs being managed by a single process + List of burnable materials ids + """ def __init__(self, operator, model, bracket, bracket_limit, - bracketed_method='brentq', tol=0.01, target=1.0, - print_iterations=True, search_for_keff_output=True): + density_treatment = 'constant-volume', bracketed_method='brentq', + tol=0.01, target=1.0, print_iterations=True, + search_for_keff_output=True): self.operator = operator self.burn_mats = operator.burnable_mats @@ -20,6 +79,10 @@ def __init__(self, operator, model, bracket, bracket_limit, self.model = model self.geometry = model.geometry + check_value('density_treatment', density_treatment, + ('constant-density','constant-volume')) + self.density_treatment = density_treatment + check_iterable_type('bracket', bracket, Real) check_length('bracket', bracket, 2) check_less_than('bracket values', bracket[0], bracket[1]) @@ -29,12 +92,18 @@ def __init__(self, operator, model, bracket, bracket_limit, check_length('bracket_limit', bracket_limit, 2) check_less_than('bracket limit values', bracket_limit[0], bracket_limit[1]) - self.bracket_limit = bracket_limit + check_value('bracketed_method', bracketed_method, + _SCALAR_BRACKETED_METHODS) self.bracketed_method = bracketed_method + + check_type('tol', tol, Real) self.tol = tol + + check_type('target', target, Real) self.target = target + self.print_iterations = print_iterations self.search_for_keff_output = search_for_keff_output @@ -46,7 +115,7 @@ def bracketed_method(self): def bracketed_method(self, value): check_value('bracketed_method', value, _SCALAR_BRACKETED_METHODS) if value != 'brentq': - warn('brentq bracketed method is recomended here') + warn('WARNING: brentq bracketed method is recommended') self._bracketed_method = value @property @@ -69,48 +138,33 @@ def target(self, value): @abstractmethod def _model_builder(self, param): - - def _get_cell_id(self, val): - """Helper method for getting cell id from cell instance or cell name. + """ + Builds the parametric model to be passed to the `search_for_keff` + algorithm. + Callable function which builds a model according to a passed + parameter. This function must return an openmc.model.Model object. Parameters ---------- - val : Openmc.Cell or str or int representing Cell + param : parameter + model function variable Returns ------- - id : str - Cell id + _model : openmc.model.Model + OpenMC parametric model """ - if isinstance(val, Cell): - check_value('Cell id', val.id, [cell.id for cell in \ - self.geometry.get_all_cells().values()]) - val = val.id - - elif isinstance(val, str): - if val.isnumeric(): - check_value('Cell id', val, [str(cell.id) for cell in \ - self.geometry.get_all_cells().values()]) - val = int(val) - else: - check_value('Cell name', val, [cell.name for cell in \ - self.geometry.get_all_cells().values()]) - - val = [cell.id for cell in \ - self.geometry.get_all_cells().values() \ - if cell.name == val][0] - - elif isinstance(val, int): - check_value('Cell id', val, [cell.id for cell in \ - self.geometry.get_all_cells().values()]) - - else: - ValueError(f'Cell: {val} is not recognized') - - return val def _search_for_keff(self, val): """ Perform the criticality search for a given parametric model. - It supports geometrical or material based `search_for_keff`. + It supports cell or material based `search_for_keff`. + If the solution lies off the initial bracket, the method will iteratively + adapt the bracket to be able to run `search_for_keff` effectively. + The ratio between the bracket and the returned keffs values will be + the scaling factor to iteratively adapt the brackt unitl a valid solution + is found. + If the adapted bracket is moved too far off, i.e. above or below user + inputted bracket limits interval, the algorithm will stop and the closest + limit value will be used. Parameters ---------- val : float @@ -133,14 +187,15 @@ def _search_for_keff(self, val): # Run until a search_for_keff root is found or ouf ot limits root = None - while res == None: + while root == None: search = search_for_keff(self._model_builder, - bracket = [bracket[0] + val, bracket[1] + val], - tol = tol, - bracketed_method = self.bracketed_method, - target = self.target, - print_iterations = self.print_iterations, - run_args = {'output': self.search_for_keff_output}) + bracket = np.array(bracket) + val, + tol = tol, + bracketed_method = self.bracketed_method, + target = self.target, + print_iterations = self.print_iterations, + run_args = {'output': self.search_for_keff_output}, + run_in_memory = True) # if len(search) is 3 search_for_keff was successful if len(search) == 3: @@ -153,8 +208,8 @@ def _search_for_keff(self, val): else: # Set res with the closest limit and continue arg_min = abs(np.array(self.bracket_limit) - res).argmin() - warn('WARNING: Search_for_keff returned root out of '\ - 'bracket limit. Set root to {:.2f} and continue.' + warn("WARNING: Search_for_keff returned root out of " + "bracket limit. Set root to {:.2f} and continue." .format(self.bracket_limit[arg_min])) root = self.bracket_limit[arg_min] @@ -165,8 +220,8 @@ def _search_for_keff(self, val): if all(self.bracket_limit[0] < guess < self.bracket_limit[1] \ for guess in guesses): #Simple method to iteratively adapt the bracket - print('INFO: Function returned values below or above ' \ - 'target. Adapt bracket...') + print("Search_for_keff returned values below or above " + "target. Trying to iteratively adapt bracket...") # if the bracket ends up being smaller than the std of the # keff's closer value to target, no need to continue- @@ -181,7 +236,7 @@ def _search_for_keff(self, val): # Two cases: both keffs are below or above target if np.mean(keffs) < self.target: # direction of moving bracket: +1 is up, -1 is down - if guess[np.argmax(keffs)] > guess[np.argmin(keffs)]: + if guesses[np.argmax(keffs)] > guesses[np.argmin(keffs)]: dir = 1 else: dir = -1 @@ -189,7 +244,7 @@ def _search_for_keff(self, val): bracket[np.argmax(keffs)] += grad * (self.target - \ max(keffs).n) * dir else: - if guess[np.argmax(keffs)] > guess[np.argmin(keffs)]: + if guesses[np.argmax(keffs)] > guesses[np.argmin(keffs)]: dir = -1 else: dir = 1 @@ -200,51 +255,50 @@ def _search_for_keff(self, val): else: # Set res with closest limit and continue arg_min = abs(np.array(self.bracket_limit) - guesses).argmin() - warn('WARNING: Adaptive iterative bracket went off '\ - 'bracket limits. Set root to {:.2f} and continue.' + warn("WARNING: Adaptive iterative bracket went off " + "bracket limits. Set root to {:.2f} and continue." .format(self.bracket_limit[arg_min])) root = self.bracket_limit[arg_min] else: - raise ValueError(f'ERROR: Search_for_keff output is not valid') + raise ValueError('ERROR: Search_for_keff output is not valid') return root - def _save_res(self, type, step_index, root): + def _update_volumes(self): """ - Save results to msr_results.h5 file. - Parameters - ---------- - type : str - String to characterize geometry and material results - step_index : int - depletion time step index - root : float or dict - Root of the search_for_keff function - """ - filename = 'msr_results.h5' - kwargs = {'mode': "a" if os.path.isfile(filename) else "w"} - - if comm.rank == 0: - with h5py.File(filename, **kwargs) as h5: - name = '_'.join([type, str(step_index)]) - if name in list(h5.keys()): - last = sorted([int(re.split('_',i)[1]) for i in h5.keys()])[-1] - step_index = last + 1 - h5.create_dataset('_'.join([type, str(step_index)]), data=root) - - def _update_volumes_after_depletion(self, x): - """ - After a depletion step, both materials volume and density change, due to + Update number volume. + After a depletion step, both material volume and density change, due to decay, transmutation reactions and transfer rates, if set. At present we lack an implementation to calculate density and volume - changes due to the different molecules speciation. Therefore, the assumption - is to consider the density constant and let the material volume - vary with the change in nuclides concentrations. - The method uses the nuclides concentrations coming from the previous Bateman - solution and calculates a new volume, keeping the mass density of the material - constant. It will then assign the volumes to the AtomNumber instance. + changes due to the different molecules speciation. Therefore, OpenMC + assumes by default that the depletable volume does not change and only + updates the nuclide densities and consequently the total material density. + This method, vice-versa, assumes that the total material density does not + change and updates the materials volume in AtomNumber dataset. + Parameters + ---------- + x : list of numpy.ndarray + Total atom concentrations + """ + number_i = self.operator.number + for mat_idx, mat in enumerate(self.local_mats): + # Total number of atoms-gram per mol + agpm = 0 + for nuc in number_i.nuclides: + agpm += number_i[mat, nuc] * atomic_mass(nuc) + # Get mass dens from beginning, intended to be held constant + density = openmc.lib.materials[int(mat)].get_density('g/cm3') + number_i.volume[mat_idx] = agpm / AVOGADRO / density + + def _update_materials(self, x): + """ + Update number density and material compositions in OpenMC on all processes. + If density_treatment is set to 'constant-density', "_update_volumes" will + update material volumes, keeping the material total density constant, in + AtomNumber to renormalize the atom densities before assign them to the + model in memory. Parameters ---------- x : list of numpy.ndarray @@ -252,36 +306,336 @@ def _update_volumes_after_depletion(self, x): """ self.operator.number.set_density(x) + if self.density_treatment == 'constant-density': + self._update_volumes() + for rank in range(comm.size): number_i = comm.bcast(self.operator.number, root=rank) - for i, mat in enumerate(number_i.materials): - # Total nuclides density - density = 0 + for mat in number_i.materials: + nuclides = [] + densities = [] + for nuc in number_i.nuclides: - # total number of atoms - val = number_i[mat, nuc] - # obtain nuclide density in atoms-g/mol - density += val * atomic_mass(nuc) - # Get mass dens from beginning, intended to be held constant - rho = openmc.lib.materials[int(mat)].get_density('g/cm3') - number_i.volume[i] = density / AVOGADRO / rho + # get atom density in atoms/b-cm + val = 1.0e-24 * number_i.get_atom_density(mat, nuc) + if nuc in self.operator.nuclides_with_data: + if val > 0.0: + nuclides.append(nuc) + densities.append(val) -class BatchwiseCell(Batchwise): + # Update densities on C API side + openmc.lib.materials[int(mat)].set_densities(nuclides, densities) - def __init__(self, operator, model, cell, attrib_name, axis, bracket, - bracket_limit, bracketed_method='brentq', tol=0.01, target=1.0, - print_iterations=True, search_for_keff_output=True): + def _update_x_and_set_volumes(self, x, volumes): + """ + Update x with volumes, before assign them to AtomNumber materials. + Parameters + ---------- + x : list of numpy.ndarray + Total atoms concentrations + volumes : dict - super().__init__(operator, model, bracket, bracket_limit, + Returns + ------- + x : list of numpy.ndarray + Updated total atoms concentrations + """ + number_i = self.operator.number + + for mat_idx, mat in enumerate(self.local_mats): + + if mat in volumes: + res_vol = volumes[mat] + # Normalize burbable nuclides in x vector without cross section data + for nuc_idx, nuc in enumerate(number_i.burnable_nuclides): + if nuc not in self.operator.nuclides_with_data: + # normalzie with new volume + x[mat_idx][nuc_idx] *= number_i.get_mat_volume(mat) / \ + res_vol + + # update all concentration data with the new updated volumes + for nuc, dens in zip(openmc.lib.materials[int(mat)].nuclides, + openmc.lib.materials[int(mat)].densities): + nuc_idx = number_i.index_nuc[nuc] + n_of_atoms = dens / 1.0e-24 * res_vol + + if nuc in number_i.burnable_nuclides: + # convert [#atoms/b-cm] into [#atoms] + x[mat_idx][nuc_idx] = n_of_atoms + # when the nuclide is not in depletion chain update the AtomNumber + else: + #Atom density needs to be in [#atoms/cm3] + number_i[mat, nuc] = n_of_atoms + + #Now we can update the new volume in AtomNumber + number_i.volume[mat_idx] = res_vol + return x + +class BatchwiseCell(Batchwise): + """Abstract class holding batchwise cell-based functions. + + Specific classes for running batchwise depletion calculations are + implemented as derived class of BatchwiseCell. + + .. versionadded:: 0.13.4 + + Parameters + ---------- + cell : openmc.Cell or int or str + OpenMC Cell identifier to where apply batchwise scheme + operator : openmc.deplete.Operator + OpenMC operator object + model : openmc.model.Model + OpenMC model object + bracket : list of float + Bracketing interval to search for the solution, always relative to the + solution at previous step. + bracket_limit : list of float + Absolute bracketing interval lower and upper; if during the adaptive + algorithm the search_for_keff solution lies off these limits the closest + limit will be set as new result. + density_treatment : str + Wether ot not to keep contant volume or density after a depletion step + before the next one. + Default to 'constant-volume' + bracketed_method : {'brentq', 'brenth', 'ridder', 'bisect'}, optional + Solution method to use. + This is equivalent to the `bracket_method` parameter of the + `search_for_keff`. + Defaults to 'brentq'. + tol : float + Tolerance for search_for_keff method. + This is equivalent to the `tol` parameter of the `search_for_keff`. + Default to 0.01 + target : Real, optional + This is equivalent to the `target` parameter of the `search_for_keff`. + Default to 1.0. + print_iterations : Bool, Optional + Wheter or not to print `search_for_keff` iterations. + Default to True + search_for_keff_output : Bool, Optional + Wheter or not to print transport iterations during `search_for_keff`. + Default to False + Attributes + ---------- + cell : openmc.Cell or int or str + OpenMC Cell identifier to where apply batchwise scheme + cell_materials : list of openmc.Material + Depletable materials that fill the Cell Universe. Only valid for + translation or rotation atttributes + """ + def __init__(self, cell, operator, model, bracket, bracket_limit, + density_treatment='constant-volume', bracketed_method='brentq', + tol=0.01, target=1.0, print_iterations=True, + search_for_keff_output=True): + + super().__init__(operator, model, bracket, bracket_limit, density_treatment, bracketed_method, tol, target, print_iterations, search_for_keff_output) - self.cell_id = super()._get_cell_id(cell_id_or_name) + self.cell = self._get_cell(cell) + + # list of material fill the attribute cell, if depletables + self.cell_materials = None + + @abstractmethod + def _get_cell_attrib(self): + """ + Get cell attribute coefficient. + Returns + ------- + coeff : float + cell coefficient + """ + + @abstractmethod + def _set_cell_attrib(self, val): + """ + Set cell attribute to the cell instance. + Parameters + ---------- + val : float + cell coefficient to set + """ + + def _get_cell(self, val): + """Helper method for getting cell from cell instance or cell name or id. + Parameters + ---------- + val : Openmc.Cell or str or int representing Cell + Returns + ------- + val : openmc.Cell + Openmc Cell + """ + if isinstance(val, Cell): + check_value('Cell exists', val, [cell for cell in \ + self.geometry.get_all_cells().values()]) + + elif isinstance(val, str): + if val.isnumeric(): + check_value('Cell id exists', int(val), [cell.id for cell in \ + self.geometry.get_all_cells().values()]) + val = [cell for cell in \ + self.geometry.get_all_cells().values() \ + if cell.id == int(val)][0] + else: + check_value('Cell name exists', val, [cell.name for cell in \ + self.geometry.get_all_cells().values()]) + + val = [cell for cell in \ + self.geometry.get_all_cells().values() \ + if cell.name == val][0] + + elif isinstance(val, int): + check_value('Cell id exists', val, [cell.id for cell in \ + self.geometry.get_all_cells().values()]) + val = [cell for cell in \ + self.geometry.get_all_cells().values() \ + if cell.id == val][0] + else: + ValueError(f'Cell: {val} is not supported') + + return val + + def _model_builder(self, param): + """ + Builds the parametric model that is passed to the `search_for_keff` + function by setting the parametric variable to the cell. + Parameters + ---------- + param : model parametricl variable + for examlple: cell translation coefficient + Returns + ------- + self.model : openmc.model.Model + OpenMC parametric model + """ + self._set_cell_attrib(param) + # At this stage it not important to reassign the new volume, as the + # nuclide densities remain constant. However, if at least one of the cell + # materials is set as depletable, the volume change needs to be accounted for + # as an increase or reduction of number of atoms, i.e. vector x, before + # solving the depletion equations + return self.model + + def search_for_keff(self, x, step_index): + """ + Perform the criticality search parametrizing the cell coefficient. + The `search_for_keff` solution is then set as the new cell attribute + Parameters + ---------- + x : list of numpy.ndarray + Total atoms concentrations + Returns + ------- + x : list of numpy.ndarray + Updated total atoms concentrations + """ + # Get cell attribute from previous iteration + val = self._get_cell_attrib() + check_type('Cell coeff', val, Real) + + # Update all material densities from concentration vectors + #before performing the search_for_keff. This is needed before running + # the transport equations in the search_for_keff algorithm + super()._update_materials(x) + + # Calculate new cell attribute + root = super()._search_for_keff(val) + + # set results value as attribute in the geometry + self._set_cell_attrib(root) + + # if at least one of the cell materials is depletable, calculate new + # volume and update x and number accordingly + # new volume + if self.cell_materials: + volumes = self._calculate_volumes() + x = super()._update_x_and_set_volumes(x, volumes) + + return x, root + + +class BatchwiseCellGeometrical(BatchwiseCell): + """ + Batchwise cell-based with geometrical-attribute class. + + A user doesn't need to call this class directly. + Instead an instance of this class is automatically created by calling + :meth:`openmc.deplete.Integrator.add_batchwise` method from an integrator + class, such as :class:`openmc.deplete.CECMIntegrator`. + + .. versionadded:: 0.13.4 + + Parameters + ---------- + cell : openmc.Cell or int or str + OpenMC Cell identifier to where apply batchwise scheme + attrib_name : str {'translation', 'rotation'} + Cell attribute type + operator : openmc.deplete.Operator + OpenMC operator object + model : openmc.model.Model + OpenMC model object + axis : int {0,1,2} + Directional axis for geometrical parametrization, where 0, 1 and 2 stand + for 'x', 'y' and 'z', respectively. + bracket : list of float + Bracketing interval to search for the solution, always relative to the + solution at previous step. + bracket_limit : list of float + Absolute bracketing interval lower and upper; if during the adaptive + algorithm the search_for_keff solution lies off these limits the closest + limit will be set as new result. + density_treatment : str + Wether ot not to keep contant volume or density after a depletion step + before the next one. + Default to 'constant-volume' + bracketed_method : {'brentq', 'brenth', 'ridder', 'bisect'}, optional + Solution method to use. + This is equivalent to the `bracket_method` parameter of the + `search_for_keff`. + Defaults to 'brentq'. + tol : float + Tolerance for search_for_keff method. + This is equivalent to the `tol` parameter of the `search_for_keff`. + Default to 0.01 + target : Real, optional + This is equivalent to the `target` parameter of the `search_for_keff`. + Default to 1.0. + print_iterations : Bool, Optional + Wheter or not to print `search_for_keff` iterations. + Default to True + search_for_keff_output : Bool, Optional + Wheter or not to print transport iterations during `search_for_keff`. + Default to False + Attributes + ---------- + cell : openmc.Cell or int or str + OpenMC Cell identifier to where apply batchwise scheme + attrib_name : str {'translation', 'rotation'} + Cell attribute type + axis : int {0,1,2} + Directional axis for geometrical parametrization, where 0, 1 and 2 stand + for 'x', 'y' and 'z', respectively. + """ + def __init__(self, cell, attrib_name, operator, model, axis, bracket, + bracket_limit, density_treatment='constant-volume', + bracketed_method='brentq', tol=0.01, target=1.0, + print_iterations=True, search_for_keff_output=True): + + super().__init__(cell, operator, model, bracket, bracket_limit, + density_treatment, bracketed_method, tol, target, + print_iterations, search_for_keff_output) + check_value('attrib_name', attrib_name, ('rotation', 'translation')) self.attrib_name = attrib_name + # check if cell is filled with 2 cells + check_length('fill materials', self.cell.fill.cells, 2) #index of cell directionnal axis check_value('axis', axis, (0,1,2)) self.axis = axis @@ -289,10 +643,15 @@ def __init__(self, operator, model, cell, attrib_name, axis, bracket, # Initialize vector self.vector = np.zeros(3) - # materials that fill the attribute cell, if depletables - self.cell_material_ids = [cell.fill.id for cell in \ - self.geometry.get_all_cells()[self.cell_id].fill.cells.values() \ - if cell.fill.depletable] + self.cell_materials = [cell.fill for cell in \ + self.cell.fill.cells.values() if cell.fill.depletable] + + if self.cell_materials: + self._initialize_volume_calc() + + @classmethod + def from_params(cls, obj, attr, operator, model, **kwargs): + return cls(obj, attr, operator, model, **kwargs) def _get_cell_attrib(self): """ @@ -303,7 +662,7 @@ def _get_cell_attrib(self): cell coefficient """ for cell in openmc.lib.cells.values(): - if cell.id == self.cell_id: + if cell.id == self.cell.id: if self.attrib_name == 'translation': return cell.translation[self.axis] elif self.attrib_name == 'rotation': @@ -315,92 +674,279 @@ def _set_cell_attrib(self, val): Attributes are only applied to cells filled with a universe Parameters ---------- - var : float - Surface coefficient to set - geometry : openmc.model.geometry - OpenMC geometry model - attrib_name : str - Currently only translation is implemented + val : float + Cell coefficient to set in cm for translation and def for rotation """ self.vector[self.axis] = val for cell in openmc.lib.cells.values(): - if cell.id == self.cell_id or cell.name == self.cell_id: + if cell.id == self.cell.id: setattr(cell, self.attrib_name, self.vector) - def _update_materials(self, x): + def _initialize_volume_calc(self): + """ + Set volume calculation model settings of depletable materials filling + the parametric Cell. + """ + ll, ur = self.geometry.bounding_box + mat_vol = openmc.VolumeCalculation(self.cell_materials, 100000, ll, ur) + self.model.settings.volume_calculations = mat_vol + + def _calculate_volumes(self): + """ + Perform stochastic volume calculation + Returns + ------- + volumes : dict + Dictionary of calculate volumes, where key is the mat id """ - Assign concentration vectors from Bateman solution at previous - timestep to the in-memory model materials, after having recalculated the - material volume. + openmc.lib.calculate_volumes() + volumes = {} + res = openmc.VolumeCalculation.from_hdf5('volume_1.h5') + for mat_idx, mat in enumerate(self.local_mats): + if int(mat) in [mat.id for mat in self.cell_materials]: + volumes[mat] = res.volumes[int(mat)].n + return volumes + +class BatchwiseCellTemperature(BatchwiseCell): + """ + Batchwise cell-based with temperature-attribute class. + + A user doesn't need to call this class directly. + Instead an instance of this class is automatically created by calling + :meth:`openmc.deplete.Integrator.add_batchwise` method from an integrator + class, such as :class:`openmc.deplete.CECMIntegrator`. + + .. versionadded:: 0.13.4 + + Parameters + ---------- + cell : openmc.Cell or int or str + OpenMC Cell identifier to where apply batchwise scheme + operator : openmc.deplete.Operator + OpenMC operator object + model : openmc.model.Model + OpenMC model object + bracket : list of float + Bracketing interval to search for the solution, always relative to the + solution at previous step. + bracket_limit : list of float + Absolute bracketing interval lower and upper; if during the adaptive + algorithm the search_for_keff solution lies off these limits the closest + limit will be set as new result. + density_treatment : str + Wether ot not to keep contant volume or density after a depletion step + before the next one. + Default to 'constant-volume' + bracketed_method : {'brentq', 'brenth', 'ridder', 'bisect'}, optional + Solution method to use. + This is equivalent to the `bracket_method` parameter of the + `search_for_keff`. + Defaults to 'brentq'. + tol : float + Tolerance for search_for_keff method. + This is equivalent to the `tol` parameter of the `search_for_keff`. + Default to 0.01 + target : Real, optional + This is equivalent to the `target` parameter of the `search_for_keff`. + Default to 1.0. + print_iterations : Bool, Optional + Wheter or not to print `search_for_keff` iterations. + Default to True + search_for_keff_output : Bool, Optional + Wheter or not to print transport iterations during `search_for_keff`. + Default to False + Attributes + ---------- + cell : openmc.Cell or int or str + OpenMC Cell identifier to where apply batchwise scheme + """ + def __init__(self, cell, operator, model, bracket, bracket_limit, + density_treatment='constant-volume', bracketed_method='brentq', + tol=0.01, target=1.0, print_iterations=True, + search_for_keff_output=True): + + super().__init__(cell, operator, model, bracket, bracket_limit, + density_treatment, bracketed_method, tol, target, + print_iterations, search_for_keff_output) + + # check if initial temperature has been set to right cell material + if isinstance(self.cell.fill, openmc.Universe): + cells = [cell for cell in self.cell.fill.cells.values() \ + if cell.fill.temperature] + check_length('Only one cell with temperature',cells,1) + self.cell = cells[0] + + check_type('temperature cell real', self.cell.fill.temperature, Real) + + @classmethod + def from_params(cls, obj, attr, operator, model, **kwargs): + return cls(obj, operator, model, **kwargs) + def _get_cell_attrib(self): + """ + Get cell attribute coefficient. + Returns + ------- + coeff : float + cell temperature in Kelvin + """ + for cell in openmc.lib.cells.values(): + if cell.id == self.cell.id: + return cell.get_temperature() + + def _set_cell_attrib(self, val): + """ + Set cell attribute to the cell instance. Parameters ---------- - x : list of numpy.ndarray - Total atom concentrations + val : float + Cell temperature to set in Kelvin """ - super()._update_volumes_after_depletion(x) + for cell in openmc.lib.cells.values(): + if cell.id == self.cell.id: + cell.set_temperature(val) + +class BatchwiseMaterial(Batchwise): + """Abstract class holding batchwise material-based functions. + + Specific classes for running batchwise depletion calculations are + implemented as derived class of BatchwiseMaterial. + + .. versionadded:: 0.13.4 + + Parameters + ---------- + material : openmc.Material or int or str + OpenMC Material identifier to where apply batchwise scheme + operator : openmc.deplete.Operator + OpenMC operator object + model : openmc.model.Model + OpenMC model object + mat_vector : dict + Dictionary of material composition to paremetrize, where a pair key, value + represents a nuclide and its weight fraction. + bracket : list of float + Bracketing interval to search for the solution, always relative to the + solution at previous step. + bracket_limit : list of float + Absolute bracketing interval lower and upper; if during the adaptive + algorithm the search_for_keff solution lies off these limits the closest + limit will be set as new result. + density_treatment : str + Wether ot not to keep contant volume or density after a depletion step + before the next one. + Default to 'constant-volume' + bracketed_method : {'brentq', 'brenth', 'ridder', 'bisect'}, optional + Solution method to use. + This is equivalent to the `bracket_method` parameter of the + `search_for_keff`. + Defaults to 'brentq'. + tol : float + Tolerance for search_for_keff method. + This is equivalent to the `tol` parameter of the `search_for_keff`. + Default to 0.01 + target : Real, optional + This is equivalent to the `target` parameter of the `search_for_keff`. + Default to 1.0. + print_iterations : Bool, Optional + Wheter or not to print `search_for_keff` iterations. + Default to True + search_for_keff_output : Bool, Optional + Wheter or not to print transport iterations during `search_for_keff`. + Default to False + Attributes + ---------- + material : openmc.Material or int or str + OpenMC Material identifier to where apply batchwise scheme + mat_vector : dict + Dictionary of material composition to paremetrize, where a pair key, value + represents a nuclide and its weight fraction. + """ + + def __init__(self, material, operator, model, mat_vector, bracket, + bracket_limit, density_treatment='constant-volume', + bracketed_method='brentq', tol=0.01, target=1.0, + print_iterations=True, search_for_keff_output=True): - for rank in range(comm.size): - number_i = comm.bcast(self.operator.number, root=rank) + super().__init__(operator, model, bracket, bracket_limit, + density_treatment, bracketed_method, tol, target, + print_iterations, search_for_keff_output) - for mat in number_i.materials: - nuclides = [] - densities = [] + self.material = self._get_material(material) - for nuc in number_i.nuclides: - # get atom density in atoms/b-cm - val = 1.0e-24 * number_i.get_atom_density(mat, nuc) - if nuc in self.operator.nuclides_with_data: - if val > self.atom_density_limit: - nuclides.append(nuc) - densities.append(val) + check_type("material vector", mat_vector, dict, str) + for nuc in mat_vector.keys(): + check_value("check nuclide exists", nuc, self.operator.nuclides_with_data) - #set nuclide densities to model in memory (C-API) - openmc.lib.materials[int(mat)].set_densities(nuclides, densities) + if round(sum(mat_vector.values()), 2) != 1.0: + # Normalize material elements vector + sum_values = sum(mat_vector.values()) + for elm in mat_vector: + mat_vector[elm] /= sum_values + self.mat_vector = mat_vector - def _update_volumes(self): - openmc.lib.calculate_volumes() - res = openmc.VolumeCalculation.from_hdf5('volume_1.h5') + @classmethod + def from_params(cls, obj, attr, operator, model, **kwargs): + return cls(obj, operator, model, **kwargs) - number_i = self.operator.number - for mat_idx, mat_id in enumerate(self.local_mats): - if mat_id in self.cell_material_ids: - number_i.volume[mat_idx] = res.volumes[int(mat_id)].n + def _get_material(self, val): + """Helper method for getting openmc material from Material instance or + material name or id. + Parameters + ---------- + val : Openmc.Material or str or int representing material name/id + Returns + ------- + val : openmc.Material + Openmc Material + """ + if isinstance(val, Material): + check_value('Material', str(val.id), self.burn_mats) - def _update_x(self): - number_i = self.operator.number - for mat_idx, mat_id in enumerate(self.local_mats): - if mat_id in self.cell_material_ids: - for nuc_idx, nuc in enumerate(number_i.burnable_nuclides): - x[mat_idx][nuc_idx] = number_i.volume[mat_idx] * \ - number_i.get_atom_density(mat_idx, nuc) - return x + elif isinstance(val, str): + if val.isnumeric(): + check_value('Material id', val, self.burn_mats) + val = [mat for mat in self.model.materials \ + if mat.id == int(val)][0] + + else: + check_value('Material name', val, + [mat.name for mat in self.model.materials if mat.depletable]) + val = [mat for mat in self.model.materials \ + if mat.name == val][0] + + elif isinstance(val, int): + check_value('Material id', str(val), self.burn_mats) + val = [mat for mat in self.model.materials \ + if mat.id == val][0] + else: + ValueError(f'Material: {val} is not supported') + + return val + + @abstractmethod def _model_builder(self, param): """ Builds the parametric model that is passed to the `msr_search_for_keff` - function by setting the parametric variable to the geoemetrical cell. + function by updating the material densities and setting the parametric + variable as function of the nuclides vector. Since this is a paramteric + material addition (or removal), we can parametrize the volume as well. Parameters ---------- - param : model parametricl variable - for examlple: cell translation coefficient + param : + Model material function variable Returns ------- _model : openmc.model.Model - OpenMC parametric model + Openmc parametric model """ - self._set_cell_attrib(param) - #Calulate new volume and update if materials filling the cell are - # depletable - if self.cell_material_ids: - self._update_volumes() - return self.model def search_for_keff(self, x, step_index): """ - Perform the criticality search on the parametric geometrical model. - Will set the root of the `search_for_keff` function to the cell - attribute. + Perform the criticality search on the parametric material model. + Will set the root of the `search_for_keff` function to the atoms + concentrations vector. Parameters ---------- x : list of numpy.ndarray @@ -410,26 +956,168 @@ def search_for_keff(self, x, step_index): x : list of numpy.ndarray Updated total atoms concentrations """ - # Get cell attribute from previous iteration - val = self._get_cell_attrib() - check_type('Cell coeff', val, Real) + # Update AtomNumber with new conc vectors x. Materials are also updated + # even though they are also re-calculated when running the search_for_kef + super()._update_materials(x) + + # Solve search_for_keff and find new value + root = super()._search_for_keff(0) + + #Update concentration vector and volumes with new value + volumes = self._calculate_volumes(root) + x = super()._update_x_and_set_volumes(x, volumes) + + return x, root + +class BatchwiseMaterialRefuel(BatchwiseMaterial): + """ + Batchwise material-based class for refueling (addition or removal) scheme. + + A user doesn't need to call this class directly. + Instead an instance of this class is automatically created by calling + :meth:`openmc.deplete.Integrator.add_batchwise` method from an integrator + class, such as :class:`openmc.deplete.CECMIntegrator`. + + .. versionadded:: 0.13.4 + + Parameters + ---------- + material : openmc.Material or int or str + OpenMC Material identifier to where apply batchwise scheme + operator : openmc.deplete.Operator + OpenMC operator object + model : openmc.model.Model + OpenMC model object + mat_vector : dict + Dictionary of material composition to paremetrize, where a pair key, value + represents a nuclide and its weight fraction. + bracket : list of float + Bracketing interval to search for the solution, always relative to the + solution at previous step. + bracket_limit : list of float + Absolute bracketing interval lower and upper; if during the adaptive + algorithm the search_for_keff solution lies off these limits the closest + limit will be set as new result. + density_treatment : str + Wether ot not to keep contant volume or density after a depletion step + before the next one. + Default to 'constant-volume' + bracketed_method : {'brentq', 'brenth', 'ridder', 'bisect'}, optional + Solution method to use. + This is equivalent to the `bracket_method` parameter of the + `search_for_keff`. + Defaults to 'brentq'. + tol : float + Tolerance for search_for_keff method. + This is equivalent to the `tol` parameter of the `search_for_keff`. + Default to 0.01 + target : Real, optional + This is equivalent to the `target` parameter of the `search_for_keff`. + Default to 1.0. + print_iterations : Bool, Optional + Wheter or not to print `search_for_keff` iterations. + Default to True + search_for_keff_output : Bool, Optional + Wheter or not to print transport iterations during `search_for_keff`. + Default to False + Attributes + ---------- + material : openmc.Material or int or str + OpenMC Material identifier to where apply batchwise scheme + mat_vector : dict + Dictionary of material composition to paremetrize, where a pair key, value + represents a nuclide and its weight fraction. + """ + + def __init__(self, material, operator, model, mat_vector, bracket, + bracket_limit, density_treatment='constant-volume', + bracketed_method='brentq', tol=0.01, target=1.0, + print_iterations=True, search_for_keff_output=True): - # Update volume and concentration vectors before performing the search_for_keff - self._update_materials(x) + super().__init__(material, operator, model, mat_vector, bracket, + bracket_limit, density_treatment, bracketed_method, tol, + target, print_iterations, search_for_keff_output) - # Calculate new cell attribute - root = super().search_for_keff(val) + @classmethod + def from_params(cls, obj, attr, operator, model, **kwargs): + return cls(obj, operator, model, **kwargs) - # set results value as attribute in the geometry - self._set_cell_attrib(root) - print('UPDATE: old value: {:.2f} cm --> ' \ - 'new value: {:.2f} cm'.format(val, root)) + def _model_builder(self, param): + """ + Builds the parametric model that is passed to the `msr_search_for_keff` + function by updating the material densities and setting the parametric + variable to the material nuclides to add. We can either fix the total + volume or the material density according to user input. + Parameters + ---------- + param : + Model function variable, total grams of material to add or remove + Returns + ------- + model : openmc.model.Model + OpenMC parametric model + """ + for rank in range(comm.size): + number_i = comm.bcast(self.operator.number, root=rank) - # x needs to be updated after the search with new volumes if materials cell - # depletable - if self.cell_material_ids: - self._update_x() + for mat in number_i.materials: + nuclides = [] + densities = [] - #Store results - super()._save_res('geometry', step_index, root) - return x + if int(mat) == self.material.id: + + if self.density_treatment == 'constant-density': + vol = number_i.get_mat_volume(mat) + \ + (param / self.material.get_mass_density()) + + elif self.density_treatment == 'constant-volume': + vol = number_i.get_mat_volume(mat) + + for nuc in number_i.index_nuc: + # check only nuclides with cross sections data + if nuc in self.operator.nuclides_with_data: + if nuc in self.mat_vector: + # units [#atoms/cm-b] + val = 1.0e-24 * (number_i.get_atom_density(mat, + nuc) + param / atomic_mass(nuc) * \ + AVOGADRO * self.mat_vector[nuc] / vol) + + else: + # get normalized atoms density in [atoms/b-cm] + val = 1.0e-24 * number_i[mat, nuc] / vol + + if val > 0.0: + nuclides.append(nuc) + densities.append(val) + + else: + # for all other materials, still check atom density limits + for nuc in number_i.nuclides: + if nuc in self.operator.nuclides_with_data: + # get normalized atoms density in [atoms/b-cm] + val = 1.0e-24 * number_i.get_atom_density(mat, nuc) + + if val > 0.0: + nuclides.append(nuc) + densities.append(val) + + #set nuclides and densities to the in-memory model + openmc.lib.materials[int(mat)].set_densities(nuclides, densities) + + # alwyas need to return a model + return self.model + + def _calculate_volumes(self, res): + """ + """ + number_i = self.operator.number + volumes = {} + + for mat in self.local_mats: + if int(mat) == self.material.id: + if self.density_treatment == 'constant-density': + volumes[mat] = number_i.get_mat_volume(mat) + \ + (res / self.material.get_mass_density()) + elif self.density_treatment == 'constant-volume': + volumes[mat] = number_i.get_mat_volume(mat) + return volumes diff --git a/openmc/deplete/stepresult.py b/openmc/deplete/stepresult.py index 372f7cf25de..3aa8b45dc08 100644 --- a/openmc/deplete/stepresult.py +++ b/openmc/deplete/stepresult.py @@ -74,7 +74,7 @@ def __init__(self): self.mat_to_hdf5_ind = None self.data = None - + self.batchwise = None def __repr__(self): t = self.time[0] dt = self.time[1] - self.time[0] @@ -349,6 +349,10 @@ def _write_hdf5_metadata(self, handle): "depletion time", (1,), maxshape=(None,), dtype="float64") + handle.create_dataset( + "batchwise_root", (1,), maxshape=(None,), + dtype="float64") + def _to_hdf5(self, handle, index, parallel=False): """Converts results object into an hdf5 object. @@ -379,6 +383,7 @@ def _to_hdf5(self, handle, index, parallel=False): time_dset = handle["/time"] source_rate_dset = handle["/source_rate"] proc_time_dset = handle["/depletion time"] + root_dset = handle["/batchwise_root"] # Get number of results stored number_shape = list(number_dset.shape) @@ -412,6 +417,10 @@ def _to_hdf5(self, handle, index, parallel=False): proc_shape[0] = new_shape proc_time_dset.resize(proc_shape) + root_shape = list(root_dset.shape) + root_shape[0] = new_shape + root_dset.resize(root_shape) + # If nothing to write, just return if len(self.index_mat) == 0: return @@ -435,6 +444,7 @@ def _to_hdf5(self, handle, index, parallel=False): proc_time_dset[index] = ( self.proc_time / (comm.size * self.n_hdf5_mats) ) + root_dset[index] = self.batchwise @classmethod def from_hdf5(cls, handle, step): @@ -458,11 +468,13 @@ def from_hdf5(cls, handle, step): else: # Older versions used "power" instead of "source_rate" source_rate_dset = handle["/power"] + root_dset = handle["/batchwise_root"] results.data = number_dset[step, :, :, :] results.k = eigenvalues_dset[step, :] results.time = time_dset[step, :] results.source_rate = source_rate_dset[step, 0] + results.batchwise = root_dset[step] if "depletion time" in handle: proc_time_dset = handle["/depletion time"] @@ -509,7 +521,7 @@ def from_hdf5(cls, handle, step): @staticmethod def save(op, x, op_results, t, source_rate, step_ind, proc_time=None, - path: PathLike = "depletion_results.h5"): + root=None, path: PathLike = "depletion_results.h5"): """Creates and writes depletion results to disk Parameters @@ -564,6 +576,7 @@ def save(op, x, op_results, t, source_rate, step_ind, proc_time=None, results.proc_time = proc_time if results.proc_time is not None: results.proc_time = comm.reduce(proc_time, op=MPI.SUM) + results.batchwise = root if not Path(path).is_file(): Path(path).parent.mkdir(parents=True, exist_ok=True) diff --git a/openmc/search.py b/openmc/search.py index 7f6b3b5b9a4..4818fbe10bd 100644 --- a/openmc/search.py +++ b/openmc/search.py @@ -12,7 +12,7 @@ def _search_keff(guess, target, model_builder, model_args, print_iterations, - run_args, guesses, results): + run_args, guesses, results, run_in_memory): """Function which will actually create our model, run the calculation, and obtain the result. This function will be passed to the root finding algorithm @@ -51,7 +51,11 @@ def _search_keff(guess, target, model_builder, model_args, print_iterations, model = model_builder(guess, **model_args) # Run the model and obtain keff - sp_filepath = model.run(**run_args) + if run_in_memory: + openmc.lib.run(**run_args) + sp_filepath = f'statepoint.{model.settings.batches}.h5' + else: + sp_filepath = model.run(**run_args) with openmc.StatePoint(sp_filepath) as sp: keff = sp.keff @@ -70,7 +74,7 @@ def _search_keff(guess, target, model_builder, model_args, print_iterations, def search_for_keff(model_builder, initial_guess=None, target=1.0, bracket=None, model_args=None, tol=None, bracketed_method='bisect', print_iterations=False, - run_args=None, **kwargs): + run_args=None, run_in_memory=False, **kwargs): """Function to perform a keff search by modifying a model parametrized by a single independent variable. @@ -193,12 +197,26 @@ def search_for_keff(model_builder, initial_guess=None, target=1.0, # Add information to be passed to the searching function args['args'] = (target, model_builder, model_args, print_iterations, - run_args, guesses, results) + run_args, guesses, results, run_in_memory) # Create a new dictionary with the arguments from args and kwargs args.update(kwargs) # Perform the search - zero_value = root_finder(**args) + if not run_in_memory: + zero_value = root_finder(**args) + return zero_value - return zero_value, guesses, results + else: + try: + zero_value, root_res = root_finder(**args, full_output=True, disp=False) + if root_res.converged: + return zero_value, guesses, results + + else: + print(f'WARNING: {root_res.flag}') + return guesses, results + # In case the root finder is not successful + except Exception as e: + print(f'WARNING: {e}') + return guesses, results diff --git a/tests/unit_tests/test_deplete_batchwise.py b/tests/unit_tests/test_deplete_batchwise.py new file mode 100644 index 00000000000..7c05786fa5f --- /dev/null +++ b/tests/unit_tests/test_deplete_batchwise.py @@ -0,0 +1,135 @@ +""" Tests for Batchwise class """ + +from pathlib import Path +from math import exp + +import pytest +import numpy as np + +import openmc +import openmc.lib +from openmc.deplete import CoupledOperator +from openmc.deplete import (BatchwiseCellGeometrical, BatchwiseCellTemperature, + BatchwiseMaterialRefuel) + +CHAIN_PATH = Path(__file__).parents[1] / "chain_simple.xml" + +@pytest.fixture +def model(): + f = openmc.Material(name="f") + f.add_element("U", 1, percent_type="ao", enrichment=4.25) + f.add_element("O", 2) + f.set_density("g/cc", 10.4) + f.temperature = 293.15 + w = openmc.Material(name="w") + w.add_element("O", 1) + w.add_element("H", 2) + w.set_density("g/cc", 1.0) + w.depletable = True + h = openmc.Material(name='h') + h.set_density('g/cm3', 0.001598) + h.add_element('He', 2.4044e-4) + radii = [0.42, 0.45] + f.volume = np.pi * radii[0] ** 2 + w.volume = np.pi * (radii[1]**2 - radii[0]**2) + materials = openmc.Materials([f, w, h]) + surf_wh = openmc.ZPlane(z0=0) + surf_f = openmc.Sphere(r=radii[0]) + surf_w = openmc.Sphere(r=radii[1], boundary_type='vacuum') + cell_w = openmc.Cell(fill=w, region=-surf_wh) + cell_h = openmc.Cell(fill=h, region=+surf_wh) + uni_wh = openmc.Universe(cells=(cell_w, cell_h)) + cell_f = openmc.Cell(name='f',fill=f, region=-surf_f) + cell_wh = openmc.Cell(name='wh',fill=uni_wh, region=+surf_f & -surf_w) + geometry = openmc.Geometry([cell_f, cell_wh]) + settings = openmc.Settings() + settings.particles = 1000 + settings.inactive = 10 + settings.batches = 50 + return openmc.Model(geometry, materials, settings) + +@pytest.mark.parametrize("object_name, attribute", [ + ('wh', 'translation'), + ('wh', 'rotation'), + ('f', 'temperature'), + ('f', 'refuel'), + ]) +def test_attributes(model, object_name, attribute): + """ + Test classes attributes are set correctly + """ + op = CoupledOperator(model, CHAIN_PATH) + + bracket = [-1,1] + bracket_limit = [-10,10] + axis = 2 + mat_vector = {'U235':0.1, 'U238':0.9} + if attribute in ('translation','rotation'): + bw = BatchwiseCellGeometrical(object_name, attribute, op, model, axis, bracket, bracket_limit) + assert bw.cell_materials == [cell.fill for cell in \ + model.geometry.get_cells_by_name(object_name)[0].fill.cells.values() \ + if cell.fill.depletable] + assert bw.attrib_name == attribute + assert bw.axis == axis + + elif attribute == 'temperature': + bw = BatchwiseCellTemperature(object_name, op, model, bracket, bracket_limit) + elif attribute == 'refuel': + bw = BatchwiseMaterialRefuel(object_name, op, model, mat_vector, bracket, bracket_limit) + assert bw.mat_vector == mat_vector + + assert bw.bracket == bracket + assert bw.bracket_limit == bracket_limit + assert bw.burn_mats == op.burnable_mats + assert bw.local_mats == op.local_mats + +@pytest.mark.parametrize("attribute", [ + ('translation'), + ('rotation') + ]) +def test_cell_methods(run_in_tmpdir, model, attribute): + """ + Test cell base class internal method + """ + bracket = [-1,1] + bracket_limit = [-10,10] + axis = 2 + op = CoupledOperator(model, CHAIN_PATH) + integrator = openmc.deplete.PredictorIntegrator( + op, [1,1], 0.0, timestep_units = 'd') + integrator.add_batchwise('wh', attribute, axis=axis, bracket=bracket, + bracket_limit=bracket_limit) + + model.export_to_xml() + openmc.lib.init() + integrator.batchwise._set_cell_attrib(0) + assert integrator.batchwise._get_cell_attrib() == 0 + vol = integrator.batchwise._calculate_volumes() + for cell in integrator.batchwise.cell_materials: + assert vol[str(cell.id)] == pytest.approx([mat.volume for mat in model.materials if mat.id == cell.id]) + openmc.lib.finalize() + +def test_update_materials_methods(run_in_tmpdir): + """ + It's the same methods for all classe so one class is enough + """ + bracket = [-1,1] + bracket_limit = [-10,10] + axis = 2 + op = CoupledOperator(model, CHAIN_PATH) + integrator = openmc.deplete.PredictorIntegrator( + op, [1,1], 0.0, timestep_units = 'd') + integrator.add_batchwise('wh', 'translation', axis=axis, bracket=bracket, + bracket_limit=bracket_limit) + + model.export_to_xml() + openmc.lib.init() + x = op.number.number + #Double number of atoms of U238 in fuel + mat_index = op.number.index_mat['1'] + nuc_index = op.number.index_nuc['U238'] + vol = op.number.get_mat_volume('1') + op.number.number[mat_index][nuc_idx] *= 2 + integrator.batchwise._update_volumes(op.number.number) + + vol = integrator.batchwise._calculate_volumes() From 7138d2d5b64767c25e969624fd15e8310823c299 Mon Sep 17 00:00:00 2001 From: church89 Date: Tue, 5 Sep 2023 10:57:06 +0200 Subject: [PATCH 21/59] update unit test --- openmc/deplete/batchwise.py | 2 +- tests/unit_tests/test_deplete_batchwise.py | 87 ++++++++++++++++++++-- 2 files changed, 80 insertions(+), 9 deletions(-) diff --git a/openmc/deplete/batchwise.py b/openmc/deplete/batchwise.py index 7136f7e6b21..b3a2b69f0e4 100644 --- a/openmc/deplete/batchwise.py +++ b/openmc/deplete/batchwise.py @@ -291,7 +291,7 @@ def _update_volumes(self): # Get mass dens from beginning, intended to be held constant density = openmc.lib.materials[int(mat)].get_density('g/cm3') number_i.volume[mat_idx] = agpm / AVOGADRO / density - + def _update_materials(self, x): """ Update number density and material compositions in OpenMC on all processes. diff --git a/tests/unit_tests/test_deplete_batchwise.py b/tests/unit_tests/test_deplete_batchwise.py index 7c05786fa5f..37e14009852 100644 --- a/tests/unit_tests/test_deplete_batchwise.py +++ b/tests/unit_tests/test_deplete_batchwise.py @@ -106,12 +106,13 @@ def test_cell_methods(run_in_tmpdir, model, attribute): assert integrator.batchwise._get_cell_attrib() == 0 vol = integrator.batchwise._calculate_volumes() for cell in integrator.batchwise.cell_materials: - assert vol[str(cell.id)] == pytest.approx([mat.volume for mat in model.materials if mat.id == cell.id]) + assert vol[str(cell.id)] == pytest.approx([mat.volume for mat in model.materials if mat.id == cell.id][0]) openmc.lib.finalize() -def test_update_materials_methods(run_in_tmpdir): +def test_update_volumes(run_in_tmpdir): """ - It's the same methods for all classe so one class is enough + Method to update volume in AtomNumber after depletion step. + Method inheritated by all derived classes so one check is enough. """ bracket = [-1,1] bracket_limit = [-10,10] @@ -124,12 +125,82 @@ def test_update_materials_methods(run_in_tmpdir): model.export_to_xml() openmc.lib.init() - x = op.number.number - #Double number of atoms of U238 in fuel + + #Increase number of atoms of U238 in fuel by fix amount and check the + # volume increase at constant-density mat_index = op.number.index_mat['1'] nuc_index = op.number.index_nuc['U238'] vol = op.number.get_mat_volume('1') - op.number.number[mat_index][nuc_idx] *= 2 - integrator.batchwise._update_volumes(op.number.number) + atoms_to_add = 1e22 + op.number.number[mat_index][nuc_idx] += atoms_to_add + integrator.batchwise._update_volumes() - vol = integrator.batchwise._calculate_volumes() + atoms_to_vol = atoms_to_add * openmc.data.atomic_mass('U238') \ + /openmc.data.AVOGADRO / 10.4 + assert op.number.get_mat_volume('1') == vol + atoms_to_vol + + openmc.lib.finalize() + +# def test_update_materials(run_in_tmpdir): +# """ +# Method to update volume in AtomNumber after depletion step if 'constant-density' +# is passed to the batchwise instance. +# Method inheritated by all derived classes so one check is enough. +# """ +# bracket = [-1,1] +# bracket_limit = [-10,10] +# axis = 2 +# op = CoupledOperator(model, CHAIN_PATH) +# integrator = openmc.deplete.PredictorIntegrator( +# op, [1,1], 0.0, timestep_units = 'd') +# integrator.add_batchwise('wh', 'translation', axis=axis, bracket=bracket, +# bracket_limit=bracket_limit) +# +# model.export_to_xml() +# openmc.lib.init() +# +# #Increase number of atoms of U238 in fuel by fix amount and check that the +# # densities in openmc.lib have beeen updated +# mat_index = op.number.index_mat['1'] +# nuc_index = op.number.index_nuc['U238'] +# vol = op.number.get_mat_volume('1') +# atoms_to_add = 1e20 +# op.number.number[mat_index][nuc_idx] += atoms_to_add +# integrator.batchwise._update_materials(op.number.number) +# +# assert openmc.lib.materials[1][] == op.number.get_atom_density('1','U238') +# +# openmc.lib.finalize() +# +# def test_update_x_and_set_volumes_method(run_in_tmpdir): +# """ +# Method to update volume in AtomNumber after depletion step if 'constant-density' +# is passed to the batchwise instance. +# Method inheritated by all derived classes so one check is enough. +# """ +# bracket = [-1,1] +# bracket_limit = [-10,10] +# axis = 2 +# op = CoupledOperator(model, CHAIN_PATH) +# integrator = openmc.deplete.PredictorIntegrator( +# op, [1,1], 0.0, timestep_units = 'd') +# integrator.add_batchwise('wh', 'translation', axis=axis, bracket=bracket, +# bracket_limit=bracket_limit) +# +# model.export_to_xml() +# openmc.lib.init() +# +# #Increase number of atoms of U238 in fuel by fix amount and check that the +# # densities in openmc.lib have beeen updated +# mat_index = op.number.index_mat['1'] +# nuc_index = op.number.index_nuc['U238'] +# vol = op.number.get_mat_volume('1') +# atoms_to_add = 1e20 +# vol_to_add = 10 +# op.number.number[mat_index][nuc_idx] += atoms_to_add +# op.number.volumes[mat_index] += vol_to_add +# integrator.batchwise._update_x_and_set_volumes(op.number.number, op.number.volumes) +# +# assert +# +# openmc.lib.finalize() From 872fa815fcc9e2217ed703e0a910019e0b0e98f6 Mon Sep 17 00:00:00 2001 From: church89 Date: Tue, 5 Sep 2023 14:04:43 +0200 Subject: [PATCH 22/59] add test unit --- tests/unit_tests/test_deplete_batchwise.py | 74 +++++++++++----------- 1 file changed, 38 insertions(+), 36 deletions(-) diff --git a/tests/unit_tests/test_deplete_batchwise.py b/tests/unit_tests/test_deplete_batchwise.py index 37e14009852..6e3cce7854c 100644 --- a/tests/unit_tests/test_deplete_batchwise.py +++ b/tests/unit_tests/test_deplete_batchwise.py @@ -116,11 +116,10 @@ def test_update_volumes(run_in_tmpdir): """ bracket = [-1,1] bracket_limit = [-10,10] - axis = 2 op = CoupledOperator(model, CHAIN_PATH) integrator = openmc.deplete.PredictorIntegrator( op, [1,1], 0.0, timestep_units = 'd') - integrator.add_batchwise('wh', 'translation', axis=axis, bracket=bracket, + integrator.add_batchwise('f', 'refuel', mat_vector={}, bracket=bracket, bracket_limit=bracket_limit) model.export_to_xml() @@ -128,49 +127,52 @@ def test_update_volumes(run_in_tmpdir): #Increase number of atoms of U238 in fuel by fix amount and check the # volume increase at constant-density - mat_index = op.number.index_mat['1'] + #extract fuel material from model materials + mat = integrator.batchwise.material + mat_index = op.number.index_mat[str(mat.id)] nuc_index = op.number.index_nuc['U238'] - vol = op.number.get_mat_volume('1') + vol = op.number.get_mat_volume(str(mat.id)) atoms_to_add = 1e22 op.number.number[mat_index][nuc_idx] += atoms_to_add integrator.batchwise._update_volumes() - + atoms_to_vol = atoms_to_add * openmc.data.atomic_mass('U238') \ - /openmc.data.AVOGADRO / 10.4 + /openmc.data.AVOGADRO / mat.density assert op.number.get_mat_volume('1') == vol + atoms_to_vol openmc.lib.finalize() -# def test_update_materials(run_in_tmpdir): -# """ -# Method to update volume in AtomNumber after depletion step if 'constant-density' -# is passed to the batchwise instance. -# Method inheritated by all derived classes so one check is enough. -# """ -# bracket = [-1,1] -# bracket_limit = [-10,10] -# axis = 2 -# op = CoupledOperator(model, CHAIN_PATH) -# integrator = openmc.deplete.PredictorIntegrator( -# op, [1,1], 0.0, timestep_units = 'd') -# integrator.add_batchwise('wh', 'translation', axis=axis, bracket=bracket, -# bracket_limit=bracket_limit) -# -# model.export_to_xml() -# openmc.lib.init() -# -# #Increase number of atoms of U238 in fuel by fix amount and check that the -# # densities in openmc.lib have beeen updated -# mat_index = op.number.index_mat['1'] -# nuc_index = op.number.index_nuc['U238'] -# vol = op.number.get_mat_volume('1') -# atoms_to_add = 1e20 -# op.number.number[mat_index][nuc_idx] += atoms_to_add -# integrator.batchwise._update_materials(op.number.number) -# -# assert openmc.lib.materials[1][] == op.number.get_atom_density('1','U238') -# -# openmc.lib.finalize() +def test_update_materials(run_in_tmpdir): + """ + Method to update volume in AtomNumber after depletion step if 'constant-density' + is passed to the batchwise instance. + Method inheritated by all derived classes so one check is enough. + """ + bracket = [-1,1] + bracket_limit = [-10,10] + op = CoupledOperator(model, CHAIN_PATH) + integrator = openmc.deplete.PredictorIntegrator( + op, [1,1], 0.0, timestep_units = 'd') + integrator.add_batchwise('f', 'refuel', mat_vector={}, bracket=bracket, + bracket_limit=bracket_limit) + + model.export_to_xml() + openmc.lib.init() + + #Increase number of atoms of U238 in fuel by fix amount and check that the + # densities in openmc.lib have beeen updated + mat = integrator.batchwise.material + nuc_index = op.number.index_nuc['U238'] + vol = op.number.get_mat_volume(str(mat.id)) + op.number.number[mat_index][nuc_idx] += 1e22 + x= [op.number.number[i][op.number.number[i].nonzero()] for i in range(len(op.number.number))] + integrator.batchwise._update_materials(x) + + nuc_index_lib = openmc.lib.materials[mat.id].nuclides.index('U238') + assert openmc.lib.materials[mat.id].densities[nuc_index_lib] == \ + 1e-24 * op.number.get_atom_density('1','U238') + + openmc.lib.finalize() # # def test_update_x_and_set_volumes_method(run_in_tmpdir): # """ From c16beceee7581a196bb18e7015b5b23172661828 Mon Sep 17 00:00:00 2001 From: church89 Date: Wed, 6 Sep 2023 09:13:14 +0200 Subject: [PATCH 23/59] unit test update --- openmc/deplete/batchwise.py | 6 +- tests/unit_tests/test_deplete_batchwise.py | 110 ++++++++++++--------- 2 files changed, 68 insertions(+), 48 deletions(-) diff --git a/openmc/deplete/batchwise.py b/openmc/deplete/batchwise.py index b3a2b69f0e4..e5eff7647b6 100644 --- a/openmc/deplete/batchwise.py +++ b/openmc/deplete/batchwise.py @@ -335,7 +335,8 @@ def _update_x_and_set_volumes(self, x, volumes): x : list of numpy.ndarray Total atoms concentrations volumes : dict - + Updated volumes, where key is the material id and value the material + volume in cm3 Returns ------- x : list of numpy.ndarray @@ -697,7 +698,8 @@ def _calculate_volumes(self): Returns ------- volumes : dict - Dictionary of calculate volumes, where key is the mat id + Dictionary of calculated volumes, where key is the mat id and value + the material volume in cm3 """ openmc.lib.calculate_volumes() volumes = {} diff --git a/tests/unit_tests/test_deplete_batchwise.py b/tests/unit_tests/test_deplete_batchwise.py index 6e3cce7854c..cf71986ded4 100644 --- a/tests/unit_tests/test_deplete_batchwise.py +++ b/tests/unit_tests/test_deplete_batchwise.py @@ -21,26 +21,31 @@ def model(): f.add_element("O", 2) f.set_density("g/cc", 10.4) f.temperature = 293.15 + w = openmc.Material(name="w") w.add_element("O", 1) w.add_element("H", 2) w.set_density("g/cc", 1.0) w.depletable = True + h = openmc.Material(name='h') h.set_density('g/cm3', 0.001598) h.add_element('He', 2.4044e-4) radii = [0.42, 0.45] - f.volume = np.pi * radii[0] ** 2 - w.volume = np.pi * (radii[1]**2 - radii[0]**2) + height = 0.5 + f.volume = np.pi * radii[0] ** 2 * height + w.volume = np.pi * (radii[1]**2 - radii[0]**2) * height/2 materials = openmc.Materials([f, w, h]) surf_wh = openmc.ZPlane(z0=0) surf_f = openmc.Sphere(r=radii[0]) surf_w = openmc.Sphere(r=radii[1], boundary_type='vacuum') + surf_top = openmc.ZPlane(z0=height/2) + surf_bot = openmc.ZPlane(z0=-height/2) cell_w = openmc.Cell(fill=w, region=-surf_wh) cell_h = openmc.Cell(fill=h, region=+surf_wh) uni_wh = openmc.Universe(cells=(cell_w, cell_h)) - cell_f = openmc.Cell(name='f',fill=f, region=-surf_f) - cell_wh = openmc.Cell(name='wh',fill=uni_wh, region=+surf_f & -surf_w) + cell_f = openmc.Cell(name='f',fill=f, region=-surf_f & -surf_top & +surf_bot) + cell_wh = openmc.Cell(name='wh',fill=uni_wh, region=+surf_f & -surf_w & -surf_top & +surf_bot) geometry = openmc.Geometry([cell_f, cell_wh]) settings = openmc.Settings() settings.particles = 1000 @@ -106,10 +111,13 @@ def test_cell_methods(run_in_tmpdir, model, attribute): assert integrator.batchwise._get_cell_attrib() == 0 vol = integrator.batchwise._calculate_volumes() for cell in integrator.batchwise.cell_materials: - assert vol[str(cell.id)] == pytest.approx([mat.volume for mat in model.materials if mat.id == cell.id][0]) + assert vol[str(cell.id)] == pytest.approx([ + mat.volume for mat in model.materials \ + if mat.id == cell.id][0], rel=1e-1) + openmc.lib.finalize() -def test_update_volumes(run_in_tmpdir): +def test_update_volumes(run_in_tmpdir, model): """ Method to update volume in AtomNumber after depletion step. Method inheritated by all derived classes so one check is enough. @@ -133,16 +141,18 @@ def test_update_volumes(run_in_tmpdir): nuc_index = op.number.index_nuc['U238'] vol = op.number.get_mat_volume(str(mat.id)) atoms_to_add = 1e22 - op.number.number[mat_index][nuc_idx] += atoms_to_add + op.number.number[mat_index][nuc_index] += atoms_to_add integrator.batchwise._update_volumes() - atoms_to_vol = atoms_to_add * openmc.data.atomic_mass('U238') \ + vol_to_compare = vol + atoms_to_add * openmc.data.atomic_mass('U238') \ /openmc.data.AVOGADRO / mat.density - assert op.number.get_mat_volume('1') == vol + atoms_to_vol + + + assert op.number.get_mat_volume(str(mat.id)) == pytest.approx(vol_to_compare) openmc.lib.finalize() -def test_update_materials(run_in_tmpdir): +def test_update_materials(run_in_tmpdir, model): """ Method to update volume in AtomNumber after depletion step if 'constant-density' is passed to the batchwise instance. @@ -162,47 +172,55 @@ def test_update_materials(run_in_tmpdir): #Increase number of atoms of U238 in fuel by fix amount and check that the # densities in openmc.lib have beeen updated mat = integrator.batchwise.material + mat_index = op.number.index_mat[str(mat.id)] nuc_index = op.number.index_nuc['U238'] vol = op.number.get_mat_volume(str(mat.id)) - op.number.number[mat_index][nuc_idx] += 1e22 - x= [op.number.number[i][op.number.number[i].nonzero()] for i in range(len(op.number.number))] + op.number.number[mat_index][nuc_index] += 1e22 + x = [i[:op.number.n_nuc_burn] for i in op.number.number] integrator.batchwise._update_materials(x) nuc_index_lib = openmc.lib.materials[mat.id].nuclides.index('U238') assert openmc.lib.materials[mat.id].densities[nuc_index_lib] == \ - 1e-24 * op.number.get_atom_density('1','U238') + 1e-24 * op.number.get_atom_density(str(mat.id),'U238') + + openmc.lib.finalize() + +def test_update_x_and_set_volumes_method(run_in_tmpdir,model): + """ + Method to update volume in AtomNumber after depletion step if 'constant-density' + is passed to the batchwise instance. + Method inheritated by all derived classes so one check is enough. + """ + bracket = [-1,1] + bracket_limit = [-10,10] + op = CoupledOperator(model, CHAIN_PATH) + integrator = openmc.deplete.PredictorIntegrator( + op, [1,1], 0.0, timestep_units = 'd') + integrator.add_batchwise('f', 'refuel', mat_vector={}, bracket=bracket, + bracket_limit=bracket_limit) + + model.export_to_xml() + openmc.lib.init() + + #Increase number of atoms of U238 in fuel by fix amount and check that the + # densities in openmc.lib have beeen updated + mat = integrator.batchwise.material + mat_index = op.number.index_mat[str(mat.id)] + nuc_index = op.number.index_nuc['U238'] + vol = op.number.get_mat_volume(str(mat.id)) + # increase number of U238 atoms + op.number.number[mat_index][nuc_index] += 1e22 + x = [i[:op.number.n_nuc_burn] for i in op.number.number] + # Create new volume dict + volumes = {str(mat.id): vol + 1} + new_x = integrator.batchwise._update_x_and_set_volumes(x, volumes) + + # check new x vector is updated accordingly + nuc_index_lib = openmc.lib.materials[mat.id].nuclides.index('U238') + val_to_compare = 1.0e24 * volumes[str(mat.id)] \ + * openmc.lib.materials[mat.id].densities[nuc_index_lib] + assert new_x[mat_index][nuc_index] == pytest.approx(val_to_compare) + # assert volume in AtomNumber is set correctly + assert op.number.get_mat_volume(str(mat.id)) == volumes[str(mat.id)] openmc.lib.finalize() -# -# def test_update_x_and_set_volumes_method(run_in_tmpdir): -# """ -# Method to update volume in AtomNumber after depletion step if 'constant-density' -# is passed to the batchwise instance. -# Method inheritated by all derived classes so one check is enough. -# """ -# bracket = [-1,1] -# bracket_limit = [-10,10] -# axis = 2 -# op = CoupledOperator(model, CHAIN_PATH) -# integrator = openmc.deplete.PredictorIntegrator( -# op, [1,1], 0.0, timestep_units = 'd') -# integrator.add_batchwise('wh', 'translation', axis=axis, bracket=bracket, -# bracket_limit=bracket_limit) -# -# model.export_to_xml() -# openmc.lib.init() -# -# #Increase number of atoms of U238 in fuel by fix amount and check that the -# # densities in openmc.lib have beeen updated -# mat_index = op.number.index_mat['1'] -# nuc_index = op.number.index_nuc['U238'] -# vol = op.number.get_mat_volume('1') -# atoms_to_add = 1e20 -# vol_to_add = 10 -# op.number.number[mat_index][nuc_idx] += atoms_to_add -# op.number.volumes[mat_index] += vol_to_add -# integrator.batchwise._update_x_and_set_volumes(op.number.number, op.number.volumes) -# -# assert -# -# openmc.lib.finalize() From 643a75fba1252b14ebaed771c5350e89b46834dd Mon Sep 17 00:00:00 2001 From: church89 Date: Tue, 12 Sep 2023 14:49:07 +0200 Subject: [PATCH 24/59] further improvements to the module --- openmc/deplete/batchwise.py | 317 ++++++++++++++++++++---------------- 1 file changed, 175 insertions(+), 142 deletions(-) diff --git a/openmc/deplete/batchwise.py b/openmc/deplete/batchwise.py index e5eff7647b6..e9568e777aa 100644 --- a/openmc/deplete/batchwise.py +++ b/openmc/deplete/batchwise.py @@ -13,14 +13,18 @@ check_iterable_type, check_length) class Batchwise(ABC): - """Abstract class defining a generalized batchwise scheme. + """Abstract class defining a generalized batch wise scheme. - In between transport and depletion steps batchwise operations can be added - with the aim of maintain a system critical. These operations act on OpenMC - Cell or Material, parametrizing one or more coefficients while running a - search_for_kef algorithm. + Almost any type of nuclear reactors adopt batchwise schemes to control + reactivity and maintain keff constant and equal to 1, such as control rod + adjustment or material refueling. - Specific classes for running batchwise depletion calculations are + A batch wise scheme can be added here to an integrator instance, + such as :class:`openmc.deplete.CECMIntegrator`, to parametrize one system + variable with the aim of fulfilling a design criteria, such as keeping + keff equal to 1, while running transport-depletion caculations. + + Specific classes for running batch wise depletion calculations are implemented as derived class of Batchwise . .. versionadded:: 0.13.4 @@ -39,7 +43,7 @@ class Batchwise(ABC): algorithm the search_for_keff solution lies off these limits the closest limit will be set as new result. density_treatment : str - Wether ot not to keep contant volume or density after a depletion step + Whether or not to keep constant volume or density after a depletion step before the next one. Default to 'constant-volume' bracketed_method : {'brentq', 'brenth', 'ridder', 'bisect'}, optional @@ -55,10 +59,10 @@ class Batchwise(ABC): This is equivalent to the `target` parameter of the `search_for_keff`. Default to 1.0. print_iterations : Bool, Optional - Wheter or not to print `search_for_keff` iterations. + Whether or not to print `search_for_keff` iterations. Default to True search_for_keff_output : Bool, Optional - Wheter or not to print transport iterations during `search_for_keff`. + Whether or not to print transport iterations during `search_for_keff`. Default to False Attributes ---------- @@ -136,11 +140,15 @@ def target(self, value): check_type("target", value, Real) self._target = value + @classmethod + def from_params(cls, obj, attr, operator, model, **kwargs): + return cls(obj, attr, operator, model, **kwargs) + @abstractmethod def _model_builder(self, param): """ - Builds the parametric model to be passed to the `search_for_keff` - algorithm. + Builds the parametric model to be passed to the + :meth:`openmc.search.search_for_keff` method. Callable function which builds a model according to a passed parameter. This function must return an openmc.model.Model object. Parameters @@ -156,14 +164,14 @@ def _model_builder(self, param): def _search_for_keff(self, val): """ Perform the criticality search for a given parametric model. - It supports cell or material based `search_for_keff`. - If the solution lies off the initial bracket, the method will iteratively - adapt the bracket to be able to run `search_for_keff` effectively. - The ratio between the bracket and the returned keffs values will be - the scaling factor to iteratively adapt the brackt unitl a valid solution + If the solution lies off the initial bracket, this method iteratively + adapt it until :meth:`openmc.search.search_for_keff` return a valid + solution. + The adapting bracket algorithm takes the ratio between the bracket and + the returned keffs values to scale the bracket until a valid solution is found. - If the adapted bracket is moved too far off, i.e. above or below user - inputted bracket limits interval, the algorithm will stop and the closest + A bracket limit pose the upper and lower boundaries to the adapting + bracket. If one limit is hit, the algorithm will stop and the closest limit value will be used. Parameters ---------- @@ -184,7 +192,7 @@ def _search_for_keff(self, val): else: tol = self.tol - # Run until a search_for_keff root is found or ouf ot limits + # Run until a search_for_keff root is found or out of limits root = None while root == None: @@ -267,20 +275,15 @@ def _search_for_keff(self, val): def _update_volumes(self): """ - Update number volume. + Update volumes stored in AtomNumber. After a depletion step, both material volume and density change, due to - decay, transmutation reactions and transfer rates, if set. + change in nuclides composition. At present we lack an implementation to calculate density and volume changes due to the different molecules speciation. Therefore, OpenMC assumes by default that the depletable volume does not change and only updates the nuclide densities and consequently the total material density. This method, vice-versa, assumes that the total material density does not - change and updates the materials volume in AtomNumber dataset. - - Parameters - ---------- - x : list of numpy.ndarray - Total atom concentrations + change and update the material volumes instead. """ number_i = self.operator.number for mat_idx, mat in enumerate(self.local_mats): @@ -295,10 +298,11 @@ def _update_volumes(self): def _update_materials(self, x): """ Update number density and material compositions in OpenMC on all processes. - If density_treatment is set to 'constant-density', "_update_volumes" will - update material volumes, keeping the material total density constant, in - AtomNumber to renormalize the atom densities before assign them to the - model in memory. + If density_treatment is set to 'constant-density' + :meth:`openmc.deplete.batchwise._update_volumes` is called to update + material volumes in AtomNumber, keeping the material total density + constant, before re-normalizing the atom densities and assigning them + to the model in memory. Parameters ---------- x : list of numpy.ndarray @@ -329,14 +333,14 @@ def _update_materials(self, x): def _update_x_and_set_volumes(self, x, volumes): """ - Update x with volumes, before assign them to AtomNumber materials. + Update x vector with new volumes, before assign them to AtomNumber. Parameters ---------- x : list of numpy.ndarray Total atoms concentrations volumes : dict - Updated volumes, where key is the material id and value the material - volume in cm3 + Updated volumes, where key is material id and value material + volume, in cm3 Returns ------- x : list of numpy.ndarray @@ -348,10 +352,10 @@ def _update_x_and_set_volumes(self, x, volumes): if mat in volumes: res_vol = volumes[mat] - # Normalize burbable nuclides in x vector without cross section data + # Normalize burnable nuclides in x vector without cross section data for nuc_idx, nuc in enumerate(number_i.burnable_nuclides): if nuc not in self.operator.nuclides_with_data: - # normalzie with new volume + # normalize with new volume x[mat_idx][nuc_idx] *= number_i.get_mat_volume(mat) / \ res_vol @@ -374,9 +378,9 @@ def _update_x_and_set_volumes(self, x, volumes): return x class BatchwiseCell(Batchwise): - """Abstract class holding batchwise cell-based functions. + """Abstract class holding batch wise cell-based functions. - Specific classes for running batchwise depletion calculations are + Specific classes for running batch wise depletion calculations are implemented as derived class of BatchwiseCell. .. versionadded:: 0.13.4 @@ -384,7 +388,7 @@ class BatchwiseCell(Batchwise): Parameters ---------- cell : openmc.Cell or int or str - OpenMC Cell identifier to where apply batchwise scheme + OpenMC Cell identifier to where apply batch wise scheme operator : openmc.deplete.Operator OpenMC operator object model : openmc.model.Model @@ -397,7 +401,7 @@ class BatchwiseCell(Batchwise): algorithm the search_for_keff solution lies off these limits the closest limit will be set as new result. density_treatment : str - Wether ot not to keep contant volume or density after a depletion step + Whether or not to keep constant volume or density after a depletion step before the next one. Default to 'constant-volume' bracketed_method : {'brentq', 'brenth', 'ridder', 'bisect'}, optional @@ -413,20 +417,23 @@ class BatchwiseCell(Batchwise): This is equivalent to the `target` parameter of the `search_for_keff`. Default to 1.0. print_iterations : Bool, Optional - Wheter or not to print `search_for_keff` iterations. + Whether or not to print `search_for_keff` iterations. Default to True search_for_keff_output : Bool, Optional - Wheter or not to print transport iterations during `search_for_keff`. + Whether or not to print transport iterations during `search_for_keff`. Default to False Attributes ---------- cell : openmc.Cell or int or str - OpenMC Cell identifier to where apply batchwise scheme + OpenMC Cell identifier to where apply batch wise scheme cell_materials : list of openmc.Material Depletable materials that fill the Cell Universe. Only valid for - translation or rotation atttributes + translation or rotation attributes + axis : int {0,1,2} + Directional axis for geometrical parametrization, where 0, 1 and 2 stand + for 'x', 'y' and 'z', respectively. """ - def __init__(self, cell, operator, model, bracket, bracket_limit, + def __init__(self, cell, operator, model, bracket, bracket_limit, axis=None, density_treatment='constant-volume', bracketed_method='brentq', tol=0.01, target=1.0, print_iterations=True, search_for_keff_output=True): @@ -437,6 +444,11 @@ def __init__(self, cell, operator, model, bracket, bracket_limit, self.cell = self._get_cell(cell) + if axis is not None: + #index of cell directional axis + check_value('axis', axis, (0,1,2)) + self.axis = axis + # list of material fill the attribute cell, if depletables self.cell_materials = None @@ -502,16 +514,18 @@ def _get_cell(self, val): def _model_builder(self, param): """ - Builds the parametric model that is passed to the `search_for_keff` - function by setting the parametric variable to the cell. + Builds the parametric model to be passed to the + :meth:`openmc.search.search_for_keff` method. + Callable function which builds a model according to a passed + parameter. This function must return an openmc.model.Model object. Parameters ---------- - param : model parametricl variable - for examlple: cell translation coefficient + param : model parametric variable + cell translation coefficient or cell rotation coefficient Returns ------- self.model : openmc.model.Model - OpenMC parametric model + Openmc parametric model """ self._set_cell_attrib(param) # At this stage it not important to reassign the new volume, as the @@ -523,8 +537,10 @@ def _model_builder(self, param): def search_for_keff(self, x, step_index): """ - Perform the criticality search parametrizing the cell coefficient. - The `search_for_keff` solution is then set as the new cell attribute + Perform the criticality search on the parametric cell coefficient and + update materials accordingly. + The :meth:`openmc.search.search_for_keff` solution is then set as the + new cell attribute. Parameters ---------- x : list of numpy.ndarray @@ -561,7 +577,7 @@ def search_for_keff(self, x, step_index): class BatchwiseCellGeometrical(BatchwiseCell): """ - Batchwise cell-based with geometrical-attribute class. + Batch wise cell-based with geometrical-attribute class. A user doesn't need to call this class directly. Instead an instance of this class is automatically created by calling @@ -573,8 +589,8 @@ class BatchwiseCellGeometrical(BatchwiseCell): Parameters ---------- cell : openmc.Cell or int or str - OpenMC Cell identifier to where apply batchwise scheme - attrib_name : str {'translation', 'rotation'} + OpenMC Cell identifier to where apply batch wise scheme + attrib_name : str Cell attribute type operator : openmc.deplete.Operator OpenMC operator object @@ -591,7 +607,7 @@ class BatchwiseCellGeometrical(BatchwiseCell): algorithm the search_for_keff solution lies off these limits the closest limit will be set as new result. density_treatment : str - Wether ot not to keep contant volume or density after a depletion step + Whether or not to keep constant volume or density after a depletion step before the next one. Default to 'constant-volume' bracketed_method : {'brentq', 'brenth', 'ridder', 'bisect'}, optional @@ -607,27 +623,30 @@ class BatchwiseCellGeometrical(BatchwiseCell): This is equivalent to the `target` parameter of the `search_for_keff`. Default to 1.0. print_iterations : Bool, Optional - Wheter or not to print `search_for_keff` iterations. + Whether or not to print `search_for_keff` iterations. Default to True search_for_keff_output : Bool, Optional - Wheter or not to print transport iterations during `search_for_keff`. + Whether or not to print transport iterations during `search_for_keff`. Default to False Attributes ---------- cell : openmc.Cell or int or str - OpenMC Cell identifier to where apply batchwise scheme + OpenMC Cell identifier to where apply batch wise scheme attrib_name : str {'translation', 'rotation'} Cell attribute type axis : int {0,1,2} Directional axis for geometrical parametrization, where 0, 1 and 2 stand for 'x', 'y' and 'z', respectively. + samples : int + Number of samples used to generate volume estimates for stochastic + volume calculations. """ - def __init__(self, cell, attrib_name, operator, model, axis, bracket, - bracket_limit, density_treatment='constant-volume', - bracketed_method='brentq', tol=0.01, target=1.0, + def __init__(self, cell, attrib_name, operator, model, bracket, + bracket_limit, axis, density_treatment='constant-volume', + bracketed_method='brentq', tol=0.01, target=1.0, samples=1000000, print_iterations=True, search_for_keff_output=True): - super().__init__(cell, operator, model, bracket, bracket_limit, + super().__init__(cell, operator, model, bracket, bracket_limit, axis, density_treatment, bracketed_method, tol, target, print_iterations, search_for_keff_output) @@ -637,9 +656,6 @@ def __init__(self, cell, attrib_name, operator, model, axis, bracket, # check if cell is filled with 2 cells check_length('fill materials', self.cell.fill.cells, 2) - #index of cell directionnal axis - check_value('axis', axis, (0,1,2)) - self.axis = axis # Initialize vector self.vector = np.zeros(3) @@ -647,13 +663,12 @@ def __init__(self, cell, attrib_name, operator, model, axis, bracket, self.cell_materials = [cell.fill for cell in \ self.cell.fill.cells.values() if cell.fill.depletable] + check_type('samples', samples, int) + self.samples = samples + if self.cell_materials: self._initialize_volume_calc() - @classmethod - def from_params(cls, obj, attr, operator, model, **kwargs): - return cls(obj, attr, operator, model, **kwargs) - def _get_cell_attrib(self): """ Get cell attribute coefficient. @@ -672,11 +687,12 @@ def _get_cell_attrib(self): def _set_cell_attrib(self, val): """ Set cell attribute to the cell instance. - Attributes are only applied to cells filled with a universe + Attributes are only applied to a cell filled with a universe containing + two cells itself. Parameters ---------- val : float - Cell coefficient to set in cm for translation and def for rotation + Cell coefficient to set, in cm for translation and deg for rotation """ self.vector[self.axis] = val for cell in openmc.lib.cells.values(): @@ -689,7 +705,8 @@ def _initialize_volume_calc(self): the parametric Cell. """ ll, ur = self.geometry.bounding_box - mat_vol = openmc.VolumeCalculation(self.cell_materials, 100000, ll, ur) + mat_vol = openmc.VolumeCalculation(self.cell_materials, self.samples, + ll, ur) self.model.settings.volume_calculations = mat_vol def _calculate_volumes(self): @@ -698,20 +715,22 @@ def _calculate_volumes(self): Returns ------- volumes : dict - Dictionary of calculated volumes, where key is the mat id and value - the material volume in cm3 + Dictionary of calculated volumes, where key is mat id and value + material volume, in cm3 """ openmc.lib.calculate_volumes() volumes = {} - res = openmc.VolumeCalculation.from_hdf5('volume_1.h5') - for mat_idx, mat in enumerate(self.local_mats): - if int(mat) in [mat.id for mat in self.cell_materials]: - volumes[mat] = res.volumes[int(mat)].n + if comm.rank == 0: + res = openmc.VolumeCalculation.from_hdf5('volume_1.h5') + for mat_idx, mat in enumerate(self.local_mats): + if int(mat) in [mat.id for mat in self.cell_materials]: + volumes[mat] = res.volumes[int(mat)].n + volumes = comm.bcast(volumes) return volumes class BatchwiseCellTemperature(BatchwiseCell): """ - Batchwise cell-based with temperature-attribute class. + Batch wise cell-based with temperature-attribute class. A user doesn't need to call this class directly. Instead an instance of this class is automatically created by calling @@ -723,11 +742,13 @@ class BatchwiseCellTemperature(BatchwiseCell): Parameters ---------- cell : openmc.Cell or int or str - OpenMC Cell identifier to where apply batchwise scheme + OpenMC Cell identifier to where apply batch wise scheme operator : openmc.deplete.Operator OpenMC operator object model : openmc.model.Model OpenMC model object + attrib_name : str + Cell attribute type bracket : list of float Bracketing interval to search for the solution, always relative to the solution at previous step. @@ -736,7 +757,7 @@ class BatchwiseCellTemperature(BatchwiseCell): algorithm the search_for_keff solution lies off these limits the closest limit will be set as new result. density_treatment : str - Wether ot not to keep contant volume or density after a depletion step + Whether or not to keep constant volume or density after a depletion step before the next one. Default to 'constant-volume' bracketed_method : {'brentq', 'brenth', 'ridder', 'bisect'}, optional @@ -752,25 +773,28 @@ class BatchwiseCellTemperature(BatchwiseCell): This is equivalent to the `target` parameter of the `search_for_keff`. Default to 1.0. print_iterations : Bool, Optional - Wheter or not to print `search_for_keff` iterations. + Whether or not to print `search_for_keff` iterations. Default to True search_for_keff_output : Bool, Optional - Wheter or not to print transport iterations during `search_for_keff`. + Whether or not to print transport iterations during `search_for_keff`. Default to False Attributes ---------- cell : openmc.Cell or int or str - OpenMC Cell identifier to where apply batchwise scheme + OpenMC Cell identifier to where apply batch wise scheme """ - def __init__(self, cell, operator, model, bracket, bracket_limit, - density_treatment='constant-volume', bracketed_method='brentq', - tol=0.01, target=1.0, print_iterations=True, - search_for_keff_output=True): + def __init__(self, cell, attrib_name, operator, model, bracket, bracket_limit, + axis=None, density_treatment='constant-volume', + bracketed_method='brentq', tol=0.01, target=1.0, + print_iterations=True, search_for_keff_output=True): - super().__init__(cell, operator, model, bracket, bracket_limit, + super().__init__(cell, operator, model, bracket, bracket_limit, axis, density_treatment, bracketed_method, tol, target, print_iterations, search_for_keff_output) + # Not needed but used for consistency with other classes + check_value('attrib_name', attrib_name, 'temperature') + self.attrib_name = attrib_name # check if initial temperature has been set to right cell material if isinstance(self.cell.fill, openmc.Universe): cells = [cell for cell in self.cell.fill.cells.values() \ @@ -780,17 +804,13 @@ def __init__(self, cell, operator, model, bracket, bracket_limit, check_type('temperature cell real', self.cell.fill.temperature, Real) - @classmethod - def from_params(cls, obj, attr, operator, model, **kwargs): - return cls(obj, operator, model, **kwargs) - def _get_cell_attrib(self): """ - Get cell attribute coefficient. + Get cell temperature. Returns ------- coeff : float - cell temperature in Kelvin + cell temperature, in Kelvin """ for cell in openmc.lib.cells.values(): if cell.id == self.cell.id: @@ -798,20 +818,20 @@ def _get_cell_attrib(self): def _set_cell_attrib(self, val): """ - Set cell attribute to the cell instance. + Set temperature value to the cell instance. Parameters ---------- val : float - Cell temperature to set in Kelvin + Cell temperature to set, in Kelvin """ for cell in openmc.lib.cells.values(): if cell.id == self.cell.id: cell.set_temperature(val) class BatchwiseMaterial(Batchwise): - """Abstract class holding batchwise material-based functions. + """Abstract class holding batch wise material-based functions. - Specific classes for running batchwise depletion calculations are + Specific classes for running batch wise depletion calculations are implemented as derived class of BatchwiseMaterial. .. versionadded:: 0.13.4 @@ -819,14 +839,14 @@ class BatchwiseMaterial(Batchwise): Parameters ---------- material : openmc.Material or int or str - OpenMC Material identifier to where apply batchwise scheme + OpenMC Material identifier to where apply batch wise scheme operator : openmc.deplete.Operator OpenMC operator object model : openmc.model.Model OpenMC model object mat_vector : dict - Dictionary of material composition to paremetrize, where a pair key, value - represents a nuclide and its weight fraction. + Dictionary of material composition to parameterize, where a pair key value + represents a nuclide and its weight fraction, respectively. bracket : list of float Bracketing interval to search for the solution, always relative to the solution at previous step. @@ -835,7 +855,7 @@ class BatchwiseMaterial(Batchwise): algorithm the search_for_keff solution lies off these limits the closest limit will be set as new result. density_treatment : str - Wether ot not to keep contant volume or density after a depletion step + Whether or not to keep constant volume or density after a depletion step before the next one. Default to 'constant-volume' bracketed_method : {'brentq', 'brenth', 'ridder', 'bisect'}, optional @@ -851,18 +871,18 @@ class BatchwiseMaterial(Batchwise): This is equivalent to the `target` parameter of the `search_for_keff`. Default to 1.0. print_iterations : Bool, Optional - Wheter or not to print `search_for_keff` iterations. + Whether or not to print `search_for_keff` iterations. Default to True search_for_keff_output : Bool, Optional - Wheter or not to print transport iterations during `search_for_keff`. + Whether or not to print transport iterations during `search_for_keff`. Default to False Attributes ---------- material : openmc.Material or int or str - OpenMC Material identifier to where apply batchwise scheme + OpenMC Material identifier to where apply batch wise scheme mat_vector : dict - Dictionary of material composition to paremetrize, where a pair key, value - represents a nuclide and its weight fraction. + Dictionary of material composition to parameterize, where a pair key value + represents a nuclide and its weight fraction, respectively. """ def __init__(self, material, operator, model, mat_vector, bracket, @@ -887,16 +907,12 @@ def __init__(self, material, operator, model, mat_vector, bracket, mat_vector[elm] /= sum_values self.mat_vector = mat_vector - @classmethod - def from_params(cls, obj, attr, operator, model, **kwargs): - return cls(obj, operator, model, **kwargs) - def _get_material(self, val): """Helper method for getting openmc material from Material instance or material name or id. Parameters ---------- - val : Openmc.Material or str or int representing material name/id + val : Openmc.Material or str or int representing material name or id Returns ------- val : openmc.Material @@ -930,10 +946,10 @@ def _get_material(self, val): @abstractmethod def _model_builder(self, param): """ - Builds the parametric model that is passed to the `msr_search_for_keff` - function by updating the material densities and setting the parametric - variable as function of the nuclides vector. Since this is a paramteric - material addition (or removal), we can parametrize the volume as well. + Builds the parametric model to be passed to the + :meth:`openmc.search.search_for_keff` method. + Callable function which builds a model according to a passed + parameter. This function must return an openmc.model.Model object. Parameters ---------- param : @@ -946,9 +962,9 @@ def _model_builder(self, param): def search_for_keff(self, x, step_index): """ - Perform the criticality search on the parametric material model. - Will set the root of the `search_for_keff` function to the atoms - concentrations vector. + Perform the criticality search on parametric material variable. + The :meth:`openmc.search.search_for_keff` solution is then used to + calculate the new material volume and update the atoms concentrations. Parameters ---------- x : list of numpy.ndarray @@ -973,7 +989,7 @@ def search_for_keff(self, x, step_index): class BatchwiseMaterialRefuel(BatchwiseMaterial): """ - Batchwise material-based class for refueling (addition or removal) scheme. + Batch wise material-based class for refuelling (addition or removal) scheme. A user doesn't need to call this class directly. Instead an instance of this class is automatically created by calling @@ -985,14 +1001,16 @@ class BatchwiseMaterialRefuel(BatchwiseMaterial): Parameters ---------- material : openmc.Material or int or str - OpenMC Material identifier to where apply batchwise scheme + OpenMC Material identifier to where apply batch wise scheme operator : openmc.deplete.Operator OpenMC operator object + attrib_name : str + Material attribute model : openmc.model.Model OpenMC model object mat_vector : dict - Dictionary of material composition to paremetrize, where a pair key, value - represents a nuclide and its weight fraction. + Dictionary of material composition to parameterize, where a pair key value + represents a nuclide and its weight fraction, respectively. bracket : list of float Bracketing interval to search for the solution, always relative to the solution at previous step. @@ -1001,7 +1019,7 @@ class BatchwiseMaterialRefuel(BatchwiseMaterial): algorithm the search_for_keff solution lies off these limits the closest limit will be set as new result. density_treatment : str - Wether ot not to keep contant volume or density after a depletion step + Whether or not to keep constant volume or density after a depletion step before the next one. Default to 'constant-volume' bracketed_method : {'brentq', 'brenth', 'ridder', 'bisect'}, optional @@ -1017,21 +1035,21 @@ class BatchwiseMaterialRefuel(BatchwiseMaterial): This is equivalent to the `target` parameter of the `search_for_keff`. Default to 1.0. print_iterations : Bool, Optional - Wheter or not to print `search_for_keff` iterations. + Whether or not to print `search_for_keff` iterations. Default to True search_for_keff_output : Bool, Optional - Wheter or not to print transport iterations during `search_for_keff`. + Whether or not to print transport iterations during `search_for_keff`. Default to False Attributes ---------- material : openmc.Material or int or str - OpenMC Material identifier to where apply batchwise scheme + OpenMC Material identifier to where apply batch wise scheme mat_vector : dict - Dictionary of material composition to paremetrize, where a pair key, value - represents a nuclide and its weight fraction. + Dictionary of material composition to parameterize, where a pair key value + represents a nuclide and its weight fraction, respectively. """ - def __init__(self, material, operator, model, mat_vector, bracket, + def __init__(self, material, attrib_name, operator, model, mat_vector, bracket, bracket_limit, density_treatment='constant-volume', bracketed_method='brentq', tol=0.01, target=1.0, print_iterations=True, search_for_keff_output=True): @@ -1040,24 +1058,27 @@ def __init__(self, material, operator, model, mat_vector, bracket, bracket_limit, density_treatment, bracketed_method, tol, target, print_iterations, search_for_keff_output) - @classmethod - def from_params(cls, obj, attr, operator, model, **kwargs): - return cls(obj, operator, model, **kwargs) + # Not needed but used for consistency with other classes + check_value('attrib_name', attrib_name, 'refuel') + self.attrib_name = attrib_name def _model_builder(self, param): """ - Builds the parametric model that is passed to the `msr_search_for_keff` - function by updating the material densities and setting the parametric - variable to the material nuclides to add. We can either fix the total - volume or the material density according to user input. + Callable function which builds a model according to a passed + parameter. This function must return an openmc.model.Model object. + Builds the parametric model to be passed to the + :meth:`openmc.search.search_for_keff` method. + The parametrization can either be at constant volume or constant + density, according to user input. + Default is constant volume. Parameters ---------- - param : + param : float Model function variable, total grams of material to add or remove Returns ------- model : openmc.model.Model - OpenMC parametric model + Openmc parametric model """ for rank in range(comm.size): number_i = comm.bcast(self.operator.number, root=rank) @@ -1106,11 +1127,23 @@ def _model_builder(self, param): #set nuclides and densities to the in-memory model openmc.lib.materials[int(mat)].set_densities(nuclides, densities) - # alwyas need to return a model + # always need to return a model return self.model def _calculate_volumes(self, res): """ + Uses :meth:`openmc.batchwise._search_for_keff` solution as grams of + material to add or remove to calculate new material volume. + Parameters + ---------- + res : float + Solution in grams of material, coming from + :meth:`openmc.batchwise._search_for_keff` + Returns + ------- + volumes : dict + Dictionary of calculated volume, where key mat id and value + material volume, in cm3 """ number_i = self.operator.number volumes = {} From 94abad5bf8b1c7dc880f8426969c358a2884332f Mon Sep 17 00:00:00 2001 From: church89 Date: Tue, 12 Sep 2023 14:49:30 +0200 Subject: [PATCH 25/59] add unit and regression tests --- .../deplete_with_batchwise/__init__.py | 0 .../deplete_with_batchwise/test.py | 122 +++++++++ tests/unit_tests/test_deplete_batchwise.py | 242 ++++++++---------- 3 files changed, 232 insertions(+), 132 deletions(-) create mode 100644 tests/regression_tests/deplete_with_batchwise/__init__.py create mode 100644 tests/regression_tests/deplete_with_batchwise/test.py diff --git a/tests/regression_tests/deplete_with_batchwise/__init__.py b/tests/regression_tests/deplete_with_batchwise/__init__.py new file mode 100644 index 00000000000..e69de29bb2d diff --git a/tests/regression_tests/deplete_with_batchwise/test.py b/tests/regression_tests/deplete_with_batchwise/test.py new file mode 100644 index 00000000000..5d35282b602 --- /dev/null +++ b/tests/regression_tests/deplete_with_batchwise/test.py @@ -0,0 +1,122 @@ +""" Tests for Batchwise class """ + +from pathlib import Path +import shutil +import sys + +import pytest +import numpy as np + +import openmc +import openmc.lib +from openmc.deplete import CoupledOperator + +from tests.regression_tests import config + +@pytest.fixture +def model(): + f = openmc.Material(name='f') + f.set_density('g/cm3', 10.29769) + f.add_element('U', 1., enrichment=2.4) + f.add_element('O', 2.) + + h = openmc.Material(name='h') + h.set_density('g/cm3', 0.001598) + h.add_element('He', 2.4044e-4) + + w = openmc.Material(name='w') + w.set_density('g/cm3', 0.740582) + w.add_element('H', 2) + w.add_element('O', 1) + + # Define overall material + materials = openmc.Materials([f, h, w]) + + # Define surfaces + radii = [0.5, 0.8, 1] + height = 80 + surf_in = openmc.ZCylinder(r=radii[0]) + surf_mid = openmc.ZCylinder(r=radii[1]) + surf_out = openmc.ZCylinder(r=radii[2], boundary_type='reflective') + surf_top = openmc.ZPlane(z0=height/2, boundary_type='vacuum') + surf_bot = openmc.ZPlane(z0=-height/2, boundary_type='vacuum') + + surf_trans = openmc.ZPlane(z0=0) + surf_rot1 = openmc.XPlane(x0=0) + surf_rot2 = openmc.YPlane(y0=0) + + # Define cells + cell_f = openmc.Cell(name='fuel_cell', fill=f, + region=-surf_in & -surf_top & +surf_bot) + cell_g = openmc.Cell(fill=h, + region = +surf_in & -surf_mid & -surf_top & +surf_bot & +surf_rot2) + + # Define unbounded cells for rotation universe + cell_w = openmc.Cell(fill=w, region = -surf_rot1) + cell_h = openmc.Cell(fill=h, region = +surf_rot1) + universe_rot = openmc.Universe(cells=(cell_w, cell_h)) + cell_rot = openmc.Cell(name="rot_cell", fill=universe_rot, + region = +surf_in & -surf_mid & -surf_top & +surf_bot & -surf_rot2) + + # Define unbounded cells for translation universe + cell_w = openmc.Cell(fill=w, region=+surf_in & -surf_trans ) + cell_h = openmc.Cell(fill=h, region=+surf_in & +surf_trans) + universe_trans = openmc.Universe(cells=(cell_w, cell_h)) + cell_trans = openmc.Cell(name="trans_cell", fill=universe_trans, + region=+surf_mid & -surf_out & -surf_top & +surf_bot) + + # Define overall geometry + geometry = openmc.Geometry([cell_f, cell_g, cell_rot, cell_trans]) + + # Set material volume for depletion fuel. + f.volume = np.pi * radii[0]**2 * height + + settings = openmc.Settings() + settings.particles = 1000 + settings.inactive = 10 + settings.batches = 50 + + return openmc.Model(geometry, materials, settings) + +@pytest.mark.skipif(sys.version_info < (3, 9), reason="Requires Python 3.9+") +@pytest.mark.parametrize("obj, attribute, bracket_limit, axis, vec, ref_result", [ + ('trans_cell', 'translation', [-40,40], 2, None, 'depletion_with_translation'), + ('rot_cell', 'rotation', [-90,90], 2, None, 'depletion_with_rotation'), + ('f', 'refuel', [-100,100], None, {'U235':1}, 'depletion_with_refuel') + ]) +def test_batchwise(run_in_tmpdir, model, obj, attribute, bracket_limit, axis, + vec, ref_result): + chain_file = Path(__file__).parents[2] / 'chain_simple.xml' + op = CoupledOperator(model, chain_file) + + integrator = openmc.deplete.PredictorIntegrator( + op, [1], 174., timestep_units = 'd') + + kwargs = {'bracket': [-4,4], 'bracket_limit':bracket_limit, + 'tol': 0.1,} + + if vec is not None: + kwargs['mat_vector']=vec + + if axis is not None: + kwargs['axis'] = axis + + integrator.add_batchwise(obj, attribute, **kwargs) + integrator.integrate() + + # Get path to test and reference results + path_test = op.output_dir / 'depletion_results.h5' + path_reference = Path(__file__).with_name(f'ref_{ref_result}.h5') + + # If updating results, do so and return + if config['update']: + shutil.copyfile(str(path_test), str(path_reference)) + return + + # Load the reference/test results + res_test = openmc.deplete.Results(path_test) + res_ref = openmc.deplete.Results(path_reference) + + # Use hight tolerance here + assert [res.batchwise for res in res_test] == pytest.approx( + [res.batchwise for res in res_ref], rel=2) diff --git a/tests/unit_tests/test_deplete_batchwise.py b/tests/unit_tests/test_deplete_batchwise.py index cf71986ded4..98ae9a01ff3 100644 --- a/tests/unit_tests/test_deplete_batchwise.py +++ b/tests/unit_tests/test_deplete_batchwise.py @@ -1,7 +1,6 @@ """ Tests for Batchwise class """ from pathlib import Path -from math import exp import pytest import numpy as np @@ -16,119 +15,157 @@ @pytest.fixture def model(): - f = openmc.Material(name="f") + f = openmc.Material(name="fuel") f.add_element("U", 1, percent_type="ao", enrichment=4.25) f.add_element("O", 2) f.set_density("g/cc", 10.4) f.temperature = 293.15 - w = openmc.Material(name="w") + w = openmc.Material(name="water") w.add_element("O", 1) w.add_element("H", 2) w.set_density("g/cc", 1.0) + w.temperature = 273.15 w.depletable = True - h = openmc.Material(name='h') + h = openmc.Material(name='helium') + h.add_element('He', 1) h.set_density('g/cm3', 0.001598) - h.add_element('He', 2.4044e-4) + radii = [0.42, 0.45] height = 0.5 + f.volume = np.pi * radii[0] ** 2 * height w.volume = np.pi * (radii[1]**2 - radii[0]**2) * height/2 + materials = openmc.Materials([f, w, h]) - surf_wh = openmc.ZPlane(z0=0) - surf_f = openmc.Sphere(r=radii[0]) - surf_w = openmc.Sphere(r=radii[1], boundary_type='vacuum') + + surf_interface = openmc.ZPlane(z0=0) surf_top = openmc.ZPlane(z0=height/2) surf_bot = openmc.ZPlane(z0=-height/2) - cell_w = openmc.Cell(fill=w, region=-surf_wh) - cell_h = openmc.Cell(fill=h, region=+surf_wh) - uni_wh = openmc.Universe(cells=(cell_w, cell_h)) - cell_f = openmc.Cell(name='f',fill=f, region=-surf_f & -surf_top & +surf_bot) - cell_wh = openmc.Cell(name='wh',fill=uni_wh, region=+surf_f & -surf_w & -surf_top & +surf_bot) - geometry = openmc.Geometry([cell_f, cell_wh]) + surf_in = openmc.Sphere(r=radii[0]) + surf_out = openmc.Sphere(r=radii[1], boundary_type='vacuum') + + cell_water = openmc.Cell(fill=w, region=-surf_interface) + cell_helium = openmc.Cell(fill=h, region=+surf_interface) + universe = openmc.Universe(cells=(cell_water, cell_helium)) + cell_fuel = openmc.Cell(name='fuel_cell', fill=f, + region=-surf_in & -surf_top & +surf_bot) + cell_universe = openmc.Cell(name='universe_cell',fill=universe, + region=+surf_in & -surf_out & -surf_top & +surf_bot) + geometry = openmc.Geometry([cell_fuel, cell_universe]) + settings = openmc.Settings() settings.particles = 1000 settings.inactive = 10 settings.batches = 50 + return openmc.Model(geometry, materials, settings) -@pytest.mark.parametrize("object_name, attribute", [ - ('wh', 'translation'), - ('wh', 'rotation'), - ('f', 'temperature'), - ('f', 'refuel'), +@pytest.mark.parametrize("case_name, obj, attribute, bracket, limit, axis, vec", [ + ('cell translation','universe_cell', 'translation', [-1,1], [-10,10], 2, None), + ('cell rotation', 'universe_cell', 'rotation', [-1,1], [-10,10], 2, None), + ('single cell temperature', 'fuel_cell', 'temperature', [-1,1], [-10,10], 2, None ), + ('multi cell', 'universe_cell', 'temperature', [-1,1], [-10,10], 2, None ), + ('material refuel', 'fuel', 'refuel', [-1,1], [-10,10], None, {'U235':0.1, 'U238':0.9}), + ('invalid_1', 'universe_cell', 'refuel', [-1,1], [-10,10], None, {'U235':0.1, 'U238':0.9}), + ('invalid_2', 'fuel', 'temperature', [-1,1], [-10,10], 2, None ), + ('invalid_3', 'fuel', 'translation', [-1,1], [-10,10], 2, None), ]) -def test_attributes(model, object_name, attribute): +def test_attributes(case_name, model, obj, attribute, bracket, limit, axis, vec): """ Test classes attributes are set correctly """ op = CoupledOperator(model, CHAIN_PATH) - bracket = [-1,1] - bracket_limit = [-10,10] - axis = 2 - mat_vector = {'U235':0.1, 'U238':0.9} - if attribute in ('translation','rotation'): - bw = BatchwiseCellGeometrical(object_name, attribute, op, model, axis, bracket, bracket_limit) - assert bw.cell_materials == [cell.fill for cell in \ - model.geometry.get_cells_by_name(object_name)[0].fill.cells.values() \ - if cell.fill.depletable] - assert bw.attrib_name == attribute - assert bw.axis == axis - - elif attribute == 'temperature': - bw = BatchwiseCellTemperature(object_name, op, model, bracket, bracket_limit) - elif attribute == 'refuel': - bw = BatchwiseMaterialRefuel(object_name, op, model, mat_vector, bracket, bracket_limit) - assert bw.mat_vector == mat_vector - - assert bw.bracket == bracket - assert bw.bracket_limit == bracket_limit - assert bw.burn_mats == op.burnable_mats - assert bw.local_mats == op.local_mats - -@pytest.mark.parametrize("attribute", [ - ('translation'), - ('rotation') + int = openmc.deplete.PredictorIntegrator( + op, [1,1], 0.0, timestep_units = 'd') + + kwargs = {'bracket': bracket, 'bracket_limit':limit, 'tol': 0.1,} + + if vec is not None: + kwargs['mat_vector']=vec + + if axis is not None: + kwargs['axis'] = axis + + if case_name == "invalid_1": + with pytest.raises(ValueError) as e: + int.add_batchwise(obj, attribute, **kwargs) + assert str(e.value) == 'Unable to set "Material name" to "universe_cell" '\ + 'since it is not in "[\'fuel\', \'water\']"' + elif case_name == "invalid_2": + with pytest.raises(ValueError) as e: + int.add_batchwise(obj, attribute, **kwargs) + assert str(e.value) == 'Unable to set "Cell name exists" to "fuel" since '\ + 'it is not in "[\'fuel_cell\', \'universe_cell\', \'\', \'\']"' + + elif case_name == "invalid_3": + with pytest.raises(ValueError) as e: + int.add_batchwise(obj, attribute, **kwargs) + assert str(e.value) == 'Unable to set "Cell name exists" to "fuel" since '\ + 'it is not in "[\'fuel_cell\', \'universe_cell\', \'\', \'\']"' + else: + int.add_batchwise(obj, attribute, **kwargs) + if attribute in ('translation','rotation'): + assert int.batchwise.cell_materials == [cell.fill for cell in \ + model.geometry.get_cells_by_name(obj)[0].fill.cells.values() \ + if cell.fill.depletable] + assert int.batchwise.axis == axis + + elif attribute == 'refuel': + assert int.batchwise.mat_vector == vec + + assert int.batchwise.attrib_name == attribute + assert int.batchwise.bracket == bracket + assert int.batchwise.bracket_limit == limit + assert int.batchwise.burn_mats == op.burnable_mats + assert int.batchwise.local_mats == op.local_mats + +@pytest.mark.parametrize("obj, attribute, value_to_set", [ + ('universe_cell', 'translation', 0), + ('universe_cell', 'rotation', 0) ]) -def test_cell_methods(run_in_tmpdir, model, attribute): +def test_cell_methods(run_in_tmpdir, model, obj, attribute, value_to_set): """ Test cell base class internal method """ - bracket = [-1,1] - bracket_limit = [-10,10] - axis = 2 + kwargs = {'bracket':[-1,1], 'bracket_limit':[-10,10], 'axis':2, 'tol':0.1} + op = CoupledOperator(model, CHAIN_PATH) integrator = openmc.deplete.PredictorIntegrator( op, [1,1], 0.0, timestep_units = 'd') - integrator.add_batchwise('wh', attribute, axis=axis, bracket=bracket, - bracket_limit=bracket_limit) + integrator.add_batchwise(obj, attribute, **kwargs) model.export_to_xml() openmc.lib.init() - integrator.batchwise._set_cell_attrib(0) - assert integrator.batchwise._get_cell_attrib() == 0 + integrator.batchwise._set_cell_attrib(value_to_set) + assert integrator.batchwise._get_cell_attrib() == value_to_set + vol = integrator.batchwise._calculate_volumes() for cell in integrator.batchwise.cell_materials: assert vol[str(cell.id)] == pytest.approx([ - mat.volume for mat in model.materials \ - if mat.id == cell.id][0], rel=1e-1) + mat.volume for mat in model.materials \ + if mat.id == cell.id][0], rel=tolerance) openmc.lib.finalize() -def test_update_volumes(run_in_tmpdir, model): +@pytest.mark.parametrize("nuclide, atoms_to_add", [ + ('U238', 1.0e22), + ('Xe135', 1.0e21) + ]) +def test_internal_methods(run_in_tmpdir, model, nuclide, atoms_to_add): """ Method to update volume in AtomNumber after depletion step. Method inheritated by all derived classes so one check is enough. """ - bracket = [-1,1] - bracket_limit = [-10,10] + + kwargs = {'bracket':[-1,1], 'bracket_limit':[-10,10], 'mat_vector':{}} + op = CoupledOperator(model, CHAIN_PATH) - integrator = openmc.deplete.PredictorIntegrator( - op, [1,1], 0.0, timestep_units = 'd') - integrator.add_batchwise('f', 'refuel', mat_vector={}, bracket=bracket, - bracket_limit=bracket_limit) + integrator = openmc.deplete.PredictorIntegrator(op, [1,1], 0.0, + timestep_units = 'd') + integrator.add_batchwise('fuel', 'refuel', **kwargs) model.export_to_xml() openmc.lib.init() @@ -138,89 +175,30 @@ def test_update_volumes(run_in_tmpdir, model): #extract fuel material from model materials mat = integrator.batchwise.material mat_index = op.number.index_mat[str(mat.id)] - nuc_index = op.number.index_nuc['U238'] + nuc_index = op.number.index_nuc[nuclide] vol = op.number.get_mat_volume(str(mat.id)) - atoms_to_add = 1e22 op.number.number[mat_index][nuc_index] += atoms_to_add integrator.batchwise._update_volumes() - vol_to_compare = vol + atoms_to_add * openmc.data.atomic_mass('U238') \ - /openmc.data.AVOGADRO / mat.density - + vol_to_compare = vol + (atoms_to_add * openmc.data.atomic_mass(nuclide) /\ + openmc.data.AVOGADRO / mat.density) assert op.number.get_mat_volume(str(mat.id)) == pytest.approx(vol_to_compare) - openmc.lib.finalize() - -def test_update_materials(run_in_tmpdir, model): - """ - Method to update volume in AtomNumber after depletion step if 'constant-density' - is passed to the batchwise instance. - Method inheritated by all derived classes so one check is enough. - """ - bracket = [-1,1] - bracket_limit = [-10,10] - op = CoupledOperator(model, CHAIN_PATH) - integrator = openmc.deplete.PredictorIntegrator( - op, [1,1], 0.0, timestep_units = 'd') - integrator.add_batchwise('f', 'refuel', mat_vector={}, bracket=bracket, - bracket_limit=bracket_limit) - - model.export_to_xml() - openmc.lib.init() - - #Increase number of atoms of U238 in fuel by fix amount and check that the - # densities in openmc.lib have beeen updated - mat = integrator.batchwise.material - mat_index = op.number.index_mat[str(mat.id)] - nuc_index = op.number.index_nuc['U238'] - vol = op.number.get_mat_volume(str(mat.id)) - op.number.number[mat_index][nuc_index] += 1e22 x = [i[:op.number.n_nuc_burn] for i in op.number.number] integrator.batchwise._update_materials(x) + nuc_index_lib = openmc.lib.materials[mat.id].nuclides.index(nuclide) + dens_to_compare = 1.0e-24 * op.number.get_atom_density(str(mat.id), nuclide) - nuc_index_lib = openmc.lib.materials[mat.id].nuclides.index('U238') - assert openmc.lib.materials[mat.id].densities[nuc_index_lib] == \ - 1e-24 * op.number.get_atom_density(str(mat.id),'U238') - - openmc.lib.finalize() - -def test_update_x_and_set_volumes_method(run_in_tmpdir,model): - """ - Method to update volume in AtomNumber after depletion step if 'constant-density' - is passed to the batchwise instance. - Method inheritated by all derived classes so one check is enough. - """ - bracket = [-1,1] - bracket_limit = [-10,10] - op = CoupledOperator(model, CHAIN_PATH) - integrator = openmc.deplete.PredictorIntegrator( - op, [1,1], 0.0, timestep_units = 'd') - integrator.add_batchwise('f', 'refuel', mat_vector={}, bracket=bracket, - bracket_limit=bracket_limit) - - model.export_to_xml() - openmc.lib.init() + assert openmc.lib.materials[mat.id].densities[nuc_index_lib] == pytest.approx(dens_to_compare) - #Increase number of atoms of U238 in fuel by fix amount and check that the - # densities in openmc.lib have beeen updated - mat = integrator.batchwise.material - mat_index = op.number.index_mat[str(mat.id)] - nuc_index = op.number.index_nuc['U238'] - vol = op.number.get_mat_volume(str(mat.id)) - # increase number of U238 atoms - op.number.number[mat_index][nuc_index] += 1e22 - x = [i[:op.number.n_nuc_burn] for i in op.number.number] - # Create new volume dict volumes = {str(mat.id): vol + 1} new_x = integrator.batchwise._update_x_and_set_volumes(x, volumes) - - # check new x vector is updated accordingly - nuc_index_lib = openmc.lib.materials[mat.id].nuclides.index('U238') - val_to_compare = 1.0e24 * volumes[str(mat.id)] \ - * openmc.lib.materials[mat.id].densities[nuc_index_lib] - assert new_x[mat_index][nuc_index] == pytest.approx(val_to_compare) + dens_to_compare = 1.0e24 * volumes[str(mat.id)] *\ + openmc.lib.materials[mat.id].densities[nuc_index_lib] + assert new_x[mat_index][nuc_index] == pytest.approx(dens_to_compare) # assert volume in AtomNumber is set correctly assert op.number.get_mat_volume(str(mat.id)) == volumes[str(mat.id)] + openmc.lib.finalize() From 688631c9fdb732c328be4ec71b9a2b909be30520 Mon Sep 17 00:00:00 2001 From: church89 Date: Wed, 13 Sep 2023 16:52:51 +0200 Subject: [PATCH 26/59] add missing docstrings --- openmc/deplete/abc.py | 14 ++++++++++---- openmc/deplete/batchwise.py | 11 +++++------ 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py index 7f06fd332f9..6dcb0eac167 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -556,6 +556,9 @@ class Integrator(ABC): transfer_rates : openmc.deplete.TransferRates Instance of TransferRates class to perform continuous transfer during depletion + batchwise : openmc.deplete.Batchwise + Instance of Batchwise class to perform batch-wise scheme during + transport-depletion simulation. .. versionadded:: 0.14.0 @@ -881,15 +884,18 @@ def add_batchwise(self, obj, attr, **kwargs): Parameters ---------- + obj : openmc.Cell or openmc.Material object or id or str name + Cell or Materials identifier to where add batchwise scheme attr : str - Type of batchwise operation to add. `Trans` stands for geometrical - translation, `refuel` for material refueling and `dilute` for material - dilute. + Attribute to specify the type of batchwise scheme. Accepted values + are: 'translation', 'rotation', 'temperature' for an openmc.Cell + object; 'refuel' for an openmc.Material object. **kwargs keyword arguments that are passed to the batchwise class. """ - check_value('attribute', attr, ('translation', 'rotation', 'temperature', 'refuel')) + check_value('attribute', attr, ('translation', 'rotation', + 'temperature', 'refuel')) if attr in ('translation', 'rotation'): batchwise = BatchwiseCellGeometrical elif attr == 'temperature': diff --git a/openmc/deplete/batchwise.py b/openmc/deplete/batchwise.py index e9568e777aa..955d2878b78 100644 --- a/openmc/deplete/batchwise.py +++ b/openmc/deplete/batchwise.py @@ -15,14 +15,13 @@ class Batchwise(ABC): """Abstract class defining a generalized batch wise scheme. - Almost any type of nuclear reactors adopt batchwise schemes to control - reactivity and maintain keff constant and equal to 1, such as control rod - adjustment or material refueling. + Batchwise schemes, such as control rod adjustment or material refueling to + control reactivity and maintain keff constant and equal to one. A batch wise scheme can be added here to an integrator instance, such as :class:`openmc.deplete.CECMIntegrator`, to parametrize one system - variable with the aim of fulfilling a design criteria, such as keeping - keff equal to 1, while running transport-depletion caculations. + variable with the aim of satisfy certain design criteria, such as keeping + keff equal to one, while running transport-depletion caculations. Specific classes for running batch wise depletion calculations are implemented as derived class of Batchwise @@ -277,7 +276,7 @@ def _update_volumes(self): """ Update volumes stored in AtomNumber. After a depletion step, both material volume and density change, due to - change in nuclides composition. + changes in nuclides composition. At present we lack an implementation to calculate density and volume changes due to the different molecules speciation. Therefore, OpenMC assumes by default that the depletable volume does not change and only From 325969f56117bc83d3d1493fd828e60ffd150fb9 Mon Sep 17 00:00:00 2001 From: church89 Date: Wed, 13 Sep 2023 17:07:26 +0200 Subject: [PATCH 27/59] remove unused import --- openmc/deplete/abc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py index 6dcb0eac167..3f1b52f8243 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -25,7 +25,7 @@ from .results import Results from .pool import deplete from .transfer_rates import TransferRates -from openmc import Material, Cell, Universe +from openmc import Material, Cell from .batchwise import (BatchwiseCellGeometrical, BatchwiseCellTemperature, BatchwiseMaterialRefuel) From 5d6e9914035109140229c0c4b9b39e126b45e0c2 Mon Sep 17 00:00:00 2001 From: church89 Date: Wed, 13 Sep 2023 17:07:57 +0200 Subject: [PATCH 28/59] add missing returns --- openmc/search.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openmc/search.py b/openmc/search.py index 4818fbe10bd..7e2c5a1a19d 100644 --- a/openmc/search.py +++ b/openmc/search.py @@ -205,7 +205,7 @@ def search_for_keff(model_builder, initial_guess=None, target=1.0, # Perform the search if not run_in_memory: zero_value = root_finder(**args) - return zero_value + return zero_value, guesses, results else: try: From 85a1f3505fc290d7d1f7c53e6b6559a35803cacb Mon Sep 17 00:00:00 2001 From: church89 Date: Wed, 13 Sep 2023 17:08:16 +0200 Subject: [PATCH 29/59] fix typo --- tests/regression_tests/deplete_with_batchwise/test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/regression_tests/deplete_with_batchwise/test.py b/tests/regression_tests/deplete_with_batchwise/test.py index 5d35282b602..c7fd59788bb 100644 --- a/tests/regression_tests/deplete_with_batchwise/test.py +++ b/tests/regression_tests/deplete_with_batchwise/test.py @@ -117,6 +117,6 @@ def test_batchwise(run_in_tmpdir, model, obj, attribute, bracket_limit, axis, res_test = openmc.deplete.Results(path_test) res_ref = openmc.deplete.Results(path_reference) - # Use hight tolerance here + # Use high tolerance here assert [res.batchwise for res in res_test] == pytest.approx( [res.batchwise for res in res_ref], rel=2) From d69a8a853a81cd004eb05f1fd43549f0291f728e Mon Sep 17 00:00:00 2001 From: church89 Date: Thu, 14 Sep 2023 12:03:23 +0200 Subject: [PATCH 30/59] prevent to openmc batchwise_root when to there --- openmc/deplete/stepresult.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/openmc/deplete/stepresult.py b/openmc/deplete/stepresult.py index 3aa8b45dc08..2d4328441b2 100644 --- a/openmc/deplete/stepresult.py +++ b/openmc/deplete/stepresult.py @@ -468,19 +468,21 @@ def from_hdf5(cls, handle, step): else: # Older versions used "power" instead of "source_rate" source_rate_dset = handle["/power"] - root_dset = handle["/batchwise_root"] results.data = number_dset[step, :, :, :] results.k = eigenvalues_dset[step, :] results.time = time_dset[step, :] results.source_rate = source_rate_dset[step, 0] - results.batchwise = root_dset[step] if "depletion time" in handle: proc_time_dset = handle["/depletion time"] if step < proc_time_dset.shape[0]: results.proc_time = proc_time_dset[step] + if "batchwise_root" in handle: + root_dset = handle["/batchwise_root"] + results.batchwise = root_dset[step] + if results.proc_time is None: results.proc_time = np.array([np.nan]) From 13ee2e3daf4858c682c49c1a0fd900b6f4031e69 Mon Sep 17 00:00:00 2001 From: Lorenzo Chierici Date: Mon, 5 Feb 2024 12:43:20 +0100 Subject: [PATCH 31/59] Apply suggestions from code review Co-authored-by: Andrew Johnson --- openmc/deplete/abc.py | 4 ++-- openmc/deplete/batchwise.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py index 3f1b52f8243..a5a615a1edf 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -812,7 +812,7 @@ def integrate( # Solve transport equation (or obtain result from restart) if i > 0 or self.operator.prev_res is None: # Update geometry/material according to batchwise definition - if self.batchwise and source_rate != 0.0: + if self.batchwise is not None and source_rate != 0.0: n, root = self._get_bos_from_batchwise(i, n) else: root = None @@ -840,7 +840,7 @@ def integrate( # solve) if output and final_step and comm.rank == 0: print(f"[openmc.deplete] t={t} (final operator evaluation)") - if self.batchwise and source_rate != 0.0: + if self.batchwise is not None and source_rate != 0.0: n, root = self._get_bos_from_batchwise(i+1, n) else: root = None diff --git a/openmc/deplete/batchwise.py b/openmc/deplete/batchwise.py index 955d2878b78..4abda675bcc 100644 --- a/openmc/deplete/batchwise.py +++ b/openmc/deplete/batchwise.py @@ -58,11 +58,11 @@ class Batchwise(ABC): This is equivalent to the `target` parameter of the `search_for_keff`. Default to 1.0. print_iterations : Bool, Optional - Whether or not to print `search_for_keff` iterations. + Print a status message each iteration Default to True search_for_keff_output : Bool, Optional - Whether or not to print transport iterations during `search_for_keff`. - Default to False + Print full transport logs during iterations (e.g., inactive generations, active + generations). Default to False Attributes ---------- burn_mats : list of str From 1731fdab5e36803da41b539bdae1474228bc33b3 Mon Sep 17 00:00:00 2001 From: church89 Date: Mon, 5 Feb 2024 16:23:49 +0100 Subject: [PATCH 32/59] address comments by @drewejohnson --- openmc/deplete/abc.py | 11 ++- openmc/deplete/batchwise.py | 132 +++++++++++++++++------------------- 2 files changed, 66 insertions(+), 77 deletions(-) diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py index a5a615a1edf..c9d0f3918c6 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -641,7 +641,7 @@ def __init__(self, operator, timesteps, power=None, power_density=None, self.source_rates = asarray(source_rates) self.transfer_rates = None - self.batchwise = None + self._batchwise = None if isinstance(solver, str): # Delay importing of cram module, which requires this file @@ -774,7 +774,7 @@ def _get_bos_from_batchwise(self, step_index, bos_conc): """ x = deepcopy(bos_conc) # Get new vector after keff criticality control - x, root = self.batchwise.search_for_keff(x, step_index) + x, root = self._batchwise.search_for_keff(x, step_index) return x, root def integrate( @@ -812,7 +812,7 @@ def integrate( # Solve transport equation (or obtain result from restart) if i > 0 or self.operator.prev_res is None: # Update geometry/material according to batchwise definition - if self.batchwise is not None and source_rate != 0.0: + if self._batchwise is not None and source_rate != 0.0: n, root = self._get_bos_from_batchwise(i, n) else: root = None @@ -840,7 +840,7 @@ def integrate( # solve) if output and final_step and comm.rank == 0: print(f"[openmc.deplete] t={t} (final operator evaluation)") - if self.batchwise is not None and source_rate != 0.0: + if self._batchwise is not None and source_rate != 0.0: n, root = self._get_bos_from_batchwise(i+1, n) else: root = None @@ -903,8 +903,7 @@ def add_batchwise(self, obj, attr, **kwargs): elif attr == 'refuel': batchwise = BatchwiseMaterialRefuel - self.batchwise = batchwise.from_params(obj, attr, self.operator, - self.operator.model, **kwargs) + self._batchwise = batchwise.from_params(obj, attr, self.operator,**kwargs) @add_params class SIIntegrator(Integrator): diff --git a/openmc/deplete/batchwise.py b/openmc/deplete/batchwise.py index 4abda675bcc..f716aa1b3dc 100644 --- a/openmc/deplete/batchwise.py +++ b/openmc/deplete/batchwise.py @@ -24,8 +24,8 @@ class Batchwise(ABC): keff equal to one, while running transport-depletion caculations. Specific classes for running batch wise depletion calculations are - implemented as derived class of Batchwise - . + implemented as derived class of Batchwise. + .. versionadded:: 0.13.4 Parameters @@ -71,7 +71,7 @@ class Batchwise(ABC): All burnable material IDs being managed by a single process List of burnable materials ids """ - def __init__(self, operator, model, bracket, bracket_limit, + def __init__(self, operator, bracket, bracket_limit, density_treatment = 'constant-volume', bracketed_method='brentq', tol=0.01, target=1.0, print_iterations=True, search_for_keff_output=True): @@ -79,16 +79,12 @@ def __init__(self, operator, model, bracket, bracket_limit, self.operator = operator self.burn_mats = operator.burnable_mats self.local_mats = operator.local_mats - self.model = model - self.geometry = model.geometry + self.model = operator.model + self.geometry = operator.model.geometry check_value('density_treatment', density_treatment, ('constant-density','constant-volume')) self.density_treatment = density_treatment - - check_iterable_type('bracket', bracket, Real) - check_length('bracket', bracket, 2) - check_less_than('bracket values', bracket[0], bracket[1]) self.bracket = bracket check_iterable_type('bracket_limit', bracket_limit, Real) @@ -97,14 +93,10 @@ def __init__(self, operator, model, bracket, bracket_limit, bracket_limit[0], bracket_limit[1]) self.bracket_limit = bracket_limit - check_value('bracketed_method', bracketed_method, - _SCALAR_BRACKETED_METHODS) self.bracketed_method = bracketed_method - check_type('tol', tol, Real) self.tol = tol - check_type('target', target, Real) self.target = target self.print_iterations = print_iterations @@ -140,8 +132,8 @@ def target(self, value): self._target = value @classmethod - def from_params(cls, obj, attr, operator, model, **kwargs): - return cls(obj, attr, operator, model, **kwargs) + def from_params(cls, obj, attr, operator, **kwargs): + return cls(obj, attr, operator, **kwargs) @abstractmethod def _model_builder(self, param): @@ -185,19 +177,13 @@ def _search_for_keff(self, val): #make sure we don't modify original bracket and tol values bracket = deepcopy(self.bracket) - #search_for_keff tolerance should vary according to the first guess value - if abs(val) > 1.0: - tol = self.tol / abs(val) - else: - tol = self.tol - # Run until a search_for_keff root is found or out of limits root = None while root == None: search = search_for_keff(self._model_builder, bracket = np.array(bracket) + val, - tol = tol, + tol = self.tol, bracketed_method = self.bracketed_method, target = self.target, print_iterations = self.print_iterations, @@ -432,17 +418,20 @@ class BatchwiseCell(Batchwise): Directional axis for geometrical parametrization, where 0, 1 and 2 stand for 'x', 'y' and 'z', respectively. """ - def __init__(self, cell, operator, model, bracket, bracket_limit, axis=None, + def __init__(self, cell, operator, bracket, bracket_limit, axis=None, density_treatment='constant-volume', bracketed_method='brentq', tol=0.01, target=1.0, print_iterations=True, search_for_keff_output=True): - super().__init__(operator, model, bracket, bracket_limit, density_treatment, + super().__init__(operator, bracket, bracket_limit, density_treatment, bracketed_method, tol, target, print_iterations, search_for_keff_output) self.cell = self._get_cell(cell) + # Initialize to None, as openmc.lib is not initialized yet here + self._cell = None + if axis is not None: #index of cell directional axis check_value('axis', axis, (0,1,2)) @@ -471,6 +460,13 @@ def _set_cell_attrib(self, val): cell coefficient to set """ + def _set_cell(self): + """ + Set _cell attribute to the corresponding openmc.lib.cell + """ + self._cell = [cell for cell in openmc.lib.cells.values() \ + if cell.id == self.cell.id][0] + def _get_cell(self, val): """Helper method for getting cell from cell instance or cell name or id. Parameters @@ -481,31 +477,24 @@ def _get_cell(self, val): val : openmc.Cell Openmc Cell """ + all_cells = [(cell.id, cell.name, cell) for cell in \ + self.geometry.get_all_cells().values()] + if isinstance(val, Cell): - check_value('Cell exists', val, [cell for cell in \ - self.geometry.get_all_cells().values()]) + check_value('Cell exists', val, [cell[2] for cell in all_cells]) elif isinstance(val, str): if val.isnumeric(): - check_value('Cell id exists', int(val), [cell.id for cell in \ - self.geometry.get_all_cells().values()]) - val = [cell for cell in \ - self.geometry.get_all_cells().values() \ - if cell.id == int(val)][0] - else: - check_value('Cell name exists', val, [cell.name for cell in \ - self.geometry.get_all_cells().values()]) + check_value('Cell id exists', int(val), [cell[0] for cell in all_cells]) + val = [cell[2] for cell in all_cells if cell[0] == int(val)][0] - val = [cell for cell in \ - self.geometry.get_all_cells().values() \ - if cell.name == val][0] + else: + check_value('Cell name exists', val, [cell[1] for cell in all_cells]) + val = [cell[2] for cell in all_cells if cell[1] == val][0] elif isinstance(val, int): - check_value('Cell id exists', val, [cell.id for cell in \ - self.geometry.get_all_cells().values()]) - val = [cell for cell in \ - self.geometry.get_all_cells().values() \ - if cell.id == val][0] + check_value('Cell id exists', val, [cell[0] for cell in all_cells]) + val = [cell[2] for cell in all_cells if cell[0] == val][0] else: ValueError(f'Cell: {val} is not supported') @@ -548,7 +537,13 @@ def search_for_keff(self, x, step_index): ------- x : list of numpy.ndarray Updated total atoms concentrations + root : float + Search_for_keff returned root value """ + # set _cell argument, once openmc.lib is intialized + if self._cell is None: + self._set_cell() + # Get cell attribute from previous iteration val = self._get_cell_attrib() check_type('Cell coeff', val, Real) @@ -556,10 +551,10 @@ def search_for_keff(self, x, step_index): # Update all material densities from concentration vectors #before performing the search_for_keff. This is needed before running # the transport equations in the search_for_keff algorithm - super()._update_materials(x) + self._update_materials(x) # Calculate new cell attribute - root = super()._search_for_keff(val) + root = self._search_for_keff(val) # set results value as attribute in the geometry self._set_cell_attrib(root) @@ -569,7 +564,7 @@ def search_for_keff(self, x, step_index): # new volume if self.cell_materials: volumes = self._calculate_volumes() - x = super()._update_x_and_set_volumes(x, volumes) + x = self._update_x_and_set_volumes(x, volumes) return x, root @@ -640,12 +635,12 @@ class BatchwiseCellGeometrical(BatchwiseCell): Number of samples used to generate volume estimates for stochastic volume calculations. """ - def __init__(self, cell, attrib_name, operator, model, bracket, + def __init__(self, cell, attrib_name, operator, bracket, bracket_limit, axis, density_treatment='constant-volume', bracketed_method='brentq', tol=0.01, target=1.0, samples=1000000, print_iterations=True, search_for_keff_output=True): - super().__init__(cell, operator, model, bracket, bracket_limit, axis, + super().__init__(cell, operator, bracket, bracket_limit, axis, density_treatment, bracketed_method, tol, target, print_iterations, search_for_keff_output) @@ -676,12 +671,10 @@ def _get_cell_attrib(self): coeff : float cell coefficient """ - for cell in openmc.lib.cells.values(): - if cell.id == self.cell.id: - if self.attrib_name == 'translation': - return cell.translation[self.axis] - elif self.attrib_name == 'rotation': - return cell.rotation[self.axis] + if self.attrib_name == 'translation': + return self._cell.translation[self.axis] + elif self.attrib_name == 'rotation': + return self._cell.rotation[self.axis] def _set_cell_attrib(self, val): """ @@ -694,9 +687,7 @@ def _set_cell_attrib(self, val): Cell coefficient to set, in cm for translation and deg for rotation """ self.vector[self.axis] = val - for cell in openmc.lib.cells.values(): - if cell.id == self.cell.id: - setattr(cell, self.attrib_name, self.vector) + setattr(self._cell, self.attrib_name, self.vector) def _initialize_volume_calc(self): """ @@ -782,18 +773,19 @@ class BatchwiseCellTemperature(BatchwiseCell): cell : openmc.Cell or int or str OpenMC Cell identifier to where apply batch wise scheme """ - def __init__(self, cell, attrib_name, operator, model, bracket, bracket_limit, + def __init__(self, cell, attrib_name, operator, bracket, bracket_limit, axis=None, density_treatment='constant-volume', bracketed_method='brentq', tol=0.01, target=1.0, print_iterations=True, search_for_keff_output=True): - super().__init__(cell, operator, model, bracket, bracket_limit, axis, + super().__init__(cell, operator, bracket, bracket_limit, axis, density_treatment, bracketed_method, tol, target, print_iterations, search_for_keff_output) # Not needed but used for consistency with other classes check_value('attrib_name', attrib_name, 'temperature') self.attrib_name = attrib_name + # check if initial temperature has been set to right cell material if isinstance(self.cell.fill, openmc.Universe): cells = [cell for cell in self.cell.fill.cells.values() \ @@ -811,9 +803,7 @@ def _get_cell_attrib(self): coeff : float cell temperature, in Kelvin """ - for cell in openmc.lib.cells.values(): - if cell.id == self.cell.id: - return cell.get_temperature() + return self._cell.get_temperature() def _set_cell_attrib(self, val): """ @@ -823,9 +813,7 @@ def _set_cell_attrib(self, val): val : float Cell temperature to set, in Kelvin """ - for cell in openmc.lib.cells.values(): - if cell.id == self.cell.id: - cell.set_temperature(val) + self._cell.set_temperature(val) class BatchwiseMaterial(Batchwise): """Abstract class holding batch wise material-based functions. @@ -884,12 +872,12 @@ class BatchwiseMaterial(Batchwise): represents a nuclide and its weight fraction, respectively. """ - def __init__(self, material, operator, model, mat_vector, bracket, + def __init__(self, material, operator, mat_vector, bracket, bracket_limit, density_treatment='constant-volume', bracketed_method='brentq', tol=0.01, target=1.0, print_iterations=True, search_for_keff_output=True): - super().__init__(operator, model, bracket, bracket_limit, + super().__init__(operator, bracket, bracket_limit, density_treatment, bracketed_method, tol, target, print_iterations, search_for_keff_output) @@ -972,17 +960,19 @@ def search_for_keff(self, x, step_index): ------- x : list of numpy.ndarray Updated total atoms concentrations + root : float + Search_for_keff returned root value """ # Update AtomNumber with new conc vectors x. Materials are also updated # even though they are also re-calculated when running the search_for_kef - super()._update_materials(x) + self._update_materials(x) # Solve search_for_keff and find new value - root = super()._search_for_keff(0) + root = self._search_for_keff(0) #Update concentration vector and volumes with new value volumes = self._calculate_volumes(root) - x = super()._update_x_and_set_volumes(x, volumes) + x = self._update_x_and_set_volumes(x, volumes) return x, root @@ -1048,12 +1038,12 @@ class BatchwiseMaterialRefuel(BatchwiseMaterial): represents a nuclide and its weight fraction, respectively. """ - def __init__(self, material, attrib_name, operator, model, mat_vector, bracket, + def __init__(self, material, attrib_name, operator, mat_vector, bracket, bracket_limit, density_treatment='constant-volume', bracketed_method='brentq', tol=0.01, target=1.0, print_iterations=True, search_for_keff_output=True): - super().__init__(material, operator, model, mat_vector, bracket, + super().__init__(material, operator, mat_vector, bracket, bracket_limit, density_treatment, bracketed_method, tol, target, print_iterations, search_for_keff_output) From e8c36f6f6522d6ff351364ca5ccba315684040f4 Mon Sep 17 00:00:00 2001 From: church89 Date: Tue, 6 Feb 2024 13:53:37 +0100 Subject: [PATCH 33/59] fix some docstrings --- openmc/deplete/batchwise.py | 59 +++++++++++++++++++------------------ 1 file changed, 30 insertions(+), 29 deletions(-) diff --git a/openmc/deplete/batchwise.py b/openmc/deplete/batchwise.py index f716aa1b3dc..9e6bc746b60 100644 --- a/openmc/deplete/batchwise.py +++ b/openmc/deplete/batchwise.py @@ -32,8 +32,6 @@ class Batchwise(ABC): ---------- operator : openmc.deplete.Operator OpenMC operator object - model : openmc.model.Model - OpenMC model object bracket : list of float Bracketing interval to search for the solution, always relative to the solution at previous step. @@ -92,13 +90,9 @@ def __init__(self, operator, bracket, bracket_limit, check_less_than('bracket limit values', bracket_limit[0], bracket_limit[1]) self.bracket_limit = bracket_limit - self.bracketed_method = bracketed_method - self.tol = tol - self.target = target - self.print_iterations = print_iterations self.search_for_keff_output = search_for_keff_output @@ -179,7 +173,6 @@ def _search_for_keff(self, val): # Run until a search_for_keff root is found or out of limits root = None - while root == None: search = search_for_keff(self._model_builder, bracket = np.array(bracket) + val, @@ -376,8 +369,6 @@ class BatchwiseCell(Batchwise): OpenMC Cell identifier to where apply batch wise scheme operator : openmc.deplete.Operator OpenMC operator object - model : openmc.model.Model - OpenMC model object bracket : list of float Bracketing interval to search for the solution, always relative to the solution at previous step. @@ -407,6 +398,7 @@ class BatchwiseCell(Batchwise): search_for_keff_output : Bool, Optional Whether or not to print transport iterations during `search_for_keff`. Default to False + Attributes ---------- cell : openmc.Cell or int or str @@ -414,9 +406,12 @@ class BatchwiseCell(Batchwise): cell_materials : list of openmc.Material Depletable materials that fill the Cell Universe. Only valid for translation or rotation attributes + lib_cell : openmc.lib.Cell + Corresponding openmc.lib.Cell once openmc.lib is initialized axis : int {0,1,2} Directional axis for geometrical parametrization, where 0, 1 and 2 stand for 'x', 'y' and 'z', respectively. + """ def __init__(self, cell, operator, bracket, bracket_limit, axis=None, density_treatment='constant-volume', bracketed_method='brentq', @@ -430,7 +425,7 @@ def __init__(self, cell, operator, bracket, bracket_limit, axis=None, self.cell = self._get_cell(cell) # Initialize to None, as openmc.lib is not initialized yet here - self._cell = None + self.lib_cell = None if axis is not None: #index of cell directional axis @@ -460,11 +455,11 @@ def _set_cell_attrib(self, val): cell coefficient to set """ - def _set_cell(self): + def _set_lib_cell(self): """ - Set _cell attribute to the corresponding openmc.lib.cell + Set openmc.lib.cell cell to self.lib_cell attribute """ - self._cell = [cell for cell in openmc.lib.cells.values() \ + self.lib_cell = [cell for cell in openmc.lib.cells.values() \ if cell.id == self.cell.id][0] def _get_cell(self, val): @@ -541,8 +536,8 @@ def search_for_keff(self, x, step_index): Search_for_keff returned root value """ # set _cell argument, once openmc.lib is intialized - if self._cell is None: - self._set_cell() + if self.lib_cell is None: + self._set_lib_cell() # Get cell attribute from previous iteration val = self._get_cell_attrib() @@ -588,8 +583,6 @@ class BatchwiseCellGeometrical(BatchwiseCell): Cell attribute type operator : openmc.deplete.Operator OpenMC operator object - model : openmc.model.Model - OpenMC model object axis : int {0,1,2} Directional axis for geometrical parametrization, where 0, 1 and 2 stand for 'x', 'y' and 'z', respectively. @@ -622,18 +615,22 @@ class BatchwiseCellGeometrical(BatchwiseCell): search_for_keff_output : Bool, Optional Whether or not to print transport iterations during `search_for_keff`. Default to False + Attributes ---------- cell : openmc.Cell or int or str OpenMC Cell identifier to where apply batch wise scheme attrib_name : str {'translation', 'rotation'} Cell attribute type + vector : numpy.ndarray + Array storing vector of translation or rotation axis : int {0,1,2} Directional axis for geometrical parametrization, where 0, 1 and 2 stand for 'x', 'y' and 'z', respectively. samples : int Number of samples used to generate volume estimates for stochastic volume calculations. + """ def __init__(self, cell, attrib_name, operator, bracket, bracket_limit, axis, density_treatment='constant-volume', @@ -672,9 +669,9 @@ def _get_cell_attrib(self): cell coefficient """ if self.attrib_name == 'translation': - return self._cell.translation[self.axis] + return self.lib_cell.translation[self.axis] elif self.attrib_name == 'rotation': - return self._cell.rotation[self.axis] + return self.lib_cell.rotation[self.axis] def _set_cell_attrib(self, val): """ @@ -687,7 +684,7 @@ def _set_cell_attrib(self, val): Cell coefficient to set, in cm for translation and deg for rotation """ self.vector[self.axis] = val - setattr(self._cell, self.attrib_name, self.vector) + setattr(self.lib_cell, self.attrib_name, self.vector) def _initialize_volume_calc(self): """ @@ -735,8 +732,6 @@ class BatchwiseCellTemperature(BatchwiseCell): OpenMC Cell identifier to where apply batch wise scheme operator : openmc.deplete.Operator OpenMC operator object - model : openmc.model.Model - OpenMC model object attrib_name : str Cell attribute type bracket : list of float @@ -768,10 +763,14 @@ class BatchwiseCellTemperature(BatchwiseCell): search_for_keff_output : Bool, Optional Whether or not to print transport iterations during `search_for_keff`. Default to False + Attributes ---------- cell : openmc.Cell or int or str OpenMC Cell identifier to where apply batch wise scheme + attrib_name : str {'temperature'} + Cell attribute type + """ def __init__(self, cell, attrib_name, operator, bracket, bracket_limit, axis=None, density_treatment='constant-volume', @@ -803,7 +802,7 @@ def _get_cell_attrib(self): coeff : float cell temperature, in Kelvin """ - return self._cell.get_temperature() + return self.lib_cell.get_temperature() def _set_cell_attrib(self, val): """ @@ -813,7 +812,7 @@ def _set_cell_attrib(self, val): val : float Cell temperature to set, in Kelvin """ - self._cell.set_temperature(val) + self.lib_cell.set_temperature(val) class BatchwiseMaterial(Batchwise): """Abstract class holding batch wise material-based functions. @@ -829,8 +828,6 @@ class BatchwiseMaterial(Batchwise): OpenMC Material identifier to where apply batch wise scheme operator : openmc.deplete.Operator OpenMC operator object - model : openmc.model.Model - OpenMC model object mat_vector : dict Dictionary of material composition to parameterize, where a pair key value represents a nuclide and its weight fraction, respectively. @@ -863,6 +860,7 @@ class BatchwiseMaterial(Batchwise): search_for_keff_output : Bool, Optional Whether or not to print transport iterations during `search_for_keff`. Default to False + Attributes ---------- material : openmc.Material or int or str @@ -870,6 +868,7 @@ class BatchwiseMaterial(Batchwise): mat_vector : dict Dictionary of material composition to parameterize, where a pair key value represents a nuclide and its weight fraction, respectively. + """ def __init__(self, material, operator, mat_vector, bracket, @@ -994,9 +993,7 @@ class BatchwiseMaterialRefuel(BatchwiseMaterial): operator : openmc.deplete.Operator OpenMC operator object attrib_name : str - Material attribute - model : openmc.model.Model - OpenMC model object + Material attribute name mat_vector : dict Dictionary of material composition to parameterize, where a pair key value represents a nuclide and its weight fraction, respectively. @@ -1029,6 +1026,7 @@ class BatchwiseMaterialRefuel(BatchwiseMaterial): search_for_keff_output : Bool, Optional Whether or not to print transport iterations during `search_for_keff`. Default to False + Attributes ---------- material : openmc.Material or int or str @@ -1036,6 +1034,9 @@ class BatchwiseMaterialRefuel(BatchwiseMaterial): mat_vector : dict Dictionary of material composition to parameterize, where a pair key value represents a nuclide and its weight fraction, respectively. + attrib_name : str + Material attribute name + """ def __init__(self, material, attrib_name, operator, mat_vector, bracket, From e4c0ad251ba785db7dbf87a64e66dbf303ba8201 Mon Sep 17 00:00:00 2001 From: church89 Date: Tue, 6 Feb 2024 14:11:10 +0100 Subject: [PATCH 34/59] reformatted with black --- openmc/deplete/batchwise.py | 543 +++++++++++++++++++++++------------- 1 file changed, 349 insertions(+), 194 deletions(-) diff --git a/openmc/deplete/batchwise.py b/openmc/deplete/batchwise.py index 9e6bc746b60..f0e21bc2258 100644 --- a/openmc/deplete/batchwise.py +++ b/openmc/deplete/batchwise.py @@ -9,8 +9,14 @@ from openmc.search import _SCALAR_BRACKETED_METHODS, search_for_keff from openmc import Material, Cell from openmc.data import atomic_mass, AVOGADRO -from openmc.checkvalue import (check_type, check_value, check_less_than, - check_iterable_type, check_length) +from openmc.checkvalue import ( + check_type, + check_value, + check_less_than, + check_iterable_type, + check_length, +) + class Batchwise(ABC): """Abstract class defining a generalized batch wise scheme. @@ -69,10 +75,19 @@ class Batchwise(ABC): All burnable material IDs being managed by a single process List of burnable materials ids """ - def __init__(self, operator, bracket, bracket_limit, - density_treatment = 'constant-volume', bracketed_method='brentq', - tol=0.01, target=1.0, print_iterations=True, - search_for_keff_output=True): + + def __init__( + self, + operator, + bracket, + bracket_limit, + density_treatment="constant-volume", + bracketed_method="brentq", + tol=0.01, + target=1.0, + print_iterations=True, + search_for_keff_output=True, + ): self.operator = operator self.burn_mats = operator.burnable_mats @@ -80,15 +95,17 @@ def __init__(self, operator, bracket, bracket_limit, self.model = operator.model self.geometry = operator.model.geometry - check_value('density_treatment', density_treatment, - ('constant-density','constant-volume')) + check_value( + "density_treatment", + density_treatment, + ("constant-density", "constant-volume"), + ) self.density_treatment = density_treatment self.bracket = bracket - check_iterable_type('bracket_limit', bracket_limit, Real) - check_length('bracket_limit', bracket_limit, 2) - check_less_than('bracket limit values', - bracket_limit[0], bracket_limit[1]) + check_iterable_type("bracket_limit", bracket_limit, Real) + check_length("bracket_limit", bracket_limit, 2) + check_less_than("bracket limit values", bracket_limit[0], bracket_limit[1]) self.bracket_limit = bracket_limit self.bracketed_method = bracketed_method self.tol = tol @@ -102,9 +119,9 @@ def bracketed_method(self): @bracketed_method.setter def bracketed_method(self, value): - check_value('bracketed_method', value, _SCALAR_BRACKETED_METHODS) - if value != 'brentq': - warn('WARNING: brentq bracketed method is recommended') + check_value("bracketed_method", value, _SCALAR_BRACKETED_METHODS) + if value != "brentq": + warn("WARNING: brentq bracketed method is recommended") self._bracketed_method = value @property @@ -131,15 +148,16 @@ def from_params(cls, obj, attr, operator, **kwargs): @abstractmethod def _model_builder(self, param): - """ - Builds the parametric model to be passed to the + """Builds the parametric model to be passed to the :meth:`openmc.search.search_for_keff` method. Callable function which builds a model according to a passed parameter. This function must return an openmc.model.Model object. + Parameters ---------- param : parameter model function variable + Returns ------- _model : openmc.model.Model @@ -147,8 +165,7 @@ def _model_builder(self, param): """ def _search_for_keff(self, val): - """ - Perform the criticality search for a given parametric model. + """Perform the criticality search for a given parametric model. If the solution lies off the initial bracket, this method iteratively adapt it until :meth:`openmc.search.search_for_keff` return a valid solution. @@ -158,56 +175,67 @@ def _search_for_keff(self, val): A bracket limit pose the upper and lower boundaries to the adapting bracket. If one limit is hit, the algorithm will stop and the closest limit value will be used. + Parameters ---------- val : float Previous result value + Returns ------- root : float Estimated value of the variable parameter where keff is the targeted value """ - #make sure we don't modify original bracket and tol values + # make sure we don't modify original bracket and tol values bracket = deepcopy(self.bracket) # Run until a search_for_keff root is found or out of limits root = None while root == None: - search = search_for_keff(self._model_builder, - bracket = np.array(bracket) + val, - tol = self.tol, - bracketed_method = self.bracketed_method, - target = self.target, - print_iterations = self.print_iterations, - run_args = {'output': self.search_for_keff_output}, - run_in_memory = True) + search = search_for_keff( + self._model_builder, + bracket=np.array(bracket) + val, + tol=self.tol, + bracketed_method=self.bracketed_method, + target=self.target, + print_iterations=self.print_iterations, + run_args={"output": self.search_for_keff_output}, + run_in_memory=True, + ) # if len(search) is 3 search_for_keff was successful if len(search) == 3: - res,_,_ = search + res, _, _ = search - #Check if root is within bracket limits - if self.bracket_limit[0] < res < self.bracket_limit[1]: + # Check if root is within bracket limits + if self.bracket_limit[0] < res < self.bracket_limit[1]: root = res else: # Set res with the closest limit and continue arg_min = abs(np.array(self.bracket_limit) - res).argmin() - warn("WARNING: Search_for_keff returned root out of " - "bracket limit. Set root to {:.2f} and continue." - .format(self.bracket_limit[arg_min])) + warn( + "WARNING: Search_for_keff returned root out of " + "bracket limit. Set root to {:.2f} and continue.".format( + self.bracket_limit[arg_min] + ) + ) root = self.bracket_limit[arg_min] elif len(search) == 2: guesses, keffs = search - #Check if all guesses are within bracket limits - if all(self.bracket_limit[0] < guess < self.bracket_limit[1] \ - for guess in guesses): - #Simple method to iteratively adapt the bracket - print("Search_for_keff returned values below or above " - "target. Trying to iteratively adapt bracket...") + # Check if all guesses are within bracket limits + if all( + self.bracket_limit[0] < guess < self.bracket_limit[1] + for guess in guesses + ): + # Simple method to iteratively adapt the bracket + print( + "Search_for_keff returned values below or above " + "target. Trying to iteratively adapt bracket..." + ) # if the bracket ends up being smaller than the std of the # keff's closer value to target, no need to continue- @@ -227,33 +255,37 @@ def _search_for_keff(self, val): else: dir = -1 bracket[np.argmin(keffs)] = bracket[np.argmax(keffs)] - bracket[np.argmax(keffs)] += grad * (self.target - \ - max(keffs).n) * dir + bracket[np.argmax(keffs)] += ( + grad * (self.target - max(keffs).n) * dir + ) else: if guesses[np.argmax(keffs)] > guesses[np.argmin(keffs)]: dir = -1 else: dir = 1 bracket[np.argmax(keffs)] = bracket[np.argmin(keffs)] - bracket[np.argmin(keffs)] += grad * (min(keffs).n - \ - self.target) * dir + bracket[np.argmin(keffs)] += ( + grad * (min(keffs).n - self.target) * dir + ) else: # Set res with closest limit and continue arg_min = abs(np.array(self.bracket_limit) - guesses).argmin() - warn("WARNING: Adaptive iterative bracket went off " - "bracket limits. Set root to {:.2f} and continue." - .format(self.bracket_limit[arg_min])) + warn( + "WARNING: Adaptive iterative bracket went off " + "bracket limits. Set root to {:.2f} and continue.".format( + self.bracket_limit[arg_min] + ) + ) root = self.bracket_limit[arg_min] else: - raise ValueError('ERROR: Search_for_keff output is not valid') + raise ValueError("ERROR: Search_for_keff output is not valid") return root def _update_volumes(self): - """ - Update volumes stored in AtomNumber. + """Update volumes stored in AtomNumber. After a depletion step, both material volume and density change, due to changes in nuclides composition. At present we lack an implementation to calculate density and volume @@ -268,19 +300,19 @@ def _update_volumes(self): # Total number of atoms-gram per mol agpm = 0 for nuc in number_i.nuclides: - agpm += number_i[mat, nuc] * atomic_mass(nuc) + agpm += number_i[mat, nuc] * atomic_mass(nuc) # Get mass dens from beginning, intended to be held constant - density = openmc.lib.materials[int(mat)].get_density('g/cm3') + density = openmc.lib.materials[int(mat)].get_density("g/cm3") number_i.volume[mat_idx] = agpm / AVOGADRO / density def _update_materials(self, x): - """ - Update number density and material compositions in OpenMC on all processes. + """Update number density and material compositions in OpenMC on all processes. If density_treatment is set to 'constant-density' :meth:`openmc.deplete.batchwise._update_volumes` is called to update material volumes in AtomNumber, keeping the material total density constant, before re-normalizing the atom densities and assigning them to the model in memory. + Parameters ---------- x : list of numpy.ndarray @@ -288,7 +320,7 @@ def _update_materials(self, x): """ self.operator.number.set_density(x) - if self.density_treatment == 'constant-density': + if self.density_treatment == "constant-density": self._update_volumes() for rank in range(comm.size): @@ -310,8 +342,8 @@ def _update_materials(self, x): openmc.lib.materials[int(mat)].set_densities(nuclides, densities) def _update_x_and_set_volumes(self, x, volumes): - """ - Update x vector with new volumes, before assign them to AtomNumber. + """Update x vector with new volumes, before assign them to AtomNumber. + Parameters ---------- x : list of numpy.ndarray @@ -319,6 +351,7 @@ def _update_x_and_set_volumes(self, x, volumes): volumes : dict Updated volumes, where key is material id and value material volume, in cm3 + Returns ------- x : list of numpy.ndarray @@ -334,12 +367,13 @@ def _update_x_and_set_volumes(self, x, volumes): for nuc_idx, nuc in enumerate(number_i.burnable_nuclides): if nuc not in self.operator.nuclides_with_data: # normalize with new volume - x[mat_idx][nuc_idx] *= number_i.get_mat_volume(mat) / \ - res_vol + x[mat_idx][nuc_idx] *= number_i.get_mat_volume(mat) / res_vol # update all concentration data with the new updated volumes - for nuc, dens in zip(openmc.lib.materials[int(mat)].nuclides, - openmc.lib.materials[int(mat)].densities): + for nuc, dens in zip( + openmc.lib.materials[int(mat)].nuclides, + openmc.lib.materials[int(mat)].densities, + ): nuc_idx = number_i.index_nuc[nuc] n_of_atoms = dens / 1.0e-24 * res_vol @@ -348,13 +382,14 @@ def _update_x_and_set_volumes(self, x, volumes): x[mat_idx][nuc_idx] = n_of_atoms # when the nuclide is not in depletion chain update the AtomNumber else: - #Atom density needs to be in [#atoms/cm3] + # Atom density needs to be in [#atoms/cm3] number_i[mat, nuc] = n_of_atoms - #Now we can update the new volume in AtomNumber + # Now we can update the new volume in AtomNumber number_i.volume[mat_idx] = res_vol return x + class BatchwiseCell(Batchwise): """Abstract class holding batch wise cell-based functions. @@ -413,14 +448,33 @@ class BatchwiseCell(Batchwise): for 'x', 'y' and 'z', respectively. """ - def __init__(self, cell, operator, bracket, bracket_limit, axis=None, - density_treatment='constant-volume', bracketed_method='brentq', - tol=0.01, target=1.0, print_iterations=True, - search_for_keff_output=True): - super().__init__(operator, bracket, bracket_limit, density_treatment, - bracketed_method, tol, target, print_iterations, - search_for_keff_output) + def __init__( + self, + cell, + operator, + bracket, + bracket_limit, + axis=None, + density_treatment="constant-volume", + bracketed_method="brentq", + tol=0.01, + target=1.0, + print_iterations=True, + search_for_keff_output=True, + ): + + super().__init__( + operator, + bracket, + bracket_limit, + density_treatment, + bracketed_method, + tol, + target, + print_iterations, + search_for_keff_output, + ) self.cell = self._get_cell(cell) @@ -428,8 +482,8 @@ def __init__(self, cell, operator, bracket, bracket_limit, axis=None, self.lib_cell = None if axis is not None: - #index of cell directional axis - check_value('axis', axis, (0,1,2)) + # index of cell directional axis + check_value("axis", axis, (0, 1, 2)) self.axis = axis # list of material fill the attribute cell, if depletables @@ -437,8 +491,8 @@ def __init__(self, cell, operator, bracket, bracket_limit, axis=None, @abstractmethod def _get_cell_attrib(self): - """ - Get cell attribute coefficient. + """Get cell attribute coefficient. + Returns ------- coeff : float @@ -447,8 +501,8 @@ def _get_cell_attrib(self): @abstractmethod def _set_cell_attrib(self, val): - """ - Set cell attribute to the cell instance. + """Set cell attribute to the cell instance. + Parameters ---------- val : float @@ -456,55 +510,59 @@ def _set_cell_attrib(self, val): """ def _set_lib_cell(self): - """ - Set openmc.lib.cell cell to self.lib_cell attribute - """ - self.lib_cell = [cell for cell in openmc.lib.cells.values() \ - if cell.id == self.cell.id][0] + """Set openmc.lib.cell cell to self.lib_cell attribute""" + self.lib_cell = [ + cell for cell in openmc.lib.cells.values() if cell.id == self.cell.id + ][0] def _get_cell(self, val): """Helper method for getting cell from cell instance or cell name or id. + Parameters ---------- val : Openmc.Cell or str or int representing Cell + Returns ------- val : openmc.Cell Openmc Cell """ - all_cells = [(cell.id, cell.name, cell) for cell in \ - self.geometry.get_all_cells().values()] + all_cells = [ + (cell.id, cell.name, cell) + for cell in self.geometry.get_all_cells().values() + ] if isinstance(val, Cell): - check_value('Cell exists', val, [cell[2] for cell in all_cells]) + check_value("Cell exists", val, [cell[2] for cell in all_cells]) elif isinstance(val, str): if val.isnumeric(): - check_value('Cell id exists', int(val), [cell[0] for cell in all_cells]) + check_value("Cell id exists", int(val), [cell[0] for cell in all_cells]) val = [cell[2] for cell in all_cells if cell[0] == int(val)][0] else: - check_value('Cell name exists', val, [cell[1] for cell in all_cells]) + check_value("Cell name exists", val, [cell[1] for cell in all_cells]) val = [cell[2] for cell in all_cells if cell[1] == val][0] elif isinstance(val, int): - check_value('Cell id exists', val, [cell[0] for cell in all_cells]) + check_value("Cell id exists", val, [cell[0] for cell in all_cells]) val = [cell[2] for cell in all_cells if cell[0] == val][0] else: - ValueError(f'Cell: {val} is not supported') + ValueError(f"Cell: {val} is not supported") return val def _model_builder(self, param): - """ - Builds the parametric model to be passed to the + """Builds the parametric model to be passed to the :meth:`openmc.search.search_for_keff` method. Callable function which builds a model according to a passed parameter. This function must return an openmc.model.Model object. + Parameters ---------- param : model parametric variable cell translation coefficient or cell rotation coefficient + Returns ------- self.model : openmc.model.Model @@ -519,15 +577,16 @@ def _model_builder(self, param): return self.model def search_for_keff(self, x, step_index): - """ - Perform the criticality search on the parametric cell coefficient and + """Perform the criticality search on the parametric cell coefficient and update materials accordingly. The :meth:`openmc.search.search_for_keff` solution is then set as the new cell attribute. + Parameters ---------- x : list of numpy.ndarray Total atoms concentrations + Returns ------- x : list of numpy.ndarray @@ -541,10 +600,10 @@ def search_for_keff(self, x, step_index): # Get cell attribute from previous iteration val = self._get_cell_attrib() - check_type('Cell coeff', val, Real) + check_type("Cell coeff", val, Real) # Update all material densities from concentration vectors - #before performing the search_for_keff. This is needed before running + # before performing the search_for_keff. This is needed before running # the transport equations in the search_for_keff algorithm self._update_materials(x) @@ -565,8 +624,7 @@ def search_for_keff(self, x, step_index): class BatchwiseCellGeometrical(BatchwiseCell): - """ - Batch wise cell-based with geometrical-attribute class. + """Batch wise cell-based with geometrical-attribute class. A user doesn't need to call this class directly. Instead an instance of this class is automatically created by calling @@ -632,52 +690,75 @@ class BatchwiseCellGeometrical(BatchwiseCell): volume calculations. """ - def __init__(self, cell, attrib_name, operator, bracket, - bracket_limit, axis, density_treatment='constant-volume', - bracketed_method='brentq', tol=0.01, target=1.0, samples=1000000, - print_iterations=True, search_for_keff_output=True): - super().__init__(cell, operator, bracket, bracket_limit, axis, - density_treatment, bracketed_method, tol, target, - print_iterations, search_for_keff_output) - - check_value('attrib_name', attrib_name, - ('rotation', 'translation')) + def __init__( + self, + cell, + attrib_name, + operator, + bracket, + bracket_limit, + axis, + density_treatment="constant-volume", + bracketed_method="brentq", + tol=0.01, + target=1.0, + samples=1000000, + print_iterations=True, + search_for_keff_output=True, + ): + + super().__init__( + cell, + operator, + bracket, + bracket_limit, + axis, + density_treatment, + bracketed_method, + tol, + target, + print_iterations, + search_for_keff_output, + ) + + check_value("attrib_name", attrib_name, ("rotation", "translation")) self.attrib_name = attrib_name # check if cell is filled with 2 cells - check_length('fill materials', self.cell.fill.cells, 2) + check_length("fill materials", self.cell.fill.cells, 2) # Initialize vector self.vector = np.zeros(3) - self.cell_materials = [cell.fill for cell in \ - self.cell.fill.cells.values() if cell.fill.depletable] + self.cell_materials = [ + cell.fill for cell in self.cell.fill.cells.values() if cell.fill.depletable + ] - check_type('samples', samples, int) + check_type("samples", samples, int) self.samples = samples if self.cell_materials: self._initialize_volume_calc() def _get_cell_attrib(self): - """ - Get cell attribute coefficient. + """Get cell attribute coefficient. + Returns ------- coeff : float cell coefficient """ - if self.attrib_name == 'translation': + if self.attrib_name == "translation": return self.lib_cell.translation[self.axis] - elif self.attrib_name == 'rotation': + elif self.attrib_name == "rotation": return self.lib_cell.rotation[self.axis] def _set_cell_attrib(self, val): - """ - Set cell attribute to the cell instance. + """Set cell attribute to the cell instance. Attributes are only applied to a cell filled with a universe containing two cells itself. + Parameters ---------- val : float @@ -687,18 +768,16 @@ def _set_cell_attrib(self, val): setattr(self.lib_cell, self.attrib_name, self.vector) def _initialize_volume_calc(self): - """ - Set volume calculation model settings of depletable materials filling + """Set volume calculation model settings of depletable materials filling the parametric Cell. """ ll, ur = self.geometry.bounding_box - mat_vol = openmc.VolumeCalculation(self.cell_materials, self.samples, - ll, ur) + mat_vol = openmc.VolumeCalculation(self.cell_materials, self.samples, ll, ur) self.model.settings.volume_calculations = mat_vol def _calculate_volumes(self): - """ - Perform stochastic volume calculation + """Perform stochastic volume calculation + Returns ------- volumes : dict @@ -708,16 +787,16 @@ def _calculate_volumes(self): openmc.lib.calculate_volumes() volumes = {} if comm.rank == 0: - res = openmc.VolumeCalculation.from_hdf5('volume_1.h5') + res = openmc.VolumeCalculation.from_hdf5("volume_1.h5") for mat_idx, mat in enumerate(self.local_mats): if int(mat) in [mat.id for mat in self.cell_materials]: volumes[mat] = res.volumes[int(mat)].n volumes = comm.bcast(volumes) return volumes + class BatchwiseCellTemperature(BatchwiseCell): - """ - Batch wise cell-based with temperature-attribute class. + """Batch wise cell-based with temperature-attribute class. A user doesn't need to call this class directly. Instead an instance of this class is automatically created by calling @@ -772,31 +851,54 @@ class BatchwiseCellTemperature(BatchwiseCell): Cell attribute type """ - def __init__(self, cell, attrib_name, operator, bracket, bracket_limit, - axis=None, density_treatment='constant-volume', - bracketed_method='brentq', tol=0.01, target=1.0, - print_iterations=True, search_for_keff_output=True): - super().__init__(cell, operator, bracket, bracket_limit, axis, - density_treatment, bracketed_method, tol, target, - print_iterations, search_for_keff_output) + def __init__( + self, + cell, + attrib_name, + operator, + bracket, + bracket_limit, + axis=None, + density_treatment="constant-volume", + bracketed_method="brentq", + tol=0.01, + target=1.0, + print_iterations=True, + search_for_keff_output=True, + ): + + super().__init__( + cell, + operator, + bracket, + bracket_limit, + axis, + density_treatment, + bracketed_method, + tol, + target, + print_iterations, + search_for_keff_output, + ) # Not needed but used for consistency with other classes - check_value('attrib_name', attrib_name, 'temperature') + check_value("attrib_name", attrib_name, "temperature") self.attrib_name = attrib_name # check if initial temperature has been set to right cell material if isinstance(self.cell.fill, openmc.Universe): - cells = [cell for cell in self.cell.fill.cells.values() \ - if cell.fill.temperature] - check_length('Only one cell with temperature',cells,1) + cells = [ + cell for cell in self.cell.fill.cells.values() if cell.fill.temperature + ] + check_length("Only one cell with temperature", cells, 1) self.cell = cells[0] - check_type('temperature cell real', self.cell.fill.temperature, Real) + check_type("temperature cell real", self.cell.fill.temperature, Real) def _get_cell_attrib(self): - """ - Get cell temperature. + """Get cell temperature. + Returns ------- coeff : float @@ -805,8 +907,8 @@ def _get_cell_attrib(self): return self.lib_cell.get_temperature() def _set_cell_attrib(self, val): - """ - Set temperature value to the cell instance. + """Set temperature value to the cell instance. + Parameters ---------- val : float @@ -814,6 +916,7 @@ def _set_cell_attrib(self, val): """ self.lib_cell.set_temperature(val) + class BatchwiseMaterial(Batchwise): """Abstract class holding batch wise material-based functions. @@ -871,14 +974,32 @@ class BatchwiseMaterial(Batchwise): """ - def __init__(self, material, operator, mat_vector, bracket, - bracket_limit, density_treatment='constant-volume', - bracketed_method='brentq', tol=0.01, target=1.0, - print_iterations=True, search_for_keff_output=True): - - super().__init__(operator, bracket, bracket_limit, - density_treatment, bracketed_method, tol, target, - print_iterations, search_for_keff_output) + def __init__( + self, + material, + operator, + mat_vector, + bracket, + bracket_limit, + density_treatment="constant-volume", + bracketed_method="brentq", + tol=0.01, + target=1.0, + print_iterations=True, + search_for_keff_output=True, + ): + + super().__init__( + operator, + bracket, + bracket_limit, + density_treatment, + bracketed_method, + tol, + target, + print_iterations, + search_for_keff_output, + ) self.material = self._get_material(material) @@ -896,50 +1017,53 @@ def __init__(self, material, operator, mat_vector, bracket, def _get_material(self, val): """Helper method for getting openmc material from Material instance or material name or id. + Parameters ---------- val : Openmc.Material or str or int representing material name or id + Returns ------- val : openmc.Material Openmc Material """ if isinstance(val, Material): - check_value('Material', str(val.id), self.burn_mats) + check_value("Material", str(val.id), self.burn_mats) elif isinstance(val, str): if val.isnumeric(): - check_value('Material id', val, self.burn_mats) - val = [mat for mat in self.model.materials \ - if mat.id == int(val)][0] + check_value("Material id", val, self.burn_mats) + val = [mat for mat in self.model.materials if mat.id == int(val)][0] else: - check_value('Material name', val, - [mat.name for mat in self.model.materials if mat.depletable]) - val = [mat for mat in self.model.materials \ - if mat.name == val][0] + check_value( + "Material name", + val, + [mat.name for mat in self.model.materials if mat.depletable], + ) + val = [mat for mat in self.model.materials if mat.name == val][0] elif isinstance(val, int): - check_value('Material id', str(val), self.burn_mats) - val = [mat for mat in self.model.materials \ - if mat.id == val][0] + check_value("Material id", str(val), self.burn_mats) + val = [mat for mat in self.model.materials if mat.id == val][0] else: - ValueError(f'Material: {val} is not supported') + ValueError(f"Material: {val} is not supported") return val @abstractmethod def _model_builder(self, param): - """ - Builds the parametric model to be passed to the + """Builds the parametric model to be passed to the :meth:`openmc.search.search_for_keff` method. Callable function which builds a model according to a passed parameter. This function must return an openmc.model.Model object. + Parameters ---------- param : Model material function variable + Returns ------- _model : openmc.model.Model @@ -947,14 +1071,15 @@ def _model_builder(self, param): """ def search_for_keff(self, x, step_index): - """ - Perform the criticality search on parametric material variable. + """Perform the criticality search on parametric material variable. The :meth:`openmc.search.search_for_keff` solution is then used to calculate the new material volume and update the atoms concentrations. + Parameters ---------- x : list of numpy.ndarray Total atoms concentrations + Returns ------- x : list of numpy.ndarray @@ -969,15 +1094,15 @@ def search_for_keff(self, x, step_index): # Solve search_for_keff and find new value root = self._search_for_keff(0) - #Update concentration vector and volumes with new value + # Update concentration vector and volumes with new value volumes = self._calculate_volumes(root) x = self._update_x_and_set_volumes(x, volumes) - return x, root + return x, root + class BatchwiseMaterialRefuel(BatchwiseMaterial): - """ - Batch wise material-based class for refuelling (addition or removal) scheme. + """Batch wise material-based class for refuelling (addition or removal) scheme. A user doesn't need to call this class directly. Instead an instance of this class is automatically created by calling @@ -1039,32 +1164,54 @@ class BatchwiseMaterialRefuel(BatchwiseMaterial): """ - def __init__(self, material, attrib_name, operator, mat_vector, bracket, - bracket_limit, density_treatment='constant-volume', - bracketed_method='brentq', tol=0.01, target=1.0, - print_iterations=True, search_for_keff_output=True): - - super().__init__(material, operator, mat_vector, bracket, - bracket_limit, density_treatment, bracketed_method, tol, - target, print_iterations, search_for_keff_output) + def __init__( + self, + material, + attrib_name, + operator, + mat_vector, + bracket, + bracket_limit, + density_treatment="constant-volume", + bracketed_method="brentq", + tol=0.01, + target=1.0, + print_iterations=True, + search_for_keff_output=True, + ): + + super().__init__( + material, + operator, + mat_vector, + bracket, + bracket_limit, + density_treatment, + bracketed_method, + tol, + target, + print_iterations, + search_for_keff_output, + ) # Not needed but used for consistency with other classes - check_value('attrib_name', attrib_name, 'refuel') + check_value("attrib_name", attrib_name, "refuel") self.attrib_name = attrib_name def _model_builder(self, param): - """ - Callable function which builds a model according to a passed + """Callable function which builds a model according to a passed parameter. This function must return an openmc.model.Model object. Builds the parametric model to be passed to the :meth:`openmc.search.search_for_keff` method. The parametrization can either be at constant volume or constant density, according to user input. Default is constant volume. + Parameters ---------- param : float Model function variable, total grams of material to add or remove + Returns ------- model : openmc.model.Model @@ -1079,11 +1226,12 @@ def _model_builder(self, param): if int(mat) == self.material.id: - if self.density_treatment == 'constant-density': - vol = number_i.get_mat_volume(mat) + \ - (param / self.material.get_mass_density()) + if self.density_treatment == "constant-density": + vol = number_i.get_mat_volume(mat) + ( + param / self.material.get_mass_density() + ) - elif self.density_treatment == 'constant-volume': + elif self.density_treatment == "constant-volume": vol = number_i.get_mat_volume(mat) for nuc in number_i.index_nuc: @@ -1091,9 +1239,14 @@ def _model_builder(self, param): if nuc in self.operator.nuclides_with_data: if nuc in self.mat_vector: # units [#atoms/cm-b] - val = 1.0e-24 * (number_i.get_atom_density(mat, - nuc) + param / atomic_mass(nuc) * \ - AVOGADRO * self.mat_vector[nuc] / vol) + val = 1.0e-24 * ( + number_i.get_atom_density(mat, nuc) + + param + / atomic_mass(nuc) + * AVOGADRO + * self.mat_vector[nuc] + / vol + ) else: # get normalized atoms density in [atoms/b-cm] @@ -1114,21 +1267,22 @@ def _model_builder(self, param): nuclides.append(nuc) densities.append(val) - #set nuclides and densities to the in-memory model + # set nuclides and densities to the in-memory model openmc.lib.materials[int(mat)].set_densities(nuclides, densities) # always need to return a model return self.model def _calculate_volumes(self, res): - """ - Uses :meth:`openmc.batchwise._search_for_keff` solution as grams of + """Uses :meth:`openmc.batchwise._search_for_keff` solution as grams of material to add or remove to calculate new material volume. + Parameters ---------- res : float Solution in grams of material, coming from :meth:`openmc.batchwise._search_for_keff` + Returns ------- volumes : dict @@ -1140,9 +1294,10 @@ def _calculate_volumes(self, res): for mat in self.local_mats: if int(mat) == self.material.id: - if self.density_treatment == 'constant-density': - volumes[mat] = number_i.get_mat_volume(mat) + \ - (res / self.material.get_mass_density()) - elif self.density_treatment == 'constant-volume': + if self.density_treatment == "constant-density": + volumes[mat] = number_i.get_mat_volume(mat) + ( + res / self.material.get_mass_density() + ) + elif self.density_treatment == "constant-volume": volumes[mat] = number_i.get_mat_volume(mat) return volumes From f1213a4f2860426a2a7e07f2cbd12692d8aec36b Mon Sep 17 00:00:00 2001 From: church89 Date: Tue, 6 Feb 2024 16:09:22 +0100 Subject: [PATCH 35/59] refactoring of _search_for_keff method --- openmc/deplete/batchwise.py | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/openmc/deplete/batchwise.py b/openmc/deplete/batchwise.py index f0e21bc2258..643169ce039 100644 --- a/openmc/deplete/batchwise.py +++ b/openmc/deplete/batchwise.py @@ -237,10 +237,17 @@ def _search_for_keff(self, val): "target. Trying to iteratively adapt bracket..." ) - # if the bracket ends up being smaller than the std of the - # keff's closer value to target, no need to continue- - if all(keff <= max(keffs).s for keff in keffs): - arg_min = abs(self.target - np.array(guesses)).argmin() + # if the difference between keffs is smaller than 1 pcm, + # the grad calculation will be overshoot, so let's set the root + # to the closest guess value + if abs(np.diff(keffs)) < 1.0e-5: + arg_min = abs(self.target - np.array(keffs)).argmin() + print( + "Difference between keff values is " + "smaller than 1 pcm. " + "Set root to guess value with " + "closest keff to target and continue..." + ) root = guesses[arg_min] # Calculate gradient as ratio of delta bracket and delta keffs @@ -268,6 +275,12 @@ def _search_for_keff(self, val): grad * (min(keffs).n - self.target) * dir ) + #check if adapted bracket are within bracketing limits + if max(bracket) + val > self.bracket_limit[1]: + bracket[np.argmax(bracket)] = self.bracket_limit[1] - val + if min(bracket) + val < self.bracket_limit[0]: + bracket[np.argmin(bracket)] = self.bracket_limit[0] - val + else: # Set res with closest limit and continue arg_min = abs(np.array(self.bracket_limit) - guesses).argmin() From 12a988d606d2afcba1df1971889ea634fbd66cad Mon Sep 17 00:00:00 2001 From: church89 Date: Tue, 6 Feb 2024 16:39:36 +0100 Subject: [PATCH 36/59] fix some spelling typos --- openmc/deplete/batchwise.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openmc/deplete/batchwise.py b/openmc/deplete/batchwise.py index 643169ce039..672f76eaa50 100644 --- a/openmc/deplete/batchwise.py +++ b/openmc/deplete/batchwise.py @@ -21,13 +21,13 @@ class Batchwise(ABC): """Abstract class defining a generalized batch wise scheme. - Batchwise schemes, such as control rod adjustment or material refueling to + Batchwise schemes, such as control rod adjustment or material refuelling to control reactivity and maintain keff constant and equal to one. A batch wise scheme can be added here to an integrator instance, such as :class:`openmc.deplete.CECMIntegrator`, to parametrize one system variable with the aim of satisfy certain design criteria, such as keeping - keff equal to one, while running transport-depletion caculations. + keff equal to one, while running transport-depletion calculations. Specific classes for running batch wise depletion calculations are implemented as derived class of Batchwise. @@ -607,7 +607,7 @@ def search_for_keff(self, x, step_index): root : float Search_for_keff returned root value """ - # set _cell argument, once openmc.lib is intialized + # set _cell argument, once openmc.lib is initialized if self.lib_cell is None: self._set_lib_cell() From ef4fe68125e455da8cda2ddb90a1c47e4eb48018 Mon Sep 17 00:00:00 2001 From: church89 Date: Wed, 7 Feb 2024 13:49:50 +0100 Subject: [PATCH 37/59] missing underscore --- openmc/deplete/abc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py index c9d0f3918c6..580692cfe14 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -18,7 +18,7 @@ from numpy import nonzero, empty, asarray from uncertainties import ufloat -from openmc.checkvalue import checkvalue, check_type, check_greater_than, PathLike +from openmc.checkvalue import check_value, check_type, check_greater_than, PathLike from openmc.mpi import comm from .stepresult import StepResult from .chain import Chain From 1aad455c3b1112cc99a2782c049997d9e8bc55db Mon Sep 17 00:00:00 2001 From: church89 Date: Mon, 12 Feb 2024 11:02:46 +0100 Subject: [PATCH 38/59] add batchwise get method and update unit test --- openmc/deplete/abc.py | 4 +++ tests/unit_tests/test_deplete_batchwise.py | 32 +++++++++++----------- 2 files changed, 20 insertions(+), 16 deletions(-) diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py index 580692cfe14..068abc4adf2 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -690,6 +690,10 @@ def solver(self, func): self._solver = func + @property + def get_batchwise(self): + return self._batchwise + def _timed_deplete(self, n, rates, dt, matrix_func=None): start = time.time() results = deplete( diff --git a/tests/unit_tests/test_deplete_batchwise.py b/tests/unit_tests/test_deplete_batchwise.py index 98ae9a01ff3..37c6b4002ea 100644 --- a/tests/unit_tests/test_deplete_batchwise.py +++ b/tests/unit_tests/test_deplete_batchwise.py @@ -108,19 +108,19 @@ def test_attributes(case_name, model, obj, attribute, bracket, limit, axis, vec) else: int.add_batchwise(obj, attribute, **kwargs) if attribute in ('translation','rotation'): - assert int.batchwise.cell_materials == [cell.fill for cell in \ + assert int.get_batchwise.cell_materials == [cell.fill for cell in \ model.geometry.get_cells_by_name(obj)[0].fill.cells.values() \ if cell.fill.depletable] - assert int.batchwise.axis == axis + assert int.get_batchwise.axis == axis elif attribute == 'refuel': - assert int.batchwise.mat_vector == vec + assert int.get_batchwise.mat_vector == vec - assert int.batchwise.attrib_name == attribute - assert int.batchwise.bracket == bracket - assert int.batchwise.bracket_limit == limit - assert int.batchwise.burn_mats == op.burnable_mats - assert int.batchwise.local_mats == op.local_mats + assert int.get_batchwise.attrib_name == attribute + assert int.get_batchwise.bracket == bracket + assert int.get_batchwise.bracket_limit == limit + assert int.get_batchwise.burn_mats == op.burnable_mats + assert int.get_batchwise.local_mats == op.local_mats @pytest.mark.parametrize("obj, attribute, value_to_set", [ ('universe_cell', 'translation', 0), @@ -139,11 +139,11 @@ def test_cell_methods(run_in_tmpdir, model, obj, attribute, value_to_set): model.export_to_xml() openmc.lib.init() - integrator.batchwise._set_cell_attrib(value_to_set) - assert integrator.batchwise._get_cell_attrib() == value_to_set + integrator.get_batchwise._set_cell_attrib(value_to_set) + assert integrator.get_batchwise._get_cell_attrib() == value_to_set - vol = integrator.batchwise._calculate_volumes() - for cell in integrator.batchwise.cell_materials: + vol = integrator.get_batchwise._calculate_volumes() + for cell in integrator.get_batchwise.cell_materials: assert vol[str(cell.id)] == pytest.approx([ mat.volume for mat in model.materials \ if mat.id == cell.id][0], rel=tolerance) @@ -173,12 +173,12 @@ def test_internal_methods(run_in_tmpdir, model, nuclide, atoms_to_add): #Increase number of atoms of U238 in fuel by fix amount and check the # volume increase at constant-density #extract fuel material from model materials - mat = integrator.batchwise.material + mat = integrator.get_batchwise.material mat_index = op.number.index_mat[str(mat.id)] nuc_index = op.number.index_nuc[nuclide] vol = op.number.get_mat_volume(str(mat.id)) op.number.number[mat_index][nuc_index] += atoms_to_add - integrator.batchwise._update_volumes() + integrator.get_batchwise._update_volumes() vol_to_compare = vol + (atoms_to_add * openmc.data.atomic_mass(nuclide) /\ openmc.data.AVOGADRO / mat.density) @@ -186,14 +186,14 @@ def test_internal_methods(run_in_tmpdir, model, nuclide, atoms_to_add): assert op.number.get_mat_volume(str(mat.id)) == pytest.approx(vol_to_compare) x = [i[:op.number.n_nuc_burn] for i in op.number.number] - integrator.batchwise._update_materials(x) + integrator.get_batchwise._update_materials(x) nuc_index_lib = openmc.lib.materials[mat.id].nuclides.index(nuclide) dens_to_compare = 1.0e-24 * op.number.get_atom_density(str(mat.id), nuclide) assert openmc.lib.materials[mat.id].densities[nuc_index_lib] == pytest.approx(dens_to_compare) volumes = {str(mat.id): vol + 1} - new_x = integrator.batchwise._update_x_and_set_volumes(x, volumes) + new_x = integrator.get_batchwise._update_x_and_set_volumes(x, volumes) dens_to_compare = 1.0e24 * volumes[str(mat.id)] *\ openmc.lib.materials[mat.id].densities[nuc_index_lib] assert new_x[mat_index][nuc_index] == pytest.approx(dens_to_compare) From f3dfdf8a45a84f876f38fc48c4fc0e7194280c3f Mon Sep 17 00:00:00 2001 From: Lorenzo Chierici Date: Thu, 14 Mar 2024 10:36:27 +0100 Subject: [PATCH 39/59] Apply suggestions from code review Address doc string comments Co-authored-by: Andrew Johnson --- openmc/deplete/abc.py | 3 +-- openmc/deplete/batchwise.py | 22 ++++++++++++++-------- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py index 068abc4adf2..852e667e8eb 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -774,8 +774,7 @@ def _get_start_data(self): len(self.operator.prev_res) - 1) def _get_bos_from_batchwise(self, step_index, bos_conc): - """Get BOS from criticality batch-wise control - """ + """Get BOS from criticality batch-wise control.""" x = deepcopy(bos_conc) # Get new vector after keff criticality control x, root = self._batchwise.search_for_keff(x, step_index) diff --git a/openmc/deplete/batchwise.py b/openmc/deplete/batchwise.py index 672f76eaa50..6d9fa5103dd 100644 --- a/openmc/deplete/batchwise.py +++ b/openmc/deplete/batchwise.py @@ -22,7 +22,8 @@ class Batchwise(ABC): """Abstract class defining a generalized batch wise scheme. Batchwise schemes, such as control rod adjustment or material refuelling to - control reactivity and maintain keff constant and equal to one. + control reactivity and maintain keff constant and equal to a desired value, + usually one. A batch wise scheme can be added here to an integrator instance, such as :class:`openmc.deplete.CECMIntegrator`, to parametrize one system @@ -32,7 +33,7 @@ class Batchwise(ABC): Specific classes for running batch wise depletion calculations are implemented as derived class of Batchwise. - .. versionadded:: 0.13.4 + .. versionadded:: 0.14.1 Parameters ---------- @@ -45,7 +46,7 @@ class Batchwise(ABC): Absolute bracketing interval lower and upper; if during the adaptive algorithm the search_for_keff solution lies off these limits the closest limit will be set as new result. - density_treatment : str + density_treatment : str, optional Whether or not to keep constant volume or density after a depletion step before the next one. Default to 'constant-volume' @@ -148,8 +149,8 @@ def from_params(cls, obj, attr, operator, **kwargs): @abstractmethod def _model_builder(self, param): - """Builds the parametric model to be passed to the - :meth:`openmc.search.search_for_keff` method. + """Build the parametric model to be solved. + Callable function which builds a model according to a passed parameter. This function must return an openmc.model.Model object. @@ -160,12 +161,13 @@ def _model_builder(self, param): Returns ------- - _model : openmc.model.Model + openmc.model.Model OpenMC parametric model """ def _search_for_keff(self, val): """Perform the criticality search for a given parametric model. + If the solution lies off the initial bracket, this method iteratively adapt it until :meth:`openmc.search.search_for_keff` return a valid solution. @@ -187,7 +189,7 @@ def _search_for_keff(self, val): Estimated value of the variable parameter where keff is the targeted value """ - # make sure we don't modify original bracket and tol values + # make sure we don't modify original bracket bracket = deepcopy(self.bracket) # Run until a search_for_keff root is found or out of limits @@ -299,6 +301,7 @@ def _search_for_keff(self, val): def _update_volumes(self): """Update volumes stored in AtomNumber. + After a depletion step, both material volume and density change, due to changes in nuclides composition. At present we lack an implementation to calculate density and volume @@ -320,6 +323,7 @@ def _update_volumes(self): def _update_materials(self, x): """Update number density and material compositions in OpenMC on all processes. + If density_treatment is set to 'constant-density' :meth:`openmc.deplete.batchwise._update_volumes` is called to update material volumes in AtomNumber, keeping the material total density @@ -769,6 +773,7 @@ def _get_cell_attrib(self): def _set_cell_attrib(self, val): """Set cell attribute to the cell instance. + Attributes are only applied to a cell filled with a universe containing two cells itself. @@ -1017,7 +1022,7 @@ def __init__( self.material = self._get_material(material) check_type("material vector", mat_vector, dict, str) - for nuc in mat_vector.keys(): + for nuc in mat_vector: check_value("check nuclide exists", nuc, self.operator.nuclides_with_data) if round(sum(mat_vector.values()), 2) != 1.0: @@ -1085,6 +1090,7 @@ def _model_builder(self, param): def search_for_keff(self, x, step_index): """Perform the criticality search on parametric material variable. + The :meth:`openmc.search.search_for_keff` solution is then used to calculate the new material volume and update the atoms concentrations. From 9d6c5be32d06d31c8e89a07ac95df0a92fced199 Mon Sep 17 00:00:00 2001 From: church89 Date: Thu, 14 Mar 2024 17:30:58 +0100 Subject: [PATCH 40/59] address comments by @drewejohnson --- openmc/deplete/abc.py | 2 +- openmc/deplete/batchwise.py | 168 ++++++++++++++++++++---------------- 2 files changed, 96 insertions(+), 74 deletions(-) diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py index 852e667e8eb..c73f397e5ae 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -691,7 +691,7 @@ def solver(self, func): self._solver = func @property - def get_batchwise(self): + def batchwise(self): return self._batchwise def _timed_deplete(self, n, rates, dt, matrix_func=None): diff --git a/openmc/deplete/batchwise.py b/openmc/deplete/batchwise.py index 6d9fa5103dd..285550874dd 100644 --- a/openmc/deplete/batchwise.py +++ b/openmc/deplete/batchwise.py @@ -3,6 +3,7 @@ from numbers import Real from warnings import warn import numpy as np +from math import isclose import openmc.lib from openmc.mpi import comm @@ -40,7 +41,7 @@ class Batchwise(ABC): operator : openmc.deplete.Operator OpenMC operator object bracket : list of float - Bracketing interval to search for the solution, always relative to the + Initial bracketing interval to search for the solution, relative to the solution at previous step. bracket_limit : list of float Absolute bracketing interval lower and upper; if during the adaptive @@ -68,6 +69,7 @@ class Batchwise(ABC): search_for_keff_output : Bool, Optional Print full transport logs during iterations (e.g., inactive generations, active generations). Default to False + Attributes ---------- burn_mats : list of str @@ -75,6 +77,7 @@ class Batchwise(ABC): local_mats : list of str All burnable material IDs being managed by a single process List of burnable materials ids + """ def __init__( @@ -165,15 +168,34 @@ def _model_builder(self, param): OpenMC parametric model """ + @abstractmethod + def search_for_keff(self, x, step_index): + """Perform the criticality search on parametric material variable. + + The :meth:`openmc.search.search_for_keff` solution is then used to + calculate the new material volume and update the atoms concentrations. + + Parameters + ---------- + x : list of numpy.ndarray + Total atoms concentrations + + Returns + ------- + x : list of numpy.ndarray + Updated total atoms concentrations + root : float + Search_for_keff returned root value + """ + def _search_for_keff(self, val): """Perform the criticality search for a given parametric model. If the solution lies off the initial bracket, this method iteratively adapt it until :meth:`openmc.search.search_for_keff` return a valid solution. - The adapting bracket algorithm takes the ratio between the bracket and - the returned keffs values to scale the bracket until a valid solution - is found. + It calculates the ratio between guessed and corresponding keffs values + as the proportional term to move the bracket towards the target. A bracket limit pose the upper and lower boundaries to the adapting bracket. If one limit is hit, the algorithm will stop and the closest limit value will be used. @@ -190,7 +212,7 @@ def _search_for_keff(self, val): targeted value """ # make sure we don't modify original bracket - bracket = deepcopy(self.bracket) + bracket = deepcopy(self.brack½et) # Run until a search_for_keff root is found or out of limits root = None @@ -240,7 +262,7 @@ def _search_for_keff(self, val): ) # if the difference between keffs is smaller than 1 pcm, - # the grad calculation will be overshoot, so let's set the root + # the slope calculation will be overshoot, so let's set the root # to the closest guess value if abs(np.diff(keffs)) < 1.0e-5: arg_min = abs(self.target - np.array(keffs)).argmin() @@ -252,9 +274,10 @@ def _search_for_keff(self, val): ) root = guesses[arg_min] - # Calculate gradient as ratio of delta bracket and delta keffs - grad = abs(np.diff(bracket) / np.diff(keffs))[0].n - # Move the bracket closer to presumed keff root. + # Calculate slope as ratio of delta bracket and delta keffs + slope = abs(np.diff(bracket) / np.diff(keffs))[0].n + # Move the bracket closer to presumed keff root by using the + # slope # Two cases: both keffs are below or above target if np.mean(keffs) < self.target: @@ -265,7 +288,7 @@ def _search_for_keff(self, val): dir = -1 bracket[np.argmin(keffs)] = bracket[np.argmax(keffs)] bracket[np.argmax(keffs)] += ( - grad * (self.target - max(keffs).n) * dir + slope * (self.target - max(keffs).n) * dir ) else: if guesses[np.argmax(keffs)] > guesses[np.argmin(keffs)]: @@ -274,14 +297,14 @@ def _search_for_keff(self, val): dir = 1 bracket[np.argmax(keffs)] = bracket[np.argmin(keffs)] bracket[np.argmin(keffs)] += ( - grad * (min(keffs).n - self.target) * dir + slope * (min(keffs).n - self.target) * dir ) #check if adapted bracket are within bracketing limits - if max(bracket) + val > self.bracket_limit[1]: - bracket[np.argmax(bracket)] = self.bracket_limit[1] - val - if min(bracket) + val < self.bracket_limit[0]: - bracket[np.argmin(bracket)] = self.bracket_limit[0] - val + if bracket[1] + val > self.bracket_limit[1]: + bracket[1] = self.bracket_limit[1] - val + if bracket[0] + val < self.bracket_limit[0]: + bracket[0] = self.bracket_limit[0] - val else: # Set res with closest limit and continue @@ -413,7 +436,7 @@ class BatchwiseCell(Batchwise): Specific classes for running batch wise depletion calculations are implemented as derived class of BatchwiseCell. - .. versionadded:: 0.13.4 + .. versionadded:: 0.14.1 Parameters ---------- @@ -422,7 +445,7 @@ class BatchwiseCell(Batchwise): operator : openmc.deplete.Operator OpenMC operator object bracket : list of float - Bracketing interval to search for the solution, always relative to the + Initial bracketing interval to search for the solution, relative to the solution at previous step. bracket_limit : list of float Absolute bracketing interval lower and upper; if during the adaptive @@ -455,9 +478,10 @@ class BatchwiseCell(Batchwise): ---------- cell : openmc.Cell or int or str OpenMC Cell identifier to where apply batch wise scheme - cell_materials : list of openmc.Material - Depletable materials that fill the Cell Universe. Only valid for - translation or rotation attributes + universe_cells : list of openmc.Cell + Cells that fill the openmc.Universe that fills the main cell + to where apply batch wise scheme, if cell materials are set as + depletable. lib_cell : openmc.lib.Cell Corresponding openmc.lib.Cell once openmc.lib is initialized axis : int {0,1,2} @@ -503,8 +527,9 @@ def __init__( check_value("axis", axis, (0, 1, 2)) self.axis = axis - # list of material fill the attribute cell, if depletables - self.cell_materials = None + # Initialize container of universe cells, to populate only if materials + # are set as depletables + self.universe_cells = None @abstractmethod def _get_cell_attrib(self): @@ -544,26 +569,37 @@ def _get_cell(self, val): val : openmc.Cell Openmc Cell """ - all_cells = [ + cell_bundles = [ (cell.id, cell.name, cell) for cell in self.geometry.get_all_cells().values() ] if isinstance(val, Cell): - check_value("Cell exists", val, [cell[2] for cell in all_cells]) + check_value("Cell exists", val, + [cell_bundle[2] for cell_bundle in cell_bundles]) elif isinstance(val, str): if val.isnumeric(): - check_value("Cell id exists", int(val), [cell[0] for cell in all_cells]) - val = [cell[2] for cell in all_cells if cell[0] == int(val)][0] + check_value("Cell id exists", int(val), + [cell_bundle[0] for cell_bundle in cell_bundles]) + + val = [cell_bundle[2] for cell_bundle in cell_bundles \ + if cell_bundle[0] == int(val)][0] else: - check_value("Cell name exists", val, [cell[1] for cell in all_cells]) - val = [cell[2] for cell in all_cells if cell[1] == val][0] + check_value("Cell name exists", val, + [cell_bundle[1] for cell_bundle in cell_bundles]) + + val = [cell_bundle[2] for cell_bundle in cell_bundles \ + if cell_bundle[1] == val][0] elif isinstance(val, int): - check_value("Cell id exists", val, [cell[0] for cell in all_cells]) - val = [cell[2] for cell in all_cells if cell[0] == val][0] + check_value("Cell id exists", val, + [cell_bundle[0] for cell_bundle in cell_bundles]) + + val = [cell_bundle[2] for cell_bundle in cell_bundles \ + if cell_bundle[0] == val][0] + else: ValueError(f"Cell: {val} is not supported") @@ -572,6 +608,7 @@ def _get_cell(self, val): def _model_builder(self, param): """Builds the parametric model to be passed to the :meth:`openmc.search.search_for_keff` method. + Callable function which builds a model according to a passed parameter. This function must return an openmc.model.Model object. @@ -586,16 +623,13 @@ def _model_builder(self, param): Openmc parametric model """ self._set_cell_attrib(param) - # At this stage it not important to reassign the new volume, as the - # nuclide densities remain constant. However, if at least one of the cell - # materials is set as depletable, the volume change needs to be accounted for - # as an increase or reduction of number of atoms, i.e. vector x, before - # solving the depletion equations + return self.model def search_for_keff(self, x, step_index): """Perform the criticality search on the parametric cell coefficient and update materials accordingly. + The :meth:`openmc.search.search_for_keff` solution is then set as the new cell attribute. @@ -633,7 +667,7 @@ def search_for_keff(self, x, step_index): # if at least one of the cell materials is depletable, calculate new # volume and update x and number accordingly # new volume - if self.cell_materials: + if self.universe_cells: volumes = self._calculate_volumes() x = self._update_x_and_set_volumes(x, volumes) @@ -648,7 +682,7 @@ class BatchwiseCellGeometrical(BatchwiseCell): :meth:`openmc.deplete.Integrator.add_batchwise` method from an integrator class, such as :class:`openmc.deplete.CECMIntegrator`. - .. versionadded:: 0.13.4 + .. versionadded:: 0.14.1 Parameters ---------- @@ -662,7 +696,7 @@ class BatchwiseCellGeometrical(BatchwiseCell): Directional axis for geometrical parametrization, where 0, 1 and 2 stand for 'x', 'y' and 'z', respectively. bracket : list of float - Bracketing interval to search for the solution, always relative to the + Initial bracketing interval to search for the solution, relative to the solution at previous step. bracket_limit : list of float Absolute bracketing interval lower and upper; if during the adaptive @@ -742,20 +776,22 @@ def __init__( check_value("attrib_name", attrib_name, ("rotation", "translation")) self.attrib_name = attrib_name - # check if cell is filled with 2 cells - check_length("fill materials", self.cell.fill.cells, 2) + # check if cell is filled with a universe containing 2 cells + check_type("fill universe", self.cell.fill, openmc.Universe) + # check if universe contains 2 cells + check_length("universe cells", self.cell.fill.cells, 2) # Initialize vector self.vector = np.zeros(3) - self.cell_materials = [ - cell.fill for cell in self.cell.fill.cells.values() if cell.fill.depletable + self.universe_cells = [ + cell for cell in self.cell.fill.cells.values() if cell.fill.depletable ] check_type("samples", samples, int) self.samples = samples - if self.cell_materials: + if self.universe_cells: self._initialize_volume_calc() def _get_cell_attrib(self): @@ -788,9 +824,10 @@ def _set_cell_attrib(self, val): def _initialize_volume_calc(self): """Set volume calculation model settings of depletable materials filling the parametric Cell. + """ ll, ur = self.geometry.bounding_box - mat_vol = openmc.VolumeCalculation(self.cell_materials, self.samples, ll, ur) + mat_vol = openmc.VolumeCalculation(self.universe_cells, self.samples, ll, ur) self.model.settings.volume_calculations = mat_vol def _calculate_volumes(self): @@ -806,9 +843,9 @@ def _calculate_volumes(self): volumes = {} if comm.rank == 0: res = openmc.VolumeCalculation.from_hdf5("volume_1.h5") - for mat_idx, mat in enumerate(self.local_mats): - if int(mat) in [mat.id for mat in self.cell_materials]: - volumes[mat] = res.volumes[int(mat)].n + for cell in self.universe_cells: + mat_id = cell.fill.id + volumes[mat_id] = res.volumes[mat_id].n volumes = comm.bcast(volumes) return volumes @@ -821,7 +858,7 @@ class BatchwiseCellTemperature(BatchwiseCell): :meth:`openmc.deplete.Integrator.add_batchwise` method from an integrator class, such as :class:`openmc.deplete.CECMIntegrator`. - .. versionadded:: 0.13.4 + .. versionadded:: 0.14.1 Parameters ---------- @@ -832,7 +869,7 @@ class BatchwiseCellTemperature(BatchwiseCell): attrib_name : str Cell attribute type bracket : list of float - Bracketing interval to search for the solution, always relative to the + Initial bracketing interval to search for the solution, relative to the solution at previous step. bracket_limit : list of float Absolute bracketing interval lower and upper; if during the adaptive @@ -941,7 +978,7 @@ class BatchwiseMaterial(Batchwise): Specific classes for running batch wise depletion calculations are implemented as derived class of BatchwiseMaterial. - .. versionadded:: 0.13.4 + .. versionadded:: 0.14.1 Parameters ---------- @@ -953,7 +990,7 @@ class BatchwiseMaterial(Batchwise): Dictionary of material composition to parameterize, where a pair key value represents a nuclide and its weight fraction, respectively. bracket : list of float - Bracketing interval to search for the solution, always relative to the + Initial bracketing interval to search for the solution, relative to the solution at previous step. bracket_limit : list of float Absolute bracketing interval lower and upper; if during the adaptive @@ -1025,7 +1062,7 @@ def __init__( for nuc in mat_vector: check_value("check nuclide exists", nuc, self.operator.nuclides_with_data) - if round(sum(mat_vector.values()), 2) != 1.0: + if not isclose(sum(mat_vector.values()), 1.0, abs_tol=0.01): # Normalize material elements vector sum_values = sum(mat_vector.values()) for elm in mat_vector: @@ -1070,24 +1107,6 @@ def _get_material(self, val): return val - @abstractmethod - def _model_builder(self, param): - """Builds the parametric model to be passed to the - :meth:`openmc.search.search_for_keff` method. - Callable function which builds a model according to a passed - parameter. This function must return an openmc.model.Model object. - - Parameters - ---------- - param : - Model material function variable - - Returns - ------- - _model : openmc.model.Model - Openmc parametric model - """ - def search_for_keff(self, x, step_index): """Perform the criticality search on parametric material variable. @@ -1110,7 +1129,8 @@ def search_for_keff(self, x, step_index): # even though they are also re-calculated when running the search_for_kef self._update_materials(x) - # Solve search_for_keff and find new value + # Set initial material addition to 0 and let program calculate the + # right amount root = self._search_for_keff(0) # Update concentration vector and volumes with new value @@ -1128,7 +1148,7 @@ class BatchwiseMaterialRefuel(BatchwiseMaterial): :meth:`openmc.deplete.Integrator.add_batchwise` method from an integrator class, such as :class:`openmc.deplete.CECMIntegrator`. - .. versionadded:: 0.13.4 + .. versionadded:: 0.14.1 Parameters ---------- @@ -1142,7 +1162,7 @@ class BatchwiseMaterialRefuel(BatchwiseMaterial): Dictionary of material composition to parameterize, where a pair key value represents a nuclide and its weight fraction, respectively. bracket : list of float - Bracketing interval to search for the solution, always relative to the + Initial bracketing interval to search for the solution, relative to the solution at previous step. bracket_limit : list of float Absolute bracketing interval lower and upper; if during the adaptive @@ -1220,8 +1240,10 @@ def __init__( def _model_builder(self, param): """Callable function which builds a model according to a passed parameter. This function must return an openmc.model.Model object. + Builds the parametric model to be passed to the :meth:`openmc.search.search_for_keff` method. + The parametrization can either be at constant volume or constant density, according to user input. Default is constant volume. From a4b44edd3c3b5b5a51967935f30ff18696443d57 Mon Sep 17 00:00:00 2001 From: church89 Date: Fri, 15 Mar 2024 11:21:48 +0100 Subject: [PATCH 41/59] address more comments by @drewejohnson --- openmc/deplete/batchwise.py | 8 +- openmc/deplete/stepresult.py | 6 +- openmc/search.py | 11 ++- .../deplete_with_batchwise/test.py | 1 - tests/unit_tests/test_deplete_batchwise.py | 84 ++++++++++--------- 5 files changed, 60 insertions(+), 50 deletions(-) diff --git a/openmc/deplete/batchwise.py b/openmc/deplete/batchwise.py index 285550874dd..6b055aae000 100644 --- a/openmc/deplete/batchwise.py +++ b/openmc/deplete/batchwise.py @@ -125,7 +125,7 @@ def bracketed_method(self): def bracketed_method(self, value): check_value("bracketed_method", value, _SCALAR_BRACKETED_METHODS) if value != "brentq": - warn("WARNING: brentq bracketed method is recommended") + warn("brentq bracketed method is recommended") self._bracketed_method = value @property @@ -212,7 +212,7 @@ def _search_for_keff(self, val): targeted value """ # make sure we don't modify original bracket - bracket = deepcopy(self.brack½et) + bracket = deepcopy(self.bracket) # Run until a search_for_keff root is found or out of limits root = None @@ -240,7 +240,7 @@ def _search_for_keff(self, val): # Set res with the closest limit and continue arg_min = abs(np.array(self.bracket_limit) - res).argmin() warn( - "WARNING: Search_for_keff returned root out of " + "Search_for_keff returned root out of " "bracket limit. Set root to {:.2f} and continue.".format( self.bracket_limit[arg_min] ) @@ -310,7 +310,7 @@ def _search_for_keff(self, val): # Set res with closest limit and continue arg_min = abs(np.array(self.bracket_limit) - guesses).argmin() warn( - "WARNING: Adaptive iterative bracket went off " + "Adaptive iterative bracket went off " "bracket limits. Set root to {:.2f} and continue.".format( self.bracket_limit[arg_min] ) diff --git a/openmc/deplete/stepresult.py b/openmc/deplete/stepresult.py index 2d4328441b2..dd9a0f2cc62 100644 --- a/openmc/deplete/stepresult.py +++ b/openmc/deplete/stepresult.py @@ -56,6 +56,8 @@ class StepResult: Number of stages in simulation. data : numpy.ndarray Atom quantity, stored by stage, mat, then by nuclide. + batchwise : float + The root returned by the reactivity controller. proc_time : int Average time spent depleting a material across all materials and processes @@ -75,6 +77,7 @@ def __init__(self): self.data = None self.batchwise = None + def __repr__(self): t = self.time[0] dt = self.time[1] - self.time[0] @@ -544,7 +547,8 @@ def save(op, x, op_results, t, source_rate, step_ind, proc_time=None, Total process time spent depleting materials. This may be process-dependent and will be reduced across MPI processes. - + root : float + The root returned by the reactivity controller. path : PathLike Path to file to write. Defaults to 'depletion_results.h5'. diff --git a/openmc/search.py b/openmc/search.py index 7e2c5a1a19d..527417a1938 100644 --- a/openmc/search.py +++ b/openmc/search.py @@ -1,5 +1,6 @@ from collections.abc import Callable from numbers import Real +from warnings import warn import scipy.optimize as sopt @@ -39,7 +40,8 @@ def _search_keff(guess, target, model_builder, model_args, print_iterations, results : Iterable of Real Running list of results thus far, to be updated during the execution of this function. - + run_in_memory : bool + Whether or not to run the openmc model in memory. Returns ------- float @@ -109,6 +111,9 @@ def search_for_keff(model_builder, initial_guess=None, target=1.0, run_args : dict, optional Keyword arguments to pass to :meth:`openmc.Model.run`. Defaults to no arguments. + run_in_memory : bool + Whether or not to run the openmc model in memory. + Defaults to False. .. versionadded:: 0.13.1 **kwargs @@ -214,9 +219,9 @@ def search_for_keff(model_builder, initial_guess=None, target=1.0, return zero_value, guesses, results else: - print(f'WARNING: {root_res.flag}') + warn(f'{root_res.flag}') return guesses, results # In case the root finder is not successful except Exception as e: - print(f'WARNING: {e}') + warn(f'{e}) return guesses, results diff --git a/tests/regression_tests/deplete_with_batchwise/test.py b/tests/regression_tests/deplete_with_batchwise/test.py index c7fd59788bb..9c45045a65f 100644 --- a/tests/regression_tests/deplete_with_batchwise/test.py +++ b/tests/regression_tests/deplete_with_batchwise/test.py @@ -78,7 +78,6 @@ def model(): return openmc.Model(geometry, materials, settings) -@pytest.mark.skipif(sys.version_info < (3, 9), reason="Requires Python 3.9+") @pytest.mark.parametrize("obj, attribute, bracket_limit, axis, vec, ref_result", [ ('trans_cell', 'translation', [-40,40], 2, None, 'depletion_with_translation'), ('rot_cell', 'rotation', [-90,90], 2, None, 'depletion_with_rotation'), diff --git a/tests/unit_tests/test_deplete_batchwise.py b/tests/unit_tests/test_deplete_batchwise.py index 37c6b4002ea..e48d86edba2 100644 --- a/tests/unit_tests/test_deplete_batchwise.py +++ b/tests/unit_tests/test_deplete_batchwise.py @@ -62,6 +62,15 @@ def model(): return openmc.Model(geometry, materials, settings) +@pytest.fixture +def operator(model): + return CoupledOperator(model, CHAIN_PATH) + +@pytest.fixture +def integrator(operator): + return openmc.deplete.PredictorIntegrator( + operator, [1,1], 0.0, timestep_units = 'd') + @pytest.mark.parametrize("case_name, obj, attribute, bracket, limit, axis, vec", [ ('cell translation','universe_cell', 'translation', [-1,1], [-10,10], 2, None), ('cell rotation', 'universe_cell', 'rotation', [-1,1], [-10,10], 2, None), @@ -72,14 +81,11 @@ def model(): ('invalid_2', 'fuel', 'temperature', [-1,1], [-10,10], 2, None ), ('invalid_3', 'fuel', 'translation', [-1,1], [-10,10], 2, None), ]) -def test_attributes(case_name, model, obj, attribute, bracket, limit, axis, vec): +def test_attributes(case_name, model, operator, integrator, obj, attribute, + bracket, limit, axis, vec): """ Test classes attributes are set correctly """ - op = CoupledOperator(model, CHAIN_PATH) - - int = openmc.deplete.PredictorIntegrator( - op, [1,1], 0.0, timestep_units = 'd') kwargs = {'bracket': bracket, 'bracket_limit':limit, 'tol': 0.1,} @@ -91,59 +97,57 @@ def test_attributes(case_name, model, obj, attribute, bracket, limit, axis, vec) if case_name == "invalid_1": with pytest.raises(ValueError) as e: - int.add_batchwise(obj, attribute, **kwargs) + integrator.add_batchwise(obj, attribute, **kwargs) assert str(e.value) == 'Unable to set "Material name" to "universe_cell" '\ 'since it is not in "[\'fuel\', \'water\']"' elif case_name == "invalid_2": with pytest.raises(ValueError) as e: - int.add_batchwise(obj, attribute, **kwargs) + integrator.add_batchwise(obj, attribute, **kwargs) assert str(e.value) == 'Unable to set "Cell name exists" to "fuel" since '\ 'it is not in "[\'fuel_cell\', \'universe_cell\', \'\', \'\']"' elif case_name == "invalid_3": with pytest.raises(ValueError) as e: - int.add_batchwise(obj, attribute, **kwargs) + integrator.add_batchwise(obj, attribute, **kwargs) assert str(e.value) == 'Unable to set "Cell name exists" to "fuel" since '\ 'it is not in "[\'fuel_cell\', \'universe_cell\', \'\', \'\']"' else: - int.add_batchwise(obj, attribute, **kwargs) + integrator.add_batchwise(obj, attribute, **kwargs) if attribute in ('translation','rotation'): - assert int.get_batchwise.cell_materials == [cell.fill for cell in \ + assert integrator.batchwise.universe_cells == [cell for cell in \ model.geometry.get_cells_by_name(obj)[0].fill.cells.values() \ if cell.fill.depletable] - assert int.get_batchwise.axis == axis + assert integrator.batchwise.axis == axis elif attribute == 'refuel': - assert int.get_batchwise.mat_vector == vec + assert integrator.batchwise.mat_vector == vec - assert int.get_batchwise.attrib_name == attribute - assert int.get_batchwise.bracket == bracket - assert int.get_batchwise.bracket_limit == limit - assert int.get_batchwise.burn_mats == op.burnable_mats - assert int.get_batchwise.local_mats == op.local_mats + assert integrator.batchwise.attrib_name == attribute + assert integrator.batchwise.bracket == bracket + assert integrator.batchwise.bracket_limit == limit + assert integrator.batchwise.burn_mats == operator.burnable_mats + assert integrator.batchwise.local_mats == operator.local_mats @pytest.mark.parametrize("obj, attribute, value_to_set", [ ('universe_cell', 'translation', 0), ('universe_cell', 'rotation', 0) ]) -def test_cell_methods(run_in_tmpdir, model, obj, attribute, value_to_set): +def test_cell_methods(run_in_tmpdir, model, operator, integrator, obj, attribute, + value_to_set): """ Test cell base class internal method """ kwargs = {'bracket':[-1,1], 'bracket_limit':[-10,10], 'axis':2, 'tol':0.1} - op = CoupledOperator(model, CHAIN_PATH) - integrator = openmc.deplete.PredictorIntegrator( - op, [1,1], 0.0, timestep_units = 'd') integrator.add_batchwise(obj, attribute, **kwargs) model.export_to_xml() openmc.lib.init() - integrator.get_batchwise._set_cell_attrib(value_to_set) - assert integrator.get_batchwise._get_cell_attrib() == value_to_set + integrator.batchwise._set_cell_attrib(value_to_set) + assert integrator.batchwise._get_cell_attrib() == value_to_set - vol = integrator.get_batchwise._calculate_volumes() - for cell in integrator.get_batchwise.cell_materials: + vol = integrator.batchwise._calculate_volumes() + for cell in integrator.batchwise.universe_cells: assert vol[str(cell.id)] == pytest.approx([ mat.volume for mat in model.materials \ if mat.id == cell.id][0], rel=tolerance) @@ -154,7 +158,8 @@ def test_cell_methods(run_in_tmpdir, model, obj, attribute, value_to_set): ('U238', 1.0e22), ('Xe135', 1.0e21) ]) -def test_internal_methods(run_in_tmpdir, model, nuclide, atoms_to_add): +def test_internal_methods(run_in_tmpdir, model, operator, integrator, nuclide, + atoms_to_add): """ Method to update volume in AtomNumber after depletion step. Method inheritated by all derived classes so one check is enough. @@ -162,9 +167,6 @@ def test_internal_methods(run_in_tmpdir, model, nuclide, atoms_to_add): kwargs = {'bracket':[-1,1], 'bracket_limit':[-10,10], 'mat_vector':{}} - op = CoupledOperator(model, CHAIN_PATH) - integrator = openmc.deplete.PredictorIntegrator(op, [1,1], 0.0, - timestep_units = 'd') integrator.add_batchwise('fuel', 'refuel', **kwargs) model.export_to_xml() @@ -173,32 +175,32 @@ def test_internal_methods(run_in_tmpdir, model, nuclide, atoms_to_add): #Increase number of atoms of U238 in fuel by fix amount and check the # volume increase at constant-density #extract fuel material from model materials - mat = integrator.get_batchwise.material - mat_index = op.number.index_mat[str(mat.id)] - nuc_index = op.number.index_nuc[nuclide] - vol = op.number.get_mat_volume(str(mat.id)) - op.number.number[mat_index][nuc_index] += atoms_to_add - integrator.get_batchwise._update_volumes() + mat = integrator.batchwise.material + mat_index = operator.number.index_mat[str(mat.id)] + nuc_index = operator.number.index_nuc[nuclide] + vol = operator.number.get_mat_volume(str(mat.id)) + operator.number.number[mat_index][nuc_index] += atoms_to_add + integrator.batchwise._update_volumes() vol_to_compare = vol + (atoms_to_add * openmc.data.atomic_mass(nuclide) /\ openmc.data.AVOGADRO / mat.density) - assert op.number.get_mat_volume(str(mat.id)) == pytest.approx(vol_to_compare) + assert operator.number.get_mat_volume(str(mat.id)) == pytest.approx(vol_to_compare) - x = [i[:op.number.n_nuc_burn] for i in op.number.number] - integrator.get_batchwise._update_materials(x) + x = [i[:operator.number.n_nuc_burn] for i in operator.number.number] + integrator.batchwise._update_materials(x) nuc_index_lib = openmc.lib.materials[mat.id].nuclides.index(nuclide) - dens_to_compare = 1.0e-24 * op.number.get_atom_density(str(mat.id), nuclide) + dens_to_compare = 1.0e-24 * operator.number.get_atom_density(str(mat.id), nuclide) assert openmc.lib.materials[mat.id].densities[nuc_index_lib] == pytest.approx(dens_to_compare) volumes = {str(mat.id): vol + 1} - new_x = integrator.get_batchwise._update_x_and_set_volumes(x, volumes) + new_x = integrator.batchwise._update_x_and_set_volumes(x, volumes) dens_to_compare = 1.0e24 * volumes[str(mat.id)] *\ openmc.lib.materials[mat.id].densities[nuc_index_lib] assert new_x[mat_index][nuc_index] == pytest.approx(dens_to_compare) # assert volume in AtomNumber is set correctly - assert op.number.get_mat_volume(str(mat.id)) == volumes[str(mat.id)] + assert operator.number.get_mat_volume(str(mat.id)) == volumes[str(mat.id)] openmc.lib.finalize() From 0a2933ce5304ad35f562b3c131af7a482e1422c7 Mon Sep 17 00:00:00 2001 From: church89 Date: Fri, 15 Mar 2024 13:37:44 +0100 Subject: [PATCH 42/59] replaced batchwise nomenclature with reactivity_control one --- openmc/deplete/__init__.py | 2 +- openmc/deplete/abc.py | 52 ++++++++------- .../{batchwise.py => reactivity_control.py} | 66 ++++++++++--------- openmc/deplete/stepresult.py | 18 ++--- openmc/search.py | 2 +- .../__init__.py | 0 .../test.py | 13 ++-- ....py => test_deplete_reactivity_control.py} | 53 ++++++++------- 8 files changed, 108 insertions(+), 98 deletions(-) rename openmc/deplete/{batchwise.py => reactivity_control.py} (95%) rename tests/regression_tests/{deplete_with_batchwise => deplete_with_reactivity_control}/__init__.py (100%) rename tests/regression_tests/{deplete_with_batchwise => deplete_with_reactivity_control}/test.py (91%) rename tests/unit_tests/{test_deplete_batchwise.py => test_deplete_reactivity_control.py} (79%) diff --git a/openmc/deplete/__init__.py b/openmc/deplete/__init__.py index c86a93c8a19..5e0063c90e5 100644 --- a/openmc/deplete/__init__.py +++ b/openmc/deplete/__init__.py @@ -17,7 +17,7 @@ from .results import * from .integrators import * from .transfer_rates import * -from .batchwise import * +from .reactivity_control import * from . import abc from . import cram from . import helpers diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py index c73f397e5ae..e77ff1d11e1 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -26,8 +26,11 @@ from .pool import deplete from .transfer_rates import TransferRates from openmc import Material, Cell -from .batchwise import (BatchwiseCellGeometrical, BatchwiseCellTemperature, - BatchwiseMaterialRefuel) +from .reactivity_control import ( + GeometricalCellReactivityController, + TemperatureCellReactivityController, + RefuelMaterialReactivityController +) __all__ = [ "OperatorResult", "TransportOperator", @@ -556,8 +559,8 @@ class Integrator(ABC): transfer_rates : openmc.deplete.TransferRates Instance of TransferRates class to perform continuous transfer during depletion - batchwise : openmc.deplete.Batchwise - Instance of Batchwise class to perform batch-wise scheme during + reactivity_control : openmc.deplete.ReactivityController + Instance of ReactivityController class to perform reactivity control during transport-depletion simulation. .. versionadded:: 0.14.0 @@ -641,7 +644,7 @@ def __init__(self, operator, timesteps, power=None, power_density=None, self.source_rates = asarray(source_rates) self.transfer_rates = None - self._batchwise = None + self._reactivity_control = None if isinstance(solver, str): # Delay importing of cram module, which requires this file @@ -691,8 +694,8 @@ def solver(self, func): self._solver = func @property - def batchwise(self): - return self._batchwise + def reactivity_control(self): + return self._reactivity_control def _timed_deplete(self, n, rates, dt, matrix_func=None): start = time.time() @@ -773,11 +776,11 @@ def _get_start_data(self): return (self.operator.prev_res[-1].time[-1], len(self.operator.prev_res) - 1) - def _get_bos_from_batchwise(self, step_index, bos_conc): - """Get BOS from criticality batch-wise control.""" + def _get_bos_from_reactivity_control(self, step_index, bos_conc): + """Get BOS from reactivity control.""" x = deepcopy(bos_conc) # Get new vector after keff criticality control - x, root = self._batchwise.search_for_keff(x, step_index) + x, root = self._reactivity_control.search_for_keff(x, step_index) return x, root def integrate( @@ -814,9 +817,9 @@ def integrate( # Solve transport equation (or obtain result from restart) if i > 0 or self.operator.prev_res is None: - # Update geometry/material according to batchwise definition - if self._batchwise is not None and source_rate != 0.0: - n, root = self._get_bos_from_batchwise(i, n) + # Update geometry/material according to reactivity control + if self._reactivity_control is not None and source_rate != 0.0: + n, root = self._get_bos_from_reactivity_control(i, n) else: root = None n, res = self._get_bos_data_from_operator(i, source_rate, n) @@ -843,8 +846,8 @@ def integrate( # solve) if output and final_step and comm.rank == 0: print(f"[openmc.deplete] t={t} (final operator evaluation)") - if self._batchwise is not None and source_rate != 0.0: - n, root = self._get_bos_from_batchwise(i+1, n) + if self._reactivity_control is not None and source_rate != 0.0: + n, root = self._get_bos_from_reactivity_control(i+1, n) else: root = None res_list = [self.operator(n, source_rate if final_step else 0.0)] @@ -882,31 +885,32 @@ def add_transfer_rate(self, material, components, transfer_rate, self.transfer_rates.set_transfer_rate(material, components, transfer_rate, transfer_rate_units, destination_material) - def add_batchwise(self, obj, attr, **kwargs): - """Add batchwise operation to integrator scheme. + def add_reactivity_control(self, obj, attr, **kwargs): + """Add reactivity control to integrator scheme. Parameters ---------- obj : openmc.Cell or openmc.Material object or id or str name - Cell or Materials identifier to where add batchwise scheme + Cell or Materials identifier to where add reactivity control attr : str - Attribute to specify the type of batchwise scheme. Accepted values + Attribute to specify the type of reactivity control. Accepted values are: 'translation', 'rotation', 'temperature' for an openmc.Cell object; 'refuel' for an openmc.Material object. **kwargs - keyword arguments that are passed to the batchwise class. + keyword arguments that are passed to ReactivityController. """ check_value('attribute', attr, ('translation', 'rotation', 'temperature', 'refuel')) if attr in ('translation', 'rotation'): - batchwise = BatchwiseCellGeometrical + reactivity_control = GeometricalCellReactivityController elif attr == 'temperature': - batchwise = BatchwiseCellTemperature + reactivity_control = TemperatureCellReactivityController elif attr == 'refuel': - batchwise = BatchwiseMaterialRefuel + reactivity_control = RefuelMaterialReactivityController - self._batchwise = batchwise.from_params(obj, attr, self.operator,**kwargs) + self._reactivity_control = reactivity_control.from_params(obj, attr, + self.operator,**kwargs) @add_params class SIIntegrator(Integrator): diff --git a/openmc/deplete/batchwise.py b/openmc/deplete/reactivity_control.py similarity index 95% rename from openmc/deplete/batchwise.py rename to openmc/deplete/reactivity_control.py index 6b055aae000..8191d88ef7f 100644 --- a/openmc/deplete/batchwise.py +++ b/openmc/deplete/reactivity_control.py @@ -19,20 +19,20 @@ ) -class Batchwise(ABC): - """Abstract class defining a generalized batch wise scheme. +class ReactivityController(ABC): + """Abstract class defining a generalized reactivity control. - Batchwise schemes, such as control rod adjustment or material refuelling to - control reactivity and maintain keff constant and equal to a desired value, - usually one. + Reactivity control schemes, such as control rod adjustment or material + refuelling to control reactivity and maintain keff constant and equal to a + desired value, usually one. - A batch wise scheme can be added here to an integrator instance, + A reactivity control scheme can be added here to an integrator instance, such as :class:`openmc.deplete.CECMIntegrator`, to parametrize one system variable with the aim of satisfy certain design criteria, such as keeping keff equal to one, while running transport-depletion calculations. - Specific classes for running batch wise depletion calculations are - implemented as derived class of Batchwise. + Specific classes for running reactivity control depletion calculations are + implemented as derived class of ReactivityController. .. versionadded:: 0.14.1 @@ -348,7 +348,7 @@ def _update_materials(self, x): """Update number density and material compositions in OpenMC on all processes. If density_treatment is set to 'constant-density' - :meth:`openmc.deplete.batchwise._update_volumes` is called to update + :meth:`openmc.deplete.reactivity_control._update_volumes` is called to update material volumes in AtomNumber, keeping the material total density constant, before re-normalizing the atom densities and assigning them to the model in memory. @@ -430,11 +430,11 @@ def _update_x_and_set_volumes(self, x, volumes): return x -class BatchwiseCell(Batchwise): - """Abstract class holding batch wise cell-based functions. +class CellReactivityController(ReactivityController): + """Abstract class holding reactivity control cell-based functions. - Specific classes for running batch wise depletion calculations are - implemented as derived class of BatchwiseCell. + Specific classes for running reactivity control depletion calculations are + implemented as derived class of CellReactivityController. .. versionadded:: 0.14.1 @@ -674,13 +674,13 @@ def search_for_keff(self, x, step_index): return x, root -class BatchwiseCellGeometrical(BatchwiseCell): - """Batch wise cell-based with geometrical-attribute class. +class GeometricalCellReactivityController(CellReactivityController): + """Reactivity control cell-based with geometrical-attribute class. A user doesn't need to call this class directly. Instead an instance of this class is automatically created by calling - :meth:`openmc.deplete.Integrator.add_batchwise` method from an integrator - class, such as :class:`openmc.deplete.CECMIntegrator`. + :meth:`openmc.deplete.Integrator.add_reactivity_control` method from an + integrator class, such as :class:`openmc.deplete.CECMIntegrator`. .. versionadded:: 0.14.1 @@ -850,13 +850,13 @@ def _calculate_volumes(self): return volumes -class BatchwiseCellTemperature(BatchwiseCell): - """Batch wise cell-based with temperature-attribute class. +class TemperatureCellReactivityController(CellReactivityController): + """Reactivity control cell-based with temperature-attribute class. A user doesn't need to call this class directly. Instead an instance of this class is automatically created by calling - :meth:`openmc.deplete.Integrator.add_batchwise` method from an integrator - class, such as :class:`openmc.deplete.CECMIntegrator`. + :meth:`openmc.deplete.Integrator.add_reactivity_control` method from an + integrator class, such as :class:`openmc.deplete.CECMIntegrator`. .. versionadded:: 0.14.1 @@ -972,11 +972,11 @@ def _set_cell_attrib(self, val): self.lib_cell.set_temperature(val) -class BatchwiseMaterial(Batchwise): - """Abstract class holding batch wise material-based functions. +class MaterialReactivityController(ReactivityController): + """Abstract class holding reactivity control material-based functions. - Specific classes for running batch wise depletion calculations are - implemented as derived class of BatchwiseMaterial. + Specific classes for running reactivity control depletion calculations are + implemented as derived class of MaterialReactivityController. .. versionadded:: 0.14.1 @@ -1140,13 +1140,14 @@ def search_for_keff(self, x, step_index): return x, root -class BatchwiseMaterialRefuel(BatchwiseMaterial): - """Batch wise material-based class for refuelling (addition or removal) scheme. +class RefuelMaterialReactivityController(MaterialReactivityController): + """Reactivity control material-based class for refuelling (addition or + removal) scheme. A user doesn't need to call this class directly. Instead an instance of this class is automatically created by calling - :meth:`openmc.deplete.Integrator.add_batchwise` method from an integrator - class, such as :class:`openmc.deplete.CECMIntegrator`. + :meth:`openmc.deplete.Integrator.add_reactivity_control` method from an + integrator class, such as :class:`openmc.deplete.CECMIntegrator`. .. versionadded:: 0.14.1 @@ -1315,14 +1316,15 @@ def _model_builder(self, param): return self.model def _calculate_volumes(self, res): - """Uses :meth:`openmc.batchwise._search_for_keff` solution as grams of - material to add or remove to calculate new material volume. + """Uses :meth:`openmc.deplete.reactivity_control._search_for_keff` + solution as grams of material to add or remove to calculate new material + volume. Parameters ---------- res : float Solution in grams of material, coming from - :meth:`openmc.batchwise._search_for_keff` + :meth:`openmc.deplete.reactivity_control._search_for_keff` Returns ------- diff --git a/openmc/deplete/stepresult.py b/openmc/deplete/stepresult.py index dd9a0f2cc62..bef7fffdd0d 100644 --- a/openmc/deplete/stepresult.py +++ b/openmc/deplete/stepresult.py @@ -56,7 +56,7 @@ class StepResult: Number of stages in simulation. data : numpy.ndarray Atom quantity, stored by stage, mat, then by nuclide. - batchwise : float + reac_cont : float The root returned by the reactivity controller. proc_time : int Average time spent depleting a material across all @@ -76,7 +76,7 @@ def __init__(self): self.mat_to_hdf5_ind = None self.data = None - self.batchwise = None + self.reac_cont = None def __repr__(self): t = self.time[0] @@ -353,7 +353,7 @@ def _write_hdf5_metadata(self, handle): dtype="float64") handle.create_dataset( - "batchwise_root", (1,), maxshape=(None,), + "reac_cont_root", (1,), maxshape=(None,), dtype="float64") def _to_hdf5(self, handle, index, parallel=False): @@ -386,7 +386,7 @@ def _to_hdf5(self, handle, index, parallel=False): time_dset = handle["/time"] source_rate_dset = handle["/source_rate"] proc_time_dset = handle["/depletion time"] - root_dset = handle["/batchwise_root"] + root_dset = handle["/reac_cont_root"] # Get number of results stored number_shape = list(number_dset.shape) @@ -447,7 +447,7 @@ def _to_hdf5(self, handle, index, parallel=False): proc_time_dset[index] = ( self.proc_time / (comm.size * self.n_hdf5_mats) ) - root_dset[index] = self.batchwise + root_dset[index] = self.reac_cont @classmethod def from_hdf5(cls, handle, step): @@ -482,9 +482,9 @@ def from_hdf5(cls, handle, step): if step < proc_time_dset.shape[0]: results.proc_time = proc_time_dset[step] - if "batchwise_root" in handle: - root_dset = handle["/batchwise_root"] - results.batchwise = root_dset[step] + if "reac_cont_root" in handle: + root_dset = handle["/reac_cont_root"] + results.reac_cont = root_dset[step] if results.proc_time is None: results.proc_time = np.array([np.nan]) @@ -582,7 +582,7 @@ def save(op, x, op_results, t, source_rate, step_ind, proc_time=None, results.proc_time = proc_time if results.proc_time is not None: results.proc_time = comm.reduce(proc_time, op=MPI.SUM) - results.batchwise = root + results.reac_cont = root if not Path(path).is_file(): Path(path).parent.mkdir(parents=True, exist_ok=True) diff --git a/openmc/search.py b/openmc/search.py index 527417a1938..9b18e870025 100644 --- a/openmc/search.py +++ b/openmc/search.py @@ -223,5 +223,5 @@ def search_for_keff(model_builder, initial_guess=None, target=1.0, return guesses, results # In case the root finder is not successful except Exception as e: - warn(f'{e}) + warn(f'{e}') return guesses, results diff --git a/tests/regression_tests/deplete_with_batchwise/__init__.py b/tests/regression_tests/deplete_with_reactivity_control/__init__.py similarity index 100% rename from tests/regression_tests/deplete_with_batchwise/__init__.py rename to tests/regression_tests/deplete_with_reactivity_control/__init__.py diff --git a/tests/regression_tests/deplete_with_batchwise/test.py b/tests/regression_tests/deplete_with_reactivity_control/test.py similarity index 91% rename from tests/regression_tests/deplete_with_batchwise/test.py rename to tests/regression_tests/deplete_with_reactivity_control/test.py index 9c45045a65f..1f1fa543d56 100644 --- a/tests/regression_tests/deplete_with_batchwise/test.py +++ b/tests/regression_tests/deplete_with_reactivity_control/test.py @@ -1,4 +1,4 @@ -""" Tests for Batchwise class """ +""" Tests for ReactivityController class """ from pathlib import Path import shutil @@ -83,8 +83,9 @@ def model(): ('rot_cell', 'rotation', [-90,90], 2, None, 'depletion_with_rotation'), ('f', 'refuel', [-100,100], None, {'U235':1}, 'depletion_with_refuel') ]) -def test_batchwise(run_in_tmpdir, model, obj, attribute, bracket_limit, axis, - vec, ref_result): +def test_reactivity_control(run_in_tmpdir, model, obj, attribute, bracket_limit, + axis, vec, ref_result): + chain_file = Path(__file__).parents[2] / 'chain_simple.xml' op = CoupledOperator(model, chain_file) @@ -100,7 +101,7 @@ def test_batchwise(run_in_tmpdir, model, obj, attribute, bracket_limit, axis, if axis is not None: kwargs['axis'] = axis - integrator.add_batchwise(obj, attribute, **kwargs) + integrator.add_reactivity_control(obj, attribute, **kwargs) integrator.integrate() # Get path to test and reference results @@ -117,5 +118,5 @@ def test_batchwise(run_in_tmpdir, model, obj, attribute, bracket_limit, axis, res_ref = openmc.deplete.Results(path_reference) # Use high tolerance here - assert [res.batchwise for res in res_test] == pytest.approx( - [res.batchwise for res in res_ref], rel=2) + assert [res.reac_cont for res in res_test] == pytest.approx( + [res.reac_cont for res in res_ref], rel=2) diff --git a/tests/unit_tests/test_deplete_batchwise.py b/tests/unit_tests/test_deplete_reactivity_control.py similarity index 79% rename from tests/unit_tests/test_deplete_batchwise.py rename to tests/unit_tests/test_deplete_reactivity_control.py index e48d86edba2..98944f42d80 100644 --- a/tests/unit_tests/test_deplete_batchwise.py +++ b/tests/unit_tests/test_deplete_reactivity_control.py @@ -1,4 +1,4 @@ -""" Tests for Batchwise class """ +""" Tests for ReactivityController class """ from pathlib import Path @@ -8,8 +8,11 @@ import openmc import openmc.lib from openmc.deplete import CoupledOperator -from openmc.deplete import (BatchwiseCellGeometrical, BatchwiseCellTemperature, - BatchwiseMaterialRefuel) +from openmc.deplete import ( + GeometricalCellReactivityController, + TemperatureCellReactivityController, + RefuelMaterialReactivityController +) CHAIN_PATH = Path(__file__).parents[1] / "chain_simple.xml" @@ -97,36 +100,36 @@ def test_attributes(case_name, model, operator, integrator, obj, attribute, if case_name == "invalid_1": with pytest.raises(ValueError) as e: - integrator.add_batchwise(obj, attribute, **kwargs) + integrator.add_reactivity_control(obj, attribute, **kwargs) assert str(e.value) == 'Unable to set "Material name" to "universe_cell" '\ 'since it is not in "[\'fuel\', \'water\']"' elif case_name == "invalid_2": with pytest.raises(ValueError) as e: - integrator.add_batchwise(obj, attribute, **kwargs) + integrator.add_reactivity_control(obj, attribute, **kwargs) assert str(e.value) == 'Unable to set "Cell name exists" to "fuel" since '\ 'it is not in "[\'fuel_cell\', \'universe_cell\', \'\', \'\']"' elif case_name == "invalid_3": with pytest.raises(ValueError) as e: - integrator.add_batchwise(obj, attribute, **kwargs) + integrator.add_reactivity_control(obj, attribute, **kwargs) assert str(e.value) == 'Unable to set "Cell name exists" to "fuel" since '\ 'it is not in "[\'fuel_cell\', \'universe_cell\', \'\', \'\']"' else: - integrator.add_batchwise(obj, attribute, **kwargs) + integrator.add_reactivity_control(obj, attribute, **kwargs) if attribute in ('translation','rotation'): - assert integrator.batchwise.universe_cells == [cell for cell in \ + assert integrator.reactivity_control.universe_cells == [cell for cell in \ model.geometry.get_cells_by_name(obj)[0].fill.cells.values() \ if cell.fill.depletable] - assert integrator.batchwise.axis == axis + assert integrator.reactivity_control.axis == axis elif attribute == 'refuel': - assert integrator.batchwise.mat_vector == vec + assert integrator.reactivity_control.mat_vector == vec - assert integrator.batchwise.attrib_name == attribute - assert integrator.batchwise.bracket == bracket - assert integrator.batchwise.bracket_limit == limit - assert integrator.batchwise.burn_mats == operator.burnable_mats - assert integrator.batchwise.local_mats == operator.local_mats + assert integrator.reactivity_control.attrib_name == attribute + assert integrator.reactivity_control.bracket == bracket + assert integrator.reactivity_control.bracket_limit == limit + assert integrator.reactivity_control.burn_mats == operator.burnable_mats + assert integrator.reactivity_control.local_mats == operator.local_mats @pytest.mark.parametrize("obj, attribute, value_to_set", [ ('universe_cell', 'translation', 0), @@ -139,15 +142,15 @@ def test_cell_methods(run_in_tmpdir, model, operator, integrator, obj, attribute """ kwargs = {'bracket':[-1,1], 'bracket_limit':[-10,10], 'axis':2, 'tol':0.1} - integrator.add_batchwise(obj, attribute, **kwargs) + integrator.add_reactivity_control(obj, attribute, **kwargs) model.export_to_xml() openmc.lib.init() - integrator.batchwise._set_cell_attrib(value_to_set) - assert integrator.batchwise._get_cell_attrib() == value_to_set + integrator.reactivity_control._set_cell_attrib(value_to_set) + assert integrator.reactivity_control._get_cell_attrib() == value_to_set - vol = integrator.batchwise._calculate_volumes() - for cell in integrator.batchwise.universe_cells: + vol = integrator.reactivity_control._calculate_volumes() + for cell in integrator.reactivity_control.universe_cells: assert vol[str(cell.id)] == pytest.approx([ mat.volume for mat in model.materials \ if mat.id == cell.id][0], rel=tolerance) @@ -167,7 +170,7 @@ def test_internal_methods(run_in_tmpdir, model, operator, integrator, nuclide, kwargs = {'bracket':[-1,1], 'bracket_limit':[-10,10], 'mat_vector':{}} - integrator.add_batchwise('fuel', 'refuel', **kwargs) + integrator.add_reactivity_control('fuel', 'refuel', **kwargs) model.export_to_xml() openmc.lib.init() @@ -175,12 +178,12 @@ def test_internal_methods(run_in_tmpdir, model, operator, integrator, nuclide, #Increase number of atoms of U238 in fuel by fix amount and check the # volume increase at constant-density #extract fuel material from model materials - mat = integrator.batchwise.material + mat = integrator.reactivity_control.material mat_index = operator.number.index_mat[str(mat.id)] nuc_index = operator.number.index_nuc[nuclide] vol = operator.number.get_mat_volume(str(mat.id)) operator.number.number[mat_index][nuc_index] += atoms_to_add - integrator.batchwise._update_volumes() + integrator.reactivity_control._update_volumes() vol_to_compare = vol + (atoms_to_add * openmc.data.atomic_mass(nuclide) /\ openmc.data.AVOGADRO / mat.density) @@ -188,14 +191,14 @@ def test_internal_methods(run_in_tmpdir, model, operator, integrator, nuclide, assert operator.number.get_mat_volume(str(mat.id)) == pytest.approx(vol_to_compare) x = [i[:operator.number.n_nuc_burn] for i in operator.number.number] - integrator.batchwise._update_materials(x) + integrator.reactivity_control._update_materials(x) nuc_index_lib = openmc.lib.materials[mat.id].nuclides.index(nuclide) dens_to_compare = 1.0e-24 * operator.number.get_atom_density(str(mat.id), nuclide) assert openmc.lib.materials[mat.id].densities[nuc_index_lib] == pytest.approx(dens_to_compare) volumes = {str(mat.id): vol + 1} - new_x = integrator.batchwise._update_x_and_set_volumes(x, volumes) + new_x = integrator.reactivity_control._update_x_and_set_volumes(x, volumes) dens_to_compare = 1.0e24 * volumes[str(mat.id)] *\ openmc.lib.materials[mat.id].densities[nuc_index_lib] assert new_x[mat_index][nuc_index] == pytest.approx(dens_to_compare) From 0ccc75aca0d24818537b3d32583e4fb15b608251 Mon Sep 17 00:00:00 2001 From: church89 Date: Fri, 15 Mar 2024 13:39:50 +0100 Subject: [PATCH 43/59] update regression reference result files --- .../ref_depletion_with_refuel.h5 | Bin 0 -> 39352 bytes .../ref_depletion_with_rotation.h5 | Bin 0 -> 39352 bytes .../ref_depletion_with_translation.h5 | Bin 0 -> 39352 bytes 3 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 tests/regression_tests/deplete_with_reactivity_control/ref_depletion_with_refuel.h5 create mode 100644 tests/regression_tests/deplete_with_reactivity_control/ref_depletion_with_rotation.h5 create mode 100644 tests/regression_tests/deplete_with_reactivity_control/ref_depletion_with_translation.h5 diff --git a/tests/regression_tests/deplete_with_reactivity_control/ref_depletion_with_refuel.h5 b/tests/regression_tests/deplete_with_reactivity_control/ref_depletion_with_refuel.h5 new file mode 100644 index 0000000000000000000000000000000000000000..a1820a3fc60cbd40b3cbf81283b881c4204fc662 GIT binary patch literal 39352 zcmeHPeQX@X6`#Gcog*>y0#R`Yl#QUpkS3ht1RqToUt%XQNC5{ZJy6@&u`jV(pFi%y zH7Qa|KSUzh-~`kjtxy7LC96`)RfWdTKZrt#(l(;56e^>rB`uVsNF`R)G)b#A-P!lv z?cLaW=Ue-T6~C2kXJ_Wkdo#bC-MZ!G&goX*O@$}K*8 z;NJThHBl}GuxPk3zYfbOm?G6{^UDDzg&0L1P<>Ti5N~=bK_AJbFg|h>qzBPl_^$kR zFIBG7xO;K|)MyAR27W*|`hiPTf3`b25K#R9@hF7j1J#mvLVTojw;}ieIl96zHV;Cw zLm9eK54ZL%*K#!et9Vt4fYoG8RG9M@pXlsvk4T?8Vg8~3Qz^jnX=0vOWR~mi=}Jb; zS;|7a9!-SAss&=zMa9o9Uf%fNf0jNRa+;s0{E&@X7dMXOB0&9@ z`YnxfDfDY3P2=56`860<%V;RO6 zrdc**A)uL~tQ?FXvS8*@0R)`60V zct(1hc-DIZ7-F7L58*H$0!}=u!hS?$OT{yFzH9Tt9nYw~wjOspBRx($8`D98c}7Ta zm=6Ibo(<}`rAo!K&?mr73)-{!;f`llV6|;M?s!IeoOl+x84NMc5O5CiBH+Zc(>LW7 zD;3X%mx7(6XwT+{JD$;NW?PRto{=6Wo(GI5n)wcDx z8_!6O6VG}BV2F8^+fO()IRZ{R)A2nzm6~auk=}-QP3>wkva!8pwS3hs!#Xp(83DoH|do8exi@#`PY_spEVI8_|pvAUV}fHKE)Hj<+Ot65|5zE4`l?!|xeMt_nZs z4B~eZsBXprdY_}#$M%cv<}p-%TaP>cN_zPFjTyV;m!%>nzZq6|L;Z>PHeE1?KPpe{ ze(#Pyv@gNdzaTB0x_QudBM{^BTtPtPkB$vb$w55psP6vs_SR^$^;S&V z))}|<*$}U2{LCtn_fiwyoc%GhAMN~SpcwI#{eNB<=X|Exe|?BZ82uUnME#Wd6{Wik zVIJzqe7`r2d?7nka`J2*LB+o|t$}os>&5tx0qJw?oEb+Iyxe{l@8nl}xIc*QKl_!r z*3*h>uajR%D?BA0Q@<+C%0y}6gX$NGO|XlX#clU zzSo>N$~>oiC*`Jxl=&VQ?#-BP8Sr|_4{fxrtZXSFf6^?v@R>psjL>-^9`J-hE0^Zw z&UZK#6WPWLG5{AE}y#IrFmEnHMNk&r$Pp=2&NIR~$3C zlWkp{?GcJ*zI&zw_>@{?()F0Bz|4$vZjW^Lw{|75Xrfc@N)jU8lZ>@Rwn!<TXA z9ddNG;Q{wgQ~nQuaB$y#kM9Y){aOBLSNDj9q>&OEx z>oJ1sNh(k4!Ktb{S>(Eso-6YDa;gZk93#L8FanGKBftnS0*nA7zz8q`i~u7reFU7I zC-|qRI*jXok}vf9VDQ;o#UxK5KOe}&hhvNYBftnS0*nA7zz8q`jKFL{!0q~>V~XpB zA&2z>uM=idFW|Z`0*nA7zz8q`i~u807y+kwW1;WD25{a;a`fC-wRpf`-bnIPUM7=hV^K(X`2!2OZI=MuFq(Hs%F`5o;zy1xO?oq9S6n@>o^{^uSy ze%95t`I*J{8v6LrYv(_9$_Q?%`&IOTe;c1VrH2llI8*<%H<$l-=$2USzgSkC4O#6t z6tTjDK34B7zcc#IhGmEL{qv7!ukWvEI6fNtN&M}ahI_Vq_Kdu{wqfX|uWr9^@b-qf z_g?C+TY6+&^Ix04{mPAhShs1_nhuH6zh2!LJhx_FW1#k%=Qd>F^m1SHPl=T_PN#SN zWiaydmon`h-FZBI@r}DO?S5g)??-m5wsBf<>ka>?IFZcAzqWqK>w(v8oSuASU*Nl6 zA2r_e{_u@2c>9dye@mU-diPP|s=o(+y|s1RICkNKuZ(Yc+<4^Jhe!52vB~&FZU2c^ w=AAM=d(Tf_O&-5sYz}D0zw_3a`d=oS&-m}}1DpsS3bSyk9PBgpzIC|%{~2>jiU0rr literal 0 HcmV?d00001 diff --git a/tests/regression_tests/deplete_with_reactivity_control/ref_depletion_with_rotation.h5 b/tests/regression_tests/deplete_with_reactivity_control/ref_depletion_with_rotation.h5 new file mode 100644 index 0000000000000000000000000000000000000000..00423a2893b9bc8171f877094ede7b54906e4d28 GIT binary patch literal 39352 zcmeHP4{Te-8NX*I#Z9v<84OYwowG3(x~`3rv@8wI+?mOPt12{?&;i zGuV_(P&#SavWcmN4J}Ax1R>GDiWU$|L6mI>2|7BqNSn3@nAD*Os#2s1t+2cIeV?Cw z$+P2^m`2U_B)@xi_ucp1{oeh)yL<1Qe;8_M{Y2T7WkMYuk0=op<|Umz!ei=wNTE38 zci@GA;b9CXTySAXjViAS(<@NF*9{4in11W-ZQF!!3DQp?W{a^^aOKB@9x1Jk4)e^X ziyeW`wyo`|jnrRK*x+OkFNzdYVt>{9b-4(c77gJM<+68=MB_uTfqqfpku)-Mf{a9p|D(_yjU&Edqj7vH#&UBU=+p0rBbj`Mo7LZIw%FCMQ=Phl;|BELP0}H zC>i1#)&CswtqOSrd^2qMmI9A}ZJ}ClxKa=xzJ;;DG%EP9{}2v7yr6ywtf1V&!#%gO zwHTsQB(Z3?CASXKDM*o;t-0lZlR}(89#DOCUJ!2v%RwK>r7%AANl5pjx$xI>+r3b^ zcH{2s8mQ42Rt)@ru=N8Ms{U+uc_5(s0pd{*#|Nq<@r3wD=}trN18Q`IW9AYF$qr?B zwR$+Ucd?eE@n6TQT-2IPHpD79fANS|e^*rb+&S|X4VZEPp3e}Mh|6TTk%8VspPZ#s zdK=M1P&BL&4IgQK_ICem*L~;PZ9%*FiSA$Xtp`r4$&fs}=2`mxO>nVP{SVc0uuKsk zzch*U3uBnv2p4wa-()@HPoTVAe+)N(KynKTrsd~f&-yO~0n*FkYBp{h%SM3uFZEj* z=Thj`NSemGrSfYqu9nbnAZkAamdH*R<4LSX1y*Dh`!~sXE`yAk%OR{nxzH7GL2`o_ zA6pCQUX+`>GFxs2`pX1R;CoK0P!_f>)`SjTN!EwBA?5|9i`IH9%JAW0zeniEI_e$d^C2PP=3hgbHXCAD!rN`NL zMtbacHdqUWm}lAjgk$p~V8=5rzDK80OU*OV+dSl}-z+2ByL_9~t9BKBRfantS+pnW z>aUma+v@7gH}W!6;wo-_h!T#1pbAxaswcfyRkBNQzslIgHAtw zCxPmgETH!}dVMUv=xiQC^|$mm^RJ|bzu#E0TYgb0vh$l^jW^Vvh;NGpgZQKK)av)n z_(S^=EIrQnLwbNe_WKJal~b1oeK!IzKCcu6bpCj;;VCtUXB;*C8xBPJ`Xbk1T6b*7 z+-F0)qVY4MNX|<`xU=@h(0;UYH$XAsDf|DNFs}Gaz5jX;kudr-0*Lx4^(#tu8p1r( zmHvM3H1dV)RH@0cJp7t}4c!RoBsYlhu>hp6v~zkK)$nrqUA&!N@!Lrugq*EqJGLOy6~k!6pYY$E*|iN!YEbd)y{V~ zAZ9(sAUr5PA1)+UI6m-T!R=b**=toL6Cb6ThBgPWBIdeP~=^et1{zPYQ ztSd^<^mosc0H0Eebh_D`CB+{F}qQfz@D@ll-L8^ww|SNBzuJB2fZQUV z=i&kV$8criLG2|RC|-E`;f}an?o`}re@5GrMQ(9!^$W#=_)U0Fd=Yq@xXZ#rsY8w~ zH$33}8QT9L5Dw1U@ABMhwLil@9nO8jKIUAQx0ed4)nse#3Is8`r!lu&4Q1>5c(na% zJt!AyrULVa9hmHInm_+Sr_SYB(fXDDC(zgoPN@XCgBKbJu&>q=#ytOgts}=B)?*6S zlT@D8g9}wRTj08qo-6YDa-j&b93#L8FanGKBftnS0*nA7zz8q`i~u9Bcm(X8CsZs@ zbr{$GB%km3!RY7=h)4fYbFu&jQyCV>asr zUMDQ4Uchx>1Q-EEfDvE>7y(8gKLU31#(dv}C2`(Ja`fC-w>WMyZzOptub1OEBftnS z0*nA7zz8q`jKK0jpwM~aXrB4vgv~sW=ZDMdzH@CD0Y-okU<4R}LJ%l)zHm6td|{I3 z3x&YXvWx&Dzz8q`jKD>SK=tQ>PjC6Kw&fqyuLPbx_Fzlv_B%R$^w60WYV5Y#|NQuK z&mBwuEA&tIJhSi7XMT?3@x|%D^BXI7oo{$E@Ym6!z2|4{4zwM; z>KCu{M*_cnr)k5rPrea&>&K7R#`c{FBwKgh8GQZS#;>f~+B*9{BKuz~tKJWq?U;_5 zVdU)J8oPJD{k_Bsq3IXKK67$yrLVbSW5>VWebU$b_t!@1eE$nJ?>MmU`^`HyH8&k? zh#$G;k4=N$D7)nc$7Y%i_I_}e!l@=u_v}Lwr^lYXVoxSc$@lBl`(CnedSHFo6MNsY za610d>$~H-ES$FQ`tQLTq83hNsSkGlZi9u>JKZ1dm^t)F8mG2+=Y5I8Cjy=WzBRsE zB~ERtcfKcan)uZ}BgeM9894rjr`CO~`|iMNV-MeWs4Eiq&*qsO4?p-uVEV+re!uVL oGl9#WICS&18%{SKeD%I$x=(#fJ#h5;gCXXW1njm|LM*b zxirKE)gcyz8dbFCR;V!w$WVliDk2UFkf>08RayxN7mx_Js)B&F3baZqpzQ2>@Ahu& zx$~`kjTOI@Zf9ra&3iN7&i7_!cjrb!^^L1mtXm=E;q!?iQD$G#=^Z?##vz5`l;46E z28Ks5obt zgVn>~AihvoDo|crX_fbhj;`*=zytje6c^W6f~&0v$+t)PEdk4-KN^X}y9Z(@Xjl?b zhWJMHzleM*M;-y+3@zUh;1RGbR0|H53j)NqFgBP*g#h*+!l4>3s9yprC^!Fb-$(Y= z8=_bYV$pDYb{)1;utX|%XO{y`3h@H+fajJRsQxwAdf;_A8L|$)eYX7nZE&$z{SVc0utX6c zzm&xKg)tnw6)tq+UveAdPoli8KZdu1z~D|4Ov%r^o()_B0;HG6)lA$tmWcrMU+T9s z&Lz;Vku;5Wi{;l~TrHyEKvZ1~mdH*RV}E7C%%>a(==@a(`w33sFdG6oo+WYoq_TzLnL1uO{BXy!BCN2Z#~sf| zkB(>kH-I7L8TAkjvmv14SvmG2DqAR?sqy^P;~DAE@od@;63jC~io;fFh(U5V9p^tj_0>Cy2l_&zYiJVU@a$cliDXXiI$7Aq9b zCM&?sFVLRD4|hDH*UXL{cRV9KI-X5#f_gL0GW!X~=0-rrvnK3ERH{%sE87Sbs?eUp z4|hDn*QyJC-bi|MJS)2e>dic(9>QTZ1av%`+LBqSP&^x~1Um_|w@{w>u-c9ucjFo9 z(ebRm3JfvNGW!X~=0-rrGe5pZr&5c}GtyfZtKPQ5itK5x-XUML%W#(!eh`vHQ+(^T z+pYM$TesP7q@ext}~H_Vz>gV zlbkwFwi{uIoX7PZ$*JRf5F62i6(Bj)PlZr!3ddWLJBM+B_m$qyOyl>ABv+0fbO!J{ z2~@Xe0lm*r>*M%Eck>vkzoW;Ue=>h)e_ZK9kQ@0NKZUka{UM>iz{PAPM6LJtwJ1TqMe_v})PwQq(>*$Kv z`)r6;G=8QP$$DuBZ^r%@+K+be4k$)EW&fWQ#$}(W_Fo?&5=Os908u}senshSLzstp zQs3{rj(j0I<#O_D9RbC^hVF!PlIzF#mQtAGvrNGLJbln%}J=oeE$D#vWa#xZNvA%e;Ez&HdYzxl5X^@cL zRX4Zw^$s*g`}zhP+|_l-?E44pH@tJ-Ke`X<{i5{z?~mWEzt>X~?y#>?{a3j!Es$H_ z^K3kz{}`T3JgB{d1I6=iKimc?N4j_V1ZklUHyFVAbt}b6kh-yFC234km``5 zOAQaWe}?jZ2!wZrinVGAY)YUj?s)Ty(1Rcx^?6+m-U#! z^(2+2_27Kfoy&7wNzWB|eK}u*S&k841Q-EEfDvE>7y(9r5nu!u0Y-okSU3W@=Lu!= zR2|0kKgs8MelR?qshH#`NfPc_YbFd9@tJ839Is z5nu!u0Y-okU<8&H0{PAxhjYvqCpGg#o*yo)`_8pt1Q-EEfDvE>@7=grqHk`iY zJ8!m_H@*DehF#S&=84aI>YlfcUob!UwI6ggz4cm6b9eQRhgJ<_{)=V#8$r7rXCij^ z%vYvsCRUEF*|)#q%!gtfPrtmjy6(w4?z(Q(kE-h$Z~jB{=PP#CePYwz-?SXKt#0_h zzutQ2ug}$f<@qC;MDl))YE;B zIyjxIKXlSK;NVnq)8GHO{v`*e-|sy1+!JFCPLuJ6U-|xyZ>MnD{L220JI7x%XTJAn z>2*UR=1X5K8mirR*7Vi}-yHqGj5&FJ^*4JyKVtsl_Sz%sg01GWkL*7FrMqX$!##g) nYfoM<$CKWxK6>)CnvFxB^Z$%+B77)J!)e7&+ass{=0Ed)t?oyi literal 0 HcmV?d00001 From da184bbc0765e967401ae2319390f5b770515fbd Mon Sep 17 00:00:00 2001 From: church89 Date: Mon, 18 Mar 2024 12:46:26 +0100 Subject: [PATCH 44/59] few fixes --- openmc/deplete/reactivity_control.py | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/openmc/deplete/reactivity_control.py b/openmc/deplete/reactivity_control.py index 8191d88ef7f..91951e86ed2 100644 --- a/openmc/deplete/reactivity_control.py +++ b/openmc/deplete/reactivity_control.py @@ -251,12 +251,9 @@ def _search_for_keff(self, val): guesses, keffs = search # Check if all guesses are within bracket limits - if all( - self.bracket_limit[0] < guess < self.bracket_limit[1] - for guess in guesses - ): + if self.bracket_limit[0] < all(guesses) < self.bracket_limit[1]: # Simple method to iteratively adapt the bracket - print( + warn( "Search_for_keff returned values below or above " "target. Trying to iteratively adapt bracket..." ) @@ -266,7 +263,7 @@ def _search_for_keff(self, val): # to the closest guess value if abs(np.diff(keffs)) < 1.0e-5: arg_min = abs(self.target - np.array(keffs)).argmin() - print( + warn( "Difference between keff values is " "smaller than 1 pcm. " "Set root to guess value with " @@ -300,7 +297,21 @@ def _search_for_keff(self, val): slope * (min(keffs).n - self.target) * dir ) - #check if adapted bracket are within bracketing limits + #check if adapted bracket lies completely outside of limits + if not ( + self.bracket_limit[0] < all(bracket+val) < self.bracket_limit[1] + ): + # Set res with closest limit and continue + arg_min = abs(np.array(self.bracket_limit) - bracket).argmin() + warn( + "Adaptive iterative bracket went off " + "bracket limits. Set root to {:.2f} and continue." + .format(self.bracket_limit[arg_min] + ) + ) + root = self.bracket_limit[arg_min] + + #check if adapted bracket ends are outside bracketing limits if bracket[1] + val > self.bracket_limit[1]: bracket[1] = self.bracket_limit[1] - val if bracket[0] + val < self.bracket_limit[0]: @@ -845,7 +856,7 @@ def _calculate_volumes(self): res = openmc.VolumeCalculation.from_hdf5("volume_1.h5") for cell in self.universe_cells: mat_id = cell.fill.id - volumes[mat_id] = res.volumes[mat_id].n + volumes[str(mat_id)] = res.volumes[cell.id].n volumes = comm.bcast(volumes) return volumes From 64543ffab570c5f8fd16f0f26be44a51cd098db3 Mon Sep 17 00:00:00 2001 From: church89 Date: Mon, 18 Mar 2024 12:46:44 +0100 Subject: [PATCH 45/59] fix unit test failing --- tests/unit_tests/test_deplete_reactivity_control.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/tests/unit_tests/test_deplete_reactivity_control.py b/tests/unit_tests/test_deplete_reactivity_control.py index 98944f42d80..0f087be6ea0 100644 --- a/tests/unit_tests/test_deplete_reactivity_control.py +++ b/tests/unit_tests/test_deplete_reactivity_control.py @@ -28,7 +28,7 @@ def model(): w.add_element("O", 1) w.add_element("H", 2) w.set_density("g/cc", 1.0) - w.temperature = 273.15 + w.temperature = 293.15 w.depletable = True h = openmc.Material(name='helium') @@ -146,14 +146,17 @@ def test_cell_methods(run_in_tmpdir, model, operator, integrator, obj, attribute model.export_to_xml() openmc.lib.init() + integrator.reactivity_control._set_lib_cell() integrator.reactivity_control._set_cell_attrib(value_to_set) assert integrator.reactivity_control._get_cell_attrib() == value_to_set vol = integrator.reactivity_control._calculate_volumes() + integrator.reactivity_control._update_x_and_set_volumes(operator.number.number, vol) + for cell in integrator.reactivity_control.universe_cells: - assert vol[str(cell.id)] == pytest.approx([ - mat.volume for mat in model.materials \ - if mat.id == cell.id][0], rel=tolerance) + mat_id = str(cell.fill.id) + index_mat = operator.number.index_mat[mat_id] + assert vol[mat_id] == operator.number.volume[index_mat] openmc.lib.finalize() From 9328e5b930524d39925b16ff4389629a6211837b Mon Sep 17 00:00:00 2001 From: church89 Date: Tue, 19 Mar 2024 11:26:42 +0100 Subject: [PATCH 46/59] explicit path argument in SIIntegrator step result save method, since right thi class is not supported --- openmc/deplete/abc.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py index e77ff1d11e1..f79f7d941a0 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -1063,13 +1063,13 @@ def integrate( n = n_list.pop() StepResult.save(self.operator, n_list, res_list, [t, t + dt], - p, self._i_res + i, proc_time, path) + p, self._i_res + i, proc_time, path=path) t += dt # No final simulation for SIE, use last iteration results StepResult.save(self.operator, [n], [res_list[-1]], [t, t], - p, self._i_res + len(self), proc_time, path) + p, self._i_res + len(self), proc_time, path=path) self.operator.write_bos_data(self._i_res + len(self)) self.operator.finalize() From a4731ab2b9b14288467bcf8326b0065634d2fa23 Mon Sep 17 00:00:00 2001 From: church89 Date: Tue, 19 Mar 2024 13:32:17 +0100 Subject: [PATCH 47/59] add reactivity control restarting condition to integrator class --- openmc/deplete/abc.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py index f79f7d941a0..b782ef830d2 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -825,7 +825,10 @@ def integrate( n, res = self._get_bos_data_from_operator(i, source_rate, n) else: n, res = self._get_bos_data_from_restart(i, source_rate, n) - + if self._reactivity_control: + root = self.operator.prev_res[-1].reac_cont + else: + root = None # Solve Bateman equations over time interval proc_time, n_list, res_list = self(n, res.rates, dt, source_rate, i) From ee13d16d7dc48ebded8a1c08d7aa138992506dfb Mon Sep 17 00:00:00 2001 From: church89 Date: Wed, 20 Mar 2024 10:39:52 +0100 Subject: [PATCH 48/59] remove unused index argumnet in restart method to comply with latest develop branch --- openmc/deplete/abc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py index 86275a54ab5..beb92dbb234 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -848,7 +848,7 @@ def integrate( root = None n, res = self._get_bos_data_from_operator(i, source_rate, n) else: - n, res = self._get_bos_data_from_restart(i, source_rate, n) + n, res = self._get_bos_data_from_restart(source_rate, n) if self._reactivity_control: root = self.operator.prev_res[-1].reac_cont else: From 12a1e70af7bd728798a87f913e6f2b2624ac9159 Mon Sep 17 00:00:00 2001 From: church89 Date: Thu, 21 Mar 2024 10:32:55 +0100 Subject: [PATCH 49/59] make sure cell attribute vector maintain initial coordinates --- openmc/deplete/reactivity_control.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/openmc/deplete/reactivity_control.py b/openmc/deplete/reactivity_control.py index 91951e86ed2..cfc158e5111 100644 --- a/openmc/deplete/reactivity_control.py +++ b/openmc/deplete/reactivity_control.py @@ -742,8 +742,6 @@ class GeometricalCellReactivityController(CellReactivityController): OpenMC Cell identifier to where apply batch wise scheme attrib_name : str {'translation', 'rotation'} Cell attribute type - vector : numpy.ndarray - Array storing vector of translation or rotation axis : int {0,1,2} Directional axis for geometrical parametrization, where 0, 1 and 2 stand for 'x', 'y' and 'z', respectively. @@ -792,9 +790,6 @@ def __init__( # check if universe contains 2 cells check_length("universe cells", self.cell.fill.cells, 2) - # Initialize vector - self.vector = np.zeros(3) - self.universe_cells = [ cell for cell in self.cell.fill.cells.values() if cell.fill.depletable ] @@ -829,8 +824,13 @@ def _set_cell_attrib(self, val): val : float Cell coefficient to set, in cm for translation and deg for rotation """ - self.vector[self.axis] = val - setattr(self.lib_cell, self.attrib_name, self.vector) + if self.attrib_name == "translation": + vector = self.lib_cell.translation + elif self.attrib_name == "rotation": + vector = self.lib_cell.rotation + + vector[self.axis] = val + setattr(self.lib_cell, self.attrib_name, vector) def _initialize_volume_calc(self): """Set volume calculation model settings of depletable materials filling From f67b2311a39d4e1ea685c523b80137b9fc30deab Mon Sep 17 00:00:00 2001 From: church89 Date: Thu, 21 Mar 2024 15:36:49 +0100 Subject: [PATCH 50/59] make sure bracket type is an array and apply black code style --- openmc/deplete/reactivity_control.py | 55 ++++++++++++++++++---------- 1 file changed, 36 insertions(+), 19 deletions(-) diff --git a/openmc/deplete/reactivity_control.py b/openmc/deplete/reactivity_control.py index cfc158e5111..9f35b5cca68 100644 --- a/openmc/deplete/reactivity_control.py +++ b/openmc/deplete/reactivity_control.py @@ -297,21 +297,23 @@ def _search_for_keff(self, val): slope * (min(keffs).n - self.target) * dir ) - #check if adapted bracket lies completely outside of limits + # check if adapted bracket lies completely outside of limits if not ( - self.bracket_limit[0] < all(bracket+val) < self.bracket_limit[1] + self.bracket_limit[0] + < all(np.array(bracket) + val) + < self.bracket_limit[1] ): # Set res with closest limit and continue arg_min = abs(np.array(self.bracket_limit) - bracket).argmin() warn( "Adaptive iterative bracket went off " - "bracket limits. Set root to {:.2f} and continue." - .format(self.bracket_limit[arg_min] + "bracket limits. Set root to {:.2f} and continue.".format( + self.bracket_limit[arg_min] ) ) root = self.bracket_limit[arg_min] - #check if adapted bracket ends are outside bracketing limits + # check if adapted bracket ends are outside bracketing limits if bracket[1] + val > self.bracket_limit[1]: bracket[1] = self.bracket_limit[1] - val if bracket[0] + val < self.bracket_limit[0]: @@ -586,30 +588,45 @@ def _get_cell(self, val): ] if isinstance(val, Cell): - check_value("Cell exists", val, - [cell_bundle[2] for cell_bundle in cell_bundles]) + check_value( + "Cell exists", val, [cell_bundle[2] for cell_bundle in cell_bundles] + ) elif isinstance(val, str): if val.isnumeric(): - check_value("Cell id exists", int(val), - [cell_bundle[0] for cell_bundle in cell_bundles]) + check_value( + "Cell id exists", + int(val), + [cell_bundle[0] for cell_bundle in cell_bundles], + ) - val = [cell_bundle[2] for cell_bundle in cell_bundles \ - if cell_bundle[0] == int(val)][0] + val = [ + cell_bundle[2] + for cell_bundle in cell_bundles + if cell_bundle[0] == int(val) + ][0] else: - check_value("Cell name exists", val, - [cell_bundle[1] for cell_bundle in cell_bundles]) + check_value( + "Cell name exists", + val, + [cell_bundle[1] for cell_bundle in cell_bundles], + ) - val = [cell_bundle[2] for cell_bundle in cell_bundles \ - if cell_bundle[1] == val][0] + val = [ + cell_bundle[2] + for cell_bundle in cell_bundles + if cell_bundle[1] == val + ][0] elif isinstance(val, int): - check_value("Cell id exists", val, - [cell_bundle[0] for cell_bundle in cell_bundles]) + check_value( + "Cell id exists", val, [cell_bundle[0] for cell_bundle in cell_bundles] + ) - val = [cell_bundle[2] for cell_bundle in cell_bundles \ - if cell_bundle[0] == val][0] + val = [ + cell_bundle[2] for cell_bundle in cell_bundles if cell_bundle[0] == val + ][0] else: ValueError(f"Cell: {val} is not supported") From 06366b578a075edfd2b4f878315f07e305e5c66d Mon Sep 17 00:00:00 2001 From: church89 Date: Thu, 21 Mar 2024 15:37:20 +0100 Subject: [PATCH 51/59] fix failing regression test --- .../regression_tests/deplete_with_reactivity_control/test.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/regression_tests/deplete_with_reactivity_control/test.py b/tests/regression_tests/deplete_with_reactivity_control/test.py index 1f1fa543d56..006187417a1 100644 --- a/tests/regression_tests/deplete_with_reactivity_control/test.py +++ b/tests/regression_tests/deplete_with_reactivity_control/test.py @@ -81,7 +81,7 @@ def model(): @pytest.mark.parametrize("obj, attribute, bracket_limit, axis, vec, ref_result", [ ('trans_cell', 'translation', [-40,40], 2, None, 'depletion_with_translation'), ('rot_cell', 'rotation', [-90,90], 2, None, 'depletion_with_rotation'), - ('f', 'refuel', [-100,100], None, {'U235':1}, 'depletion_with_refuel') + ('f', 'refuel', [-100,100], None, {'U235':0.9, 'U238':0.1}, 'depletion_with_refuel') ]) def test_reactivity_control(run_in_tmpdir, model, obj, attribute, bracket_limit, axis, vec, ref_result): @@ -118,5 +118,4 @@ def test_reactivity_control(run_in_tmpdir, model, obj, attribute, bracket_limit, res_ref = openmc.deplete.Results(path_reference) # Use high tolerance here - assert [res.reac_cont for res in res_test] == pytest.approx( - [res.reac_cont for res in res_ref], rel=2) + assert res_test[0].reac_cont == pytest.approx(res_ref[0].reac_cont, rel=2) From fbf91c4c34cad5e6b168627dd94cdc1413fc09de Mon Sep 17 00:00:00 2001 From: church89 Date: Thu, 21 Mar 2024 15:37:44 +0100 Subject: [PATCH 52/59] update reference result files --- .../ref_depletion_with_refuel.h5 | Bin 39352 -> 38432 bytes .../ref_depletion_with_rotation.h5 | Bin 39352 -> 38432 bytes .../ref_depletion_with_translation.h5 | Bin 39352 -> 38432 bytes 3 files changed, 0 insertions(+), 0 deletions(-) diff --git a/tests/regression_tests/deplete_with_reactivity_control/ref_depletion_with_refuel.h5 b/tests/regression_tests/deplete_with_reactivity_control/ref_depletion_with_refuel.h5 index a1820a3fc60cbd40b3cbf81283b881c4204fc662..26bd4839da3a1c67c9127ed0fa5ee7126340f53f 100644 GIT binary patch delta 1030 zcmZvYe`r%z6vyAa&n5RgYVr!YR>34dlYVcqLqTK&Td_uPBF z=X1_I_Y6~JJjC%JBX1_yV;Ry_20SSOSpHmBaj70@duo*Hsf-!u>_GCM@(S@GMqBNx;4W)|VTS+(S2-80)#MP| z(Oao(%$Gv0f;gRBbWAQK!~Dw^%!~J(t@4*+bmdEcK~q^ zt23*1u+BF>9MJgb2WB=0L`tdgZpo_JJJS03=}qsUsIC4K!i5$X@=jst1Avvbl$r+84p;WHILITb;E<+qG-G7av$K)KV&b1sxV@fS27s)8>XspHI8B%F{ zY~;p^F+r_j#rXuiyHi)f<=;`wcrro%X*2NLWKkt5YvxF@*e86&meJ6WJjXE!O_~PE zu#lupCk=!n@2Xt+Od9nb(UWg2=K5bVD$}W_NrKX zgLy3-ZSOJsTsPzQbhPy|6YkmgvLzoFetDXn^*7$%z5n#3y|etpi`M&zRNrj;;6qb< z%h>O3kw@LkB-`;=GJoEbYQ6rExZ};i z^;LecpGq5kxar#r#%FxZxbN$wj^ooG-~9N=(7I4E&~`qT|F~-cx^?DQ_9P_EmX%DG M7p}N1Nw_Ed2j7}FVgLXD delta 1335 zcmZuvZA@EL816ll_TG*`$;N=7Yl#R|0<`N!3+2)e%Ew6RS~T;H$TmQs*+eJU1SAj9`vqYB8ta-Eg5v#cEY3NyfZM#mx!pH!CV$h2~(Kj z=!F=J0D4Q2f4wG@47if`AWyRPKf{OlWE|Yd2M4S&ey7aJuxTucFgNW`W}CowbZGo;d&%}_6=z>|hUeOfU?MXM2_lyEko8hlY=FmKU;Sb+m8w<1qA zlcU&%kShyHmHS~{6;J$~!=25{SfaK5!faUF^}61d#YHi04KZa6imcm1w)%LH|v0GQmg-*a+}d>Ja6E zLv<-MixZCSATikZag3vCVH2`GP7=;ghOt#$CBWULpjjc&;^OdlfC0I?nW>`!5#iVn zjYDU;nFnoAIPvbrXpAS+FGz^_&;3Yf|9EY><0ZMqxVQ}ORNtqKpKcoC6b2p#W##VH(U{&G#?z|1I(~Uq zC~x5<_I=b#Nm=q}R$}jdm5qGmlp<}aXM+6lMmzuf zarwz+Mg9!i@cEG&iu{rq)G>TppYMG7jhfY=%zV|IbIq!xafA6+^LrO0KNw6qHP(PC ze~sAHE1FKDT>17&;RaRC+iHJm+_A38g8diAtIWCZEm`}F{m7$Bd7*3a`VU+iZr!ym zOUAQLJrK+_g$uqdNW3V&7?y?CUe+l;{T54J;eT@JWxkC}|IM{fxqE_)`dvL)S>Yq6 zR~HZaOvlNx>HFgyCrsqm%I3KX!h2+H$rmF{GpnRUz73ms|N4^ln4k+w zZ9Z+O5Ns(FVxgTZBdLvEd?8}?g4U-&5&KXyDoP4Qx9-$}4xICx=l}f9|2*gPQ|#-; zE;Uwp-J0{5F~if+J-wk52vj_Q9xNaMkYPJju}v(BLJ%t^N$02fcJW zVPXandxPH*Z)4fM{Vl=PHtA8w@98Q!zP`0^v*ozRc>5)ng4yklivLhlU2+!T=nm*BjbU8_?K3AK>^KaMEivpn2^r-be?FzrN+4=g@g=ea z!>o1(7ec^FT68MT5f{VYw<AgBt zuk*ZS7Nd~yYrUrJ(aY0D+vtIT+=l_;MCvugd=TYzMsgmq(iUWW0us(B!rEHr2=E+HGMt#OyLdhxRzMy%d+kjTNjUyhO+h1WcI8MMP8|PF z)$3-rpOE3b5j{cW-WwOLTp6dLIkS7R>GR1?BDW|Is8?OmrAE~Tz8$k&e?$AxMn3=P zz?5*?z70Owl$y6IV`Kwwo;NyHFV0Z`kN(+v$Bt3^mraMJr~O(gkeg^)%fCx~?-)I> zws4ec7~S^aw+Ag0d*6^F8@of@J9$CzYSRj3uc|wwzx6=(ytJ%p@ocvaf@hLotzX=0 zS^YuRW?p`~`)ZB3VFwloPY%e%pHOLf>Z_wls9L_IDrWD7;;{MfqaLmIk8k z;k2I``S>TxxaKZ3_02_TN!wBCrt^Gp-#!cV>&}JR^MiLNKlk(3P4z2OvM;%>UX~No h+2;D~x-&hxCzDGa=^@zv%u``&-n#Bt`|fm&{~wHoh|&N6 diff --git a/tests/regression_tests/deplete_with_reactivity_control/ref_depletion_with_translation.h5 b/tests/regression_tests/deplete_with_reactivity_control/ref_depletion_with_translation.h5 index 6b5b10b6782137a830491de569a298454537778d..66c12256aa4019abf49bd87625d93779c44d1da3 100644 GIT binary patch delta 987 zcmZvYZEQ@9)T@;ZnvpFnFfOEFHAfmyeS!5RXRJ9lAEiQ0o)rbfLQG_h1~D3y~%>c*(N?Hlk6 z(fv6Ix+T?Y*sKB`in1#)GDFw22Hl@iFx}dOSO{1R%GzsnW~APMBG%88IA7GW+3=d0%; zmS*j|!_sCk#wh=)NB)3R!HOx;59_Nduy>41 z>5m;5IyRY7iNYFK5=!+6+nHSIIS^-AO;D9afn3amXw9(OUxM{_U#S0|M(#^-+eXh5><*=}_^0U6h((rW3vp4DMEe|G_ zJ^C;)VJrN(=}t%24%pn5SM~Az3)`*ehvu_UK3Zfl)VuV>&b4=^Keya2cBY%GPtu6p z*-IuB&RA3A%zkyF``V}bj(6hhua{be9Cbg*!SWHo)h&O!66x!3We#tsIu-c>5v?I0 UAA?|fZu+Qw(>#--YaJi`1ywgGLI3~& delta 1284 zcmZuveN0oWeV%Q$Tb8we2Dil&9uZtnX{IBfaV0U4;UA)17W~>OI&_O8wtFqr#FxB#&pE%} zxxbU&z0S*MLO?b(nsD%$ClFUMaz%!S4sgkoyw@b8NV}zoweX%~n39o5Z4O3DGL~Rr zgmwrEQ7h{RX2kO+A!jAx*OARXlT5jD#cUX6q)IuvBZTQ-HcBOw#V*AY6!4pO1=hJJ z<)A%P3Yt`IAYBTJ+emP3s~hCh(zn%|;LY-)^-TNrTmziX&Lvphvj=Q486kt5oEd_F z{d5LD%|Nq;1$Pz+%~@pv8=Hp^n+q3{8o?hE2h3&@#A!+JYB=OJ2>IGA2-$T|nOg&k zdIj}YzBn5rRKywsjRyE>=Zj{)POK8*CQ<0cAkQG~icr0*Kn%jX=1^_}531$8c1V$u z@<=lb==V`%k=PSbbmPFN&r{(>45Rrk6LK&YDg*121$4sHu82lZC-}?3Szr&~zZE3{ zQy3SB4ixTAAou+bQRD@h0O2(rJC-2cy6CsXMMenD*rQeU?aQ>CO=ea{*RcBs1Y{?IbsfBHj?xp&7{`-1ipYxGM-@SjeA2dAQ} zqbKH#t_^*g_Ex1%?QUzlbTdg?d@jEzA^Kx&aj9ydby{vNemY~%mvuEc#oaZ(-#hlh zHG1Fz+m4RR(^Zcpb?H_$mPUMb$TfD}^41@1d;49>maq$(^l_dmvk_NHZ%s9YqmUbtE#cUF^7F0z{Uh?55B># zv(o7vETv5=eT6|c)1UE9saJq!M%(f?k&=oit#(f hbnB}rp9gGtIJx4k3H+xX*%-7&{#nCwlRu_R{R4K0g$4is From 21e1ac6bb7ffb2196f98de612b62a74d9c605cd9 Mon Sep 17 00:00:00 2001 From: church89 Date: Tue, 26 Mar 2024 09:33:30 +0100 Subject: [PATCH 53/59] change warning message --- openmc/deplete/reactivity_control.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openmc/deplete/reactivity_control.py b/openmc/deplete/reactivity_control.py index 9f35b5cca68..3250f465d71 100644 --- a/openmc/deplete/reactivity_control.py +++ b/openmc/deplete/reactivity_control.py @@ -323,7 +323,7 @@ def _search_for_keff(self, val): # Set res with closest limit and continue arg_min = abs(np.array(self.bracket_limit) - guesses).argmin() warn( - "Adaptive iterative bracket went off " + "Search_for_keff returned values off " "bracket limits. Set root to {:.2f} and continue.".format( self.bracket_limit[arg_min] ) From 6ad6ea094da85caa5bc87297594bee793c9a5e7e Mon Sep 17 00:00:00 2001 From: church89 Date: Mon, 8 Apr 2024 10:04:13 +0200 Subject: [PATCH 54/59] restore check conditions in adaptive search_for_keff routine --- openmc/deplete/reactivity_control.py | 28 +++++++++++++--------------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/openmc/deplete/reactivity_control.py b/openmc/deplete/reactivity_control.py index 3250f465d71..10d5984170f 100644 --- a/openmc/deplete/reactivity_control.py +++ b/openmc/deplete/reactivity_control.py @@ -251,7 +251,8 @@ def _search_for_keff(self, val): guesses, keffs = search # Check if all guesses are within bracket limits - if self.bracket_limit[0] < all(guesses) < self.bracket_limit[1]: + if all(self.bracket_limit[0] <= guess <= self.bracket_limit[1] \ + for guess in guesses): # Simple method to iteratively adapt the bracket warn( "Search_for_keff returned values below or above " @@ -298,24 +299,21 @@ def _search_for_keff(self, val): ) # check if adapted bracket lies completely outside of limits - if not ( - self.bracket_limit[0] - < all(np.array(bracket) + val) - < self.bracket_limit[1] - ): - # Set res with closest limit and continue - arg_min = abs(np.array(self.bracket_limit) - bracket).argmin() - warn( - "Adaptive iterative bracket went off " - "bracket limits. Set root to {:.2f} and continue.".format( - self.bracket_limit[arg_min] - ) - ) - root = self.bracket_limit[arg_min] + msg = ("WARNING: Adaptive iterative bracket {} went off " + "bracket limits. Set root to {:.2f} and continue." + ) + if all(np.array(bracket)+val <= self.bracket_limit[0]): + warn(msg.format(bracket, self.bracket_limit[0])) + root = self.bracket_limit[0] + + if all(np.array(bracket)+val >= self.bracket_limit[1]): + warn(msg.format(bracket, self.bracket_limit[1])) + root = self.bracket_limit[1] # check if adapted bracket ends are outside bracketing limits if bracket[1] + val > self.bracket_limit[1]: bracket[1] = self.bracket_limit[1] - val + if bracket[0] + val < self.bracket_limit[0]: bracket[0] = self.bracket_limit[0] - val From efc1f617e70632966cad839d699901250b808122 Mon Sep 17 00:00:00 2001 From: church89 Date: Tue, 16 Apr 2024 14:22:06 +0200 Subject: [PATCH 55/59] made ReactivityControllers settable through the integrator --- openmc/deplete/abc.py | 35 +++++++++++++++++++++++++--- openmc/deplete/reactivity_control.py | 21 +++++++++++++---- 2 files changed, 49 insertions(+), 7 deletions(-) diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py index beb92dbb234..a1ab198c297 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -31,8 +31,10 @@ from .transfer_rates import TransferRates from openmc import Material, Cell from .reactivity_control import ( + CellReactivityController, GeometricalCellReactivityController, TemperatureCellReactivityController, + MaterialReactivityController, RefuelMaterialReactivityController ) @@ -714,6 +716,29 @@ def solver(self, func): def reactivity_control(self): return self._reactivity_control + @reactivity_control.setter + def reactivity_control(self, reactivity_control): + if isinstance(reactivity_control, CellReactivityController): + if not isinstance(reactivity_control.cell, Cell): + raise TypeError( + "Reactivity control attribute must be Cell " + "not {}".format(type(reactivity_control.cell))) + elif isinstance(reactivity_control, MaterialReactivityController): + if not isinstance(reactivity_control.material, Material): + raise TypeError( + "Reactivity control attribute must be Material " + "not {}".format(type(reactivity_control.material))) + else: + raise TypeError( + "Reactivity Control must either be " + "CellReactivityController, or MaterialReactivityController, " + "not {}".format(type(reactivity_control))) + + check_value('attribute', reactivity_control.attrib_name, + ('translation', 'rotation', 'temperature', 'refuel')) + + self._reactivity_control = reactivity_control + def _timed_deplete(self, n, rates, dt, matrix_func=None): start = time.time() results = deplete( @@ -918,7 +943,11 @@ def add_transfer_rate( self.transfer_rates.set_transfer_rate(material, components, transfer_rate, transfer_rate_units, destination_material) - def add_reactivity_control(self, obj, attr, **kwargs): + def add_reactivity_control( + self, + obj: Union[Cell, Material, int, str], + attr: str, + **kwargs): """Add reactivity control to integrator scheme. Parameters @@ -930,7 +959,7 @@ def add_reactivity_control(self, obj, attr, **kwargs): are: 'translation', 'rotation', 'temperature' for an openmc.Cell object; 'refuel' for an openmc.Material object. **kwargs - keyword arguments that are passed to ReactivityController. + keyword arguments that are passed to the ReactivityController class. """ check_value('attribute', attr, ('translation', 'rotation', @@ -943,7 +972,7 @@ def add_reactivity_control(self, obj, attr, **kwargs): reactivity_control = RefuelMaterialReactivityController self._reactivity_control = reactivity_control.from_params(obj, attr, - self.operator,**kwargs) + self.operator, **kwargs) @add_params class SIIntegrator(Integrator): diff --git a/openmc/deplete/reactivity_control.py b/openmc/deplete/reactivity_control.py index 10d5984170f..8ee417a12f7 100644 --- a/openmc/deplete/reactivity_control.py +++ b/openmc/deplete/reactivity_control.py @@ -31,9 +31,14 @@ class ReactivityController(ABC): variable with the aim of satisfy certain design criteria, such as keeping keff equal to one, while running transport-depletion calculations. - Specific classes for running reactivity control depletion calculations are - implemented as derived class of ReactivityController. - + This abstract class sets the requirements + for the reactivity control set. Users should instantiate + :class:`openmc.deplete.reactivity_control.GeometricalCellReactivityController` + or + :class:`openmc.deplete.reactivity_control.TemperatureCellReactivityController` + or + :class:`openmc.deplete.reactivity_control.RefuelMaterialReactivityController` + rather than this class. .. versionadded:: 0.14.1 Parameters @@ -446,7 +451,12 @@ class CellReactivityController(ReactivityController): Specific classes for running reactivity control depletion calculations are implemented as derived class of CellReactivityController. - + Users should instantiate + :class:`openmc.deplete.reactivity_control.GeometricalCellReactivityController` + or + :class:`openmc.deplete.reactivity_control.TemperatureCellReactivityController` + rather than this class. + .. versionadded:: 0.14.1 Parameters @@ -1003,6 +1013,9 @@ class MaterialReactivityController(ReactivityController): Specific classes for running reactivity control depletion calculations are implemented as derived class of MaterialReactivityController. + Users should instantiate + :class:`openmc.deplete.reactivity_control.RefuelMaterialReactivityController` + rather than this class. .. versionadded:: 0.14.1 From d2fa7a0799e381935badad205421d8b6c421a636 Mon Sep 17 00:00:00 2001 From: church89 Date: Tue, 7 May 2024 16:45:21 +0200 Subject: [PATCH 56/59] extend use of paramteric geometry to lattice and not only universe --- openmc/deplete/reactivity_control.py | 48 ++++++++++++++++++---------- 1 file changed, 32 insertions(+), 16 deletions(-) diff --git a/openmc/deplete/reactivity_control.py b/openmc/deplete/reactivity_control.py index 8ee417a12f7..e36f64b79e8 100644 --- a/openmc/deplete/reactivity_control.py +++ b/openmc/deplete/reactivity_control.py @@ -256,8 +256,10 @@ def _search_for_keff(self, val): guesses, keffs = search # Check if all guesses are within bracket limits - if all(self.bracket_limit[0] <= guess <= self.bracket_limit[1] \ - for guess in guesses): + if all( + self.bracket_limit[0] <= guess <= self.bracket_limit[1] + for guess in guesses + ): # Simple method to iteratively adapt the bracket warn( "Search_for_keff returned values below or above " @@ -304,14 +306,15 @@ def _search_for_keff(self, val): ) # check if adapted bracket lies completely outside of limits - msg = ("WARNING: Adaptive iterative bracket {} went off " - "bracket limits. Set root to {:.2f} and continue." - ) - if all(np.array(bracket)+val <= self.bracket_limit[0]): + msg = ( + "WARNING: Adaptive iterative bracket {} went off " + "bracket limits. Set root to {:.2f} and continue." + ) + if all(np.array(bracket) + val <= self.bracket_limit[0]): warn(msg.format(bracket, self.bracket_limit[0])) root = self.bracket_limit[0] - if all(np.array(bracket)+val >= self.bracket_limit[1]): + if all(np.array(bracket) + val >= self.bracket_limit[1]): warn(msg.format(bracket, self.bracket_limit[1])) root = self.bracket_limit[1] @@ -456,7 +459,7 @@ class CellReactivityController(ReactivityController): or :class:`openmc.deplete.reactivity_control.TemperatureCellReactivityController` rather than this class. - + .. versionadded:: 0.14.1 Parameters @@ -810,14 +813,27 @@ def __init__( check_value("attrib_name", attrib_name, ("rotation", "translation")) self.attrib_name = attrib_name - # check if cell is filled with a universe containing 2 cells - check_type("fill universe", self.cell.fill, openmc.Universe) - # check if universe contains 2 cells - check_length("universe cells", self.cell.fill.cells, 2) - - self.universe_cells = [ - cell for cell in self.cell.fill.cells.values() if cell.fill.depletable - ] + if isinstance(self.cell.fill, openmc.Universe): + # check if universe contains 2 cells + check_length("universe cells", self.cell.fill.cells, 2) + self.universe_cells = [ + cell for cell in self.cell.fill.cells.values() if cell.fill.depletable + ] + elif isinstance(self.cell.fill, openmc.Lattice): + # check if lattice contains 2 universes + check_length("lattice universes", self.cell.fill.get_unique_universes(), 2) + self.universe_cells = [ + cell + for cell in self.cell.fill.get_all_cells().values() + if cell.fill.depletable + ] + else: + raise ValueError( + "{} s not a valid instance of " + " should be a {} or {} instance".format( + self.cell.fill, openmc.Universe, openmc.Lattice + ) + ) check_type("samples", samples, int) self.samples = samples From 7583f78750624945a8294eac5517256bc048f9b0 Mon Sep 17 00:00:00 2001 From: church89 Date: Wed, 5 Jun 2024 09:27:29 +0200 Subject: [PATCH 57/59] implement suggested changes to reactivity control classes --- openmc/deplete/abc.py | 60 +-- openmc/deplete/reactivity_control.py | 768 +++++++-------------------- 2 files changed, 215 insertions(+), 613 deletions(-) diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py index a1ab198c297..b8d2e1ef917 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -31,11 +31,9 @@ from .transfer_rates import TransferRates from openmc import Material, Cell from .reactivity_control import ( + ReactivityController, CellReactivityController, - GeometricalCellReactivityController, - TemperatureCellReactivityController, - MaterialReactivityController, - RefuelMaterialReactivityController + MaterialReactivityController ) __all__ = [ @@ -718,25 +716,7 @@ def reactivity_control(self): @reactivity_control.setter def reactivity_control(self, reactivity_control): - if isinstance(reactivity_control, CellReactivityController): - if not isinstance(reactivity_control.cell, Cell): - raise TypeError( - "Reactivity control attribute must be Cell " - "not {}".format(type(reactivity_control.cell))) - elif isinstance(reactivity_control, MaterialReactivityController): - if not isinstance(reactivity_control.material, Material): - raise TypeError( - "Reactivity control attribute must be Material " - "not {}".format(type(reactivity_control.material))) - else: - raise TypeError( - "Reactivity Control must either be " - "CellReactivityController, or MaterialReactivityController, " - "not {}".format(type(reactivity_control))) - - check_value('attribute', reactivity_control.attrib_name, - ('translation', 'rotation', 'temperature', 'refuel')) - + check_type('reactivity control', reactivity_control, ReactivityController) self._reactivity_control = reactivity_control def _timed_deplete(self, n, rates, dt, matrix_func=None): @@ -945,34 +925,26 @@ def add_transfer_rate( def add_reactivity_control( self, - obj: Union[Cell, Material, int, str], - attr: str, + obj: Union[Cell, Material], **kwargs): - """Add reactivity control to integrator scheme. + """Add pre-defined Cell based or Material bases reactivity control to + integrator scheme. Parameters ---------- - obj : openmc.Cell or openmc.Material object or id or str name - Cell or Materials identifier to where add reactivity control - attr : str - Attribute to specify the type of reactivity control. Accepted values - are: 'translation', 'rotation', 'temperature' for an openmc.Cell - object; 'refuel' for an openmc.Material object. + obj : openmc.Cell or openmc.Material + Cell or Material identifier to where add reactivity control **kwargs - keyword arguments that are passed to the ReactivityController class. + keyword arguments that are passed to the specific ReactivityController + class. """ - check_value('attribute', attr, ('translation', 'rotation', - 'temperature', 'refuel')) - if attr in ('translation', 'rotation'): - reactivity_control = GeometricalCellReactivityController - elif attr == 'temperature': - reactivity_control = TemperatureCellReactivityController - elif attr == 'refuel': - reactivity_control = RefuelMaterialReactivityController - - self._reactivity_control = reactivity_control.from_params(obj, attr, - self.operator, **kwargs) + if isinstance(obj, Cell): + reactivity_control = CellReactivityController + elif isinstance(obj, Material): + reactivity_control = MaterialReactivityController + self._reactivity_control = reactivity_control.from_params(obj, + self.operator, **kwargs) @add_params class SIIntegrator(Integrator): diff --git a/openmc/deplete/reactivity_control.py b/openmc/deplete/reactivity_control.py index e36f64b79e8..08e86d9a6ce 100644 --- a/openmc/deplete/reactivity_control.py +++ b/openmc/deplete/reactivity_control.py @@ -14,6 +14,7 @@ check_type, check_value, check_less_than, + check_greater_than, check_iterable_type, check_length, ) @@ -33,11 +34,9 @@ class ReactivityController(ABC): This abstract class sets the requirements for the reactivity control set. Users should instantiate - :class:`openmc.deplete.reactivity_control.GeometricalCellReactivityController` + :class:`openmc.deplete.reactivity_control.CellReactivityController` or - :class:`openmc.deplete.reactivity_control.TemperatureCellReactivityController` - or - :class:`openmc.deplete.reactivity_control.RefuelMaterialReactivityController` + :class:`openmc.deplete.reactivity_control.MaterialReactivityController` rather than this class. .. versionadded:: 0.14.1 @@ -52,10 +51,6 @@ class ReactivityController(ABC): Absolute bracketing interval lower and upper; if during the adaptive algorithm the search_for_keff solution lies off these limits the closest limit will be set as new result. - density_treatment : str, optional - Whether or not to keep constant volume or density after a depletion step - before the next one. - Default to 'constant-volume' bracketed_method : {'brentq', 'brenth', 'ridder', 'bisect'}, optional Solution method to use. This is equivalent to the `bracket_method` parameter of the @@ -90,7 +85,6 @@ def __init__( operator, bracket, bracket_limit, - density_treatment="constant-volume", bracketed_method="brentq", tol=0.01, target=1.0, @@ -103,18 +97,12 @@ def __init__( self.local_mats = operator.local_mats self.model = operator.model self.geometry = operator.model.geometry - - check_value( - "density_treatment", - density_treatment, - ("constant-density", "constant-volume"), - ) - self.density_treatment = density_treatment self.bracket = bracket check_iterable_type("bracket_limit", bracket_limit, Real) check_length("bracket_limit", bracket_limit, 2) check_less_than("bracket limit values", bracket_limit[0], bracket_limit[1]) + self.bracket_limit = bracket_limit self.bracketed_method = bracketed_method self.tol = tol @@ -130,7 +118,8 @@ def bracketed_method(self): def bracketed_method(self, value): check_value("bracketed_method", value, _SCALAR_BRACKETED_METHODS) if value != "brentq": - warn("brentq bracketed method is recommended") + warn("""brentq bracketed method is recommended to ensure + convergence of the method""") self._bracketed_method = value @property @@ -152,8 +141,8 @@ def target(self, value): self._target = value @classmethod - def from_params(cls, obj, attr, operator, **kwargs): - return cls(obj, attr, operator, **kwargs) + def from_params(cls, obj, operator, **kwargs): + return cls(obj, operator, **kwargs) @abstractmethod def _model_builder(self, param): @@ -193,17 +182,36 @@ def search_for_keff(self, x, step_index): Search_for_keff returned root value """ + @abstractmethod + def _adjust_volumes(self, root=None): + """Adjust volumes after criticality search when cell or material are + modified. + + Parameters + ---------- + root : float, Optional + :meth:`openmc.search.search_for_keff` volume root for + :class:`openmc.deplete.reactivity_control.CellReactivityController` + instances, in cm3 + Returns + ------- + volumes : dict + Dictionary of calculated volume, where key mat id and value + material volume, in cm3 + """ + def _search_for_keff(self, val): """Perform the criticality search for a given parametric model. If the solution lies off the initial bracket, this method iteratively - adapt it until :meth:`openmc.search.search_for_keff` return a valid + adapts it until :meth:`openmc.search.search_for_keff` return a valid solution. It calculates the ratio between guessed and corresponding keffs values - as the proportional term to move the bracket towards the target. - A bracket limit pose the upper and lower boundaries to the adapting - bracket. If one limit is hit, the algorithm will stop and the closest - limit value will be used. + as the proportional term, so that the bracket can be moved towards the + target. + A bracket limit poses the upper and lower boundaries to the adapting + bracket. If one limit is hit, the algoritm will stop and the closest + limit value will be set. Parameters ---------- @@ -341,36 +349,9 @@ def _search_for_keff(self, val): return root - def _update_volumes(self): - """Update volumes stored in AtomNumber. - - After a depletion step, both material volume and density change, due to - changes in nuclides composition. - At present we lack an implementation to calculate density and volume - changes due to the different molecules speciation. Therefore, OpenMC - assumes by default that the depletable volume does not change and only - updates the nuclide densities and consequently the total material density. - This method, vice-versa, assumes that the total material density does not - change and update the material volumes instead. - """ - number_i = self.operator.number - for mat_idx, mat in enumerate(self.local_mats): - # Total number of atoms-gram per mol - agpm = 0 - for nuc in number_i.nuclides: - agpm += number_i[mat, nuc] * atomic_mass(nuc) - # Get mass dens from beginning, intended to be held constant - density = openmc.lib.materials[int(mat)].get_density("g/cm3") - number_i.volume[mat_idx] = agpm / AVOGADRO / density - def _update_materials(self, x): - """Update number density and material compositions in OpenMC on all processes. - - If density_treatment is set to 'constant-density' - :meth:`openmc.deplete.reactivity_control._update_volumes` is called to update - material volumes in AtomNumber, keeping the material total density - constant, before re-normalizing the atom densities and assigning them - to the model in memory. + """Update number density and material compositions in OpenMC on all + processes. Parameters ---------- @@ -379,9 +360,6 @@ def _update_materials(self, x): """ self.operator.number.set_density(x) - if self.density_treatment == "constant-density": - self._update_volumes() - for rank in range(comm.size): number_i = comm.bcast(self.operator.number, root=rank) @@ -448,26 +426,19 @@ def _update_x_and_set_volumes(self, x, volumes): number_i.volume[mat_idx] = res_vol return x - class CellReactivityController(ReactivityController): """Abstract class holding reactivity control cell-based functions. - Specific classes for running reactivity control depletion calculations are - implemented as derived class of CellReactivityController. - Users should instantiate - :class:`openmc.deplete.reactivity_control.GeometricalCellReactivityController` - or - :class:`openmc.deplete.reactivity_control.TemperatureCellReactivityController` - rather than this class. - .. versionadded:: 0.14.1 Parameters ---------- cell : openmc.Cell or int or str - OpenMC Cell identifier to where apply batch wise scheme + OpenMC Cell identifier to where apply a reactivty control operator : openmc.deplete.Operator OpenMC operator object + attribute : str + openmc.lib.cell attribute. Only support 'translation' and 'rotation' bracket : list of float Initial bracketing interval to search for the solution, relative to the solution at previous step. @@ -475,10 +446,6 @@ class CellReactivityController(ReactivityController): Absolute bracketing interval lower and upper; if during the adaptive algorithm the search_for_keff solution lies off these limits the closest limit will be set as new result. - density_treatment : str - Whether or not to keep constant volume or density after a depletion step - before the next one. - Default to 'constant-volume' bracketed_method : {'brentq', 'brenth', 'ridder', 'bisect'}, optional Solution method to use. This is equivalent to the `bracket_method` parameter of the @@ -502,7 +469,9 @@ class CellReactivityController(ReactivityController): ---------- cell : openmc.Cell or int or str OpenMC Cell identifier to where apply batch wise scheme - universe_cells : list of openmc.Cell + attribute : str + openmc.lib.cell attribute. Only support 'translation' and 'rotation' + depletable_cells : list of openmc.Cell Cells that fill the openmc.Universe that fills the main cell to where apply batch wise scheme, if cell materials are set as depletable. @@ -518,13 +487,14 @@ def __init__( self, cell, operator, + attribute, bracket, bracket_limit, - axis=None, - density_treatment="constant-volume", + axis, bracketed_method="brentq", tol=0.01, target=1.0, + samples=1000000, print_iterations=True, search_for_keff_output=True, ): @@ -533,7 +503,6 @@ def __init__( operator, bracket, bracket_limit, - density_treatment, bracketed_method, tol, target, @@ -543,19 +512,41 @@ def __init__( self.cell = self._get_cell(cell) + # Only lib cell attributes are valid + check_value("attribute", attribute, ('translation', 'rotation')) + self.attribute = attribute + # Initialize to None, as openmc.lib is not initialized yet here self.lib_cell = None - if axis is not None: - # index of cell directional axis - check_value("axis", axis, (0, 1, 2)) + # A cell translation or roation must correspond to a geometrical + # variation of its filled materials, thus at least 2 are necessary + if isinstance(self.cell.fill, openmc.Universe): + # check if cell fill universe contains at least 2 cells + check_greater_than("universe cells", + len(self.cell.fill.cells), 2, True) + + if isinstance(self.cell.fill, openmc.Lattice): + # check if cell fill lattice contains at least 2 universes + check_greater_than("lattice universes", + len(self.cell.fill.get_unique_universes()), 2, True) + + self.depletable_cells = [ + cell + for cell in self.cell.fill.cells.values() + if cell.fill.depletable + ] + + # index of cell directional axis + check_value("axis", axis, (0, 1, 2)) self.axis = axis - # Initialize container of universe cells, to populate only if materials - # are set as depletables - self.universe_cells = None + check_type("samples", samples, int) + self.samples = samples + + if self.depletable_cells: + self._initialize_volume_calc() - @abstractmethod def _get_cell_attrib(self): """Get cell attribute coefficient. @@ -564,8 +555,8 @@ def _get_cell_attrib(self): coeff : float cell coefficient """ + return getattr(self.lib_cell, self.attribute)[self.axis] - @abstractmethod def _set_cell_attrib(self, val): """Set cell attribute to the cell instance. @@ -574,9 +565,13 @@ def _set_cell_attrib(self, val): val : float cell coefficient to set """ + attr = getattr(self.lib_cell, self.attribute) + attr[self.axis] = val + setattr(self.lib_cell, self.attribute, attr) def _set_lib_cell(self): - """Set openmc.lib.cell cell to self.lib_cell attribute""" + """Set openmc.lib.cell cell to self.lib_cell attribute + """ self.lib_cell = [ cell for cell in openmc.lib.cells.values() if cell.id == self.cell.id ][0] @@ -644,6 +639,33 @@ def _get_cell(self, val): return val + def _initialize_volume_calc(self): + """Set volume calculation model settings of depletable materials filling + the parametric Cell. + """ + ll, ur = self.geometry.bounding_box + mat_vol = openmc.VolumeCalculation(self.depletable_cells, self.samples, ll, ur) + self.model.settings.volume_calculations = mat_vol + + def _adjust_volumes(self): + """Perform stochastic volume calculation and return new volumes. + + Returns + ------- + volumes : dict + Dictionary of calculated volumes, where key is mat id and value + material volume, in cm3 + """ + openmc.lib.calculate_volumes() + volumes = {} + if comm.rank == 0: + res = openmc.VolumeCalculation.from_hdf5("volume_1.h5") + for cell in self.depletable_cells: + mat_id = cell.fill.id + volumes[str(mat_id)] = res.volumes[cell.id].n + volumes = comm.bcast(volumes) + return volumes + def _model_builder(self, param): """Builds the parametric model to be passed to the :meth:`openmc.search.search_for_keff` method. @@ -703,347 +725,28 @@ def search_for_keff(self, x, step_index): # set results value as attribute in the geometry self._set_cell_attrib(root) - # if at least one of the cell materials is depletable, calculate new - # volume and update x and number accordingly + # if at least one of the cell fill materials is depletable, assign new + # volume to the material and update x and number accordingly # new volume - if self.universe_cells: - volumes = self._calculate_volumes() + if self.depletable_cells: + volumes = self._adjust_volumes() x = self._update_x_and_set_volumes(x, volumes) return x, root - -class GeometricalCellReactivityController(CellReactivityController): - """Reactivity control cell-based with geometrical-attribute class. - - A user doesn't need to call this class directly. - Instead an instance of this class is automatically created by calling - :meth:`openmc.deplete.Integrator.add_reactivity_control` method from an - integrator class, such as :class:`openmc.deplete.CECMIntegrator`. - - .. versionadded:: 0.14.1 - - Parameters - ---------- - cell : openmc.Cell or int or str - OpenMC Cell identifier to where apply batch wise scheme - attrib_name : str - Cell attribute type - operator : openmc.deplete.Operator - OpenMC operator object - axis : int {0,1,2} - Directional axis for geometrical parametrization, where 0, 1 and 2 stand - for 'x', 'y' and 'z', respectively. - bracket : list of float - Initial bracketing interval to search for the solution, relative to the - solution at previous step. - bracket_limit : list of float - Absolute bracketing interval lower and upper; if during the adaptive - algorithm the search_for_keff solution lies off these limits the closest - limit will be set as new result. - density_treatment : str - Whether or not to keep constant volume or density after a depletion step - before the next one. - Default to 'constant-volume' - bracketed_method : {'brentq', 'brenth', 'ridder', 'bisect'}, optional - Solution method to use. - This is equivalent to the `bracket_method` parameter of the - `search_for_keff`. - Defaults to 'brentq'. - tol : float - Tolerance for search_for_keff method. - This is equivalent to the `tol` parameter of the `search_for_keff`. - Default to 0.01 - target : Real, optional - This is equivalent to the `target` parameter of the `search_for_keff`. - Default to 1.0. - print_iterations : Bool, Optional - Whether or not to print `search_for_keff` iterations. - Default to True - search_for_keff_output : Bool, Optional - Whether or not to print transport iterations during `search_for_keff`. - Default to False - - Attributes - ---------- - cell : openmc.Cell or int or str - OpenMC Cell identifier to where apply batch wise scheme - attrib_name : str {'translation', 'rotation'} - Cell attribute type - axis : int {0,1,2} - Directional axis for geometrical parametrization, where 0, 1 and 2 stand - for 'x', 'y' and 'z', respectively. - samples : int - Number of samples used to generate volume estimates for stochastic - volume calculations. - - """ - - def __init__( - self, - cell, - attrib_name, - operator, - bracket, - bracket_limit, - axis, - density_treatment="constant-volume", - bracketed_method="brentq", - tol=0.01, - target=1.0, - samples=1000000, - print_iterations=True, - search_for_keff_output=True, - ): - - super().__init__( - cell, - operator, - bracket, - bracket_limit, - axis, - density_treatment, - bracketed_method, - tol, - target, - print_iterations, - search_for_keff_output, - ) - - check_value("attrib_name", attrib_name, ("rotation", "translation")) - self.attrib_name = attrib_name - - if isinstance(self.cell.fill, openmc.Universe): - # check if universe contains 2 cells - check_length("universe cells", self.cell.fill.cells, 2) - self.universe_cells = [ - cell for cell in self.cell.fill.cells.values() if cell.fill.depletable - ] - elif isinstance(self.cell.fill, openmc.Lattice): - # check if lattice contains 2 universes - check_length("lattice universes", self.cell.fill.get_unique_universes(), 2) - self.universe_cells = [ - cell - for cell in self.cell.fill.get_all_cells().values() - if cell.fill.depletable - ] - else: - raise ValueError( - "{} s not a valid instance of " - " should be a {} or {} instance".format( - self.cell.fill, openmc.Universe, openmc.Lattice - ) - ) - - check_type("samples", samples, int) - self.samples = samples - - if self.universe_cells: - self._initialize_volume_calc() - - def _get_cell_attrib(self): - """Get cell attribute coefficient. - - Returns - ------- - coeff : float - cell coefficient - """ - if self.attrib_name == "translation": - return self.lib_cell.translation[self.axis] - elif self.attrib_name == "rotation": - return self.lib_cell.rotation[self.axis] - - def _set_cell_attrib(self, val): - """Set cell attribute to the cell instance. - - Attributes are only applied to a cell filled with a universe containing - two cells itself. - - Parameters - ---------- - val : float - Cell coefficient to set, in cm for translation and deg for rotation - """ - if self.attrib_name == "translation": - vector = self.lib_cell.translation - elif self.attrib_name == "rotation": - vector = self.lib_cell.rotation - - vector[self.axis] = val - setattr(self.lib_cell, self.attrib_name, vector) - - def _initialize_volume_calc(self): - """Set volume calculation model settings of depletable materials filling - the parametric Cell. - - """ - ll, ur = self.geometry.bounding_box - mat_vol = openmc.VolumeCalculation(self.universe_cells, self.samples, ll, ur) - self.model.settings.volume_calculations = mat_vol - - def _calculate_volumes(self): - """Perform stochastic volume calculation - - Returns - ------- - volumes : dict - Dictionary of calculated volumes, where key is mat id and value - material volume, in cm3 - """ - openmc.lib.calculate_volumes() - volumes = {} - if comm.rank == 0: - res = openmc.VolumeCalculation.from_hdf5("volume_1.h5") - for cell in self.universe_cells: - mat_id = cell.fill.id - volumes[str(mat_id)] = res.volumes[cell.id].n - volumes = comm.bcast(volumes) - return volumes - - -class TemperatureCellReactivityController(CellReactivityController): - """Reactivity control cell-based with temperature-attribute class. - - A user doesn't need to call this class directly. - Instead an instance of this class is automatically created by calling - :meth:`openmc.deplete.Integrator.add_reactivity_control` method from an - integrator class, such as :class:`openmc.deplete.CECMIntegrator`. - - .. versionadded:: 0.14.1 - - Parameters - ---------- - cell : openmc.Cell or int or str - OpenMC Cell identifier to where apply batch wise scheme - operator : openmc.deplete.Operator - OpenMC operator object - attrib_name : str - Cell attribute type - bracket : list of float - Initial bracketing interval to search for the solution, relative to the - solution at previous step. - bracket_limit : list of float - Absolute bracketing interval lower and upper; if during the adaptive - algorithm the search_for_keff solution lies off these limits the closest - limit will be set as new result. - density_treatment : str - Whether or not to keep constant volume or density after a depletion step - before the next one. - Default to 'constant-volume' - bracketed_method : {'brentq', 'brenth', 'ridder', 'bisect'}, optional - Solution method to use. - This is equivalent to the `bracket_method` parameter of the - `search_for_keff`. - Defaults to 'brentq'. - tol : float - Tolerance for search_for_keff method. - This is equivalent to the `tol` parameter of the `search_for_keff`. - Default to 0.01 - target : Real, optional - This is equivalent to the `target` parameter of the `search_for_keff`. - Default to 1.0. - print_iterations : Bool, Optional - Whether or not to print `search_for_keff` iterations. - Default to True - search_for_keff_output : Bool, Optional - Whether or not to print transport iterations during `search_for_keff`. - Default to False - - Attributes - ---------- - cell : openmc.Cell or int or str - OpenMC Cell identifier to where apply batch wise scheme - attrib_name : str {'temperature'} - Cell attribute type - - """ - - def __init__( - self, - cell, - attrib_name, - operator, - bracket, - bracket_limit, - axis=None, - density_treatment="constant-volume", - bracketed_method="brentq", - tol=0.01, - target=1.0, - print_iterations=True, - search_for_keff_output=True, - ): - - super().__init__( - cell, - operator, - bracket, - bracket_limit, - axis, - density_treatment, - bracketed_method, - tol, - target, - print_iterations, - search_for_keff_output, - ) - - # Not needed but used for consistency with other classes - check_value("attrib_name", attrib_name, "temperature") - self.attrib_name = attrib_name - - # check if initial temperature has been set to right cell material - if isinstance(self.cell.fill, openmc.Universe): - cells = [ - cell for cell in self.cell.fill.cells.values() if cell.fill.temperature - ] - check_length("Only one cell with temperature", cells, 1) - self.cell = cells[0] - - check_type("temperature cell real", self.cell.fill.temperature, Real) - - def _get_cell_attrib(self): - """Get cell temperature. - - Returns - ------- - coeff : float - cell temperature, in Kelvin - """ - return self.lib_cell.get_temperature() - - def _set_cell_attrib(self, val): - """Set temperature value to the cell instance. - - Parameters - ---------- - val : float - Cell temperature to set, in Kelvin - """ - self.lib_cell.set_temperature(val) - - class MaterialReactivityController(ReactivityController): """Abstract class holding reactivity control material-based functions. - Specific classes for running reactivity control depletion calculations are - implemented as derived class of MaterialReactivityController. - Users should instantiate - :class:`openmc.deplete.reactivity_control.RefuelMaterialReactivityController` - rather than this class. - .. versionadded:: 0.14.1 Parameters ---------- material : openmc.Material or int or str - OpenMC Material identifier to where apply batch wise scheme + OpenMC Material identifier to where apply reactivity control operator : openmc.deplete.Operator OpenMC operator object - mat_vector : dict - Dictionary of material composition to parameterize, where a pair key value - represents a nuclide and its weight fraction, respectively. + material_to_mix : openmc.Material + OpenMC Material to mix with main material for reactivity control. bracket : list of float Initial bracketing interval to search for the solution, relative to the solution at previous step. @@ -1051,10 +754,12 @@ class MaterialReactivityController(ReactivityController): Absolute bracketing interval lower and upper; if during the adaptive algorithm the search_for_keff solution lies off these limits the closest limit will be set as new result. - density_treatment : str - Whether or not to keep constant volume or density after a depletion step - before the next one. - Default to 'constant-volume' + units : {'grams', 'atoms', 'cc', 'cm3'} str + Units of material parameter to be mixed. + Default to 'cc' + dilute : bool + Whether or not to update material volume after a material mix. + Default to False bracketed_method : {'brentq', 'brenth', 'ridder', 'bisect'}, optional Solution method to use. This is equivalent to the `bracket_method` parameter of the @@ -1078,9 +783,14 @@ class MaterialReactivityController(ReactivityController): ---------- material : openmc.Material or int or str OpenMC Material identifier to where apply batch wise scheme - mat_vector : dict - Dictionary of material composition to parameterize, where a pair key value - represents a nuclide and its weight fraction, respectively. + material_to_mix : openmc.Material + OpenMC Material to mix with main material for reactivity control. + units : {'grams', 'atoms', 'cc', 'cm3'} str + Units of material parameter to be mixed. + Default to 'cc' + dilute : bool + Whether or not to update material volume after a material mix. + Default to False """ @@ -1088,10 +798,11 @@ def __init__( self, material, operator, - mat_vector, + material_to_mix, bracket, bracket_limit, - density_treatment="constant-volume", + units='cc', + dilute=False, bracketed_method="brentq", tol=0.01, target=1.0, @@ -1103,7 +814,6 @@ def __init__( operator, bracket, bracket_limit, - density_treatment, bracketed_method, tol, target, @@ -1113,16 +823,16 @@ def __init__( self.material = self._get_material(material) - check_type("material vector", mat_vector, dict, str) - for nuc in mat_vector: + check_type("material to mix", material_to_mix, openmc.Material) + for nuc in material_to_mix.get_nuclides(): check_value("check nuclide exists", nuc, self.operator.nuclides_with_data) + self.material_to_mix = material_to_mix + + check_value('units', units, ('grams', 'atoms', 'cc', 'cm3')) + self.units = units - if not isclose(sum(mat_vector.values()), 1.0, abs_tol=0.01): - # Normalize material elements vector - sum_values = sum(mat_vector.values()) - for elm in mat_vector: - mat_vector[elm] /= sum_values - self.mat_vector = mat_vector + check_type("dilute", dilute, bool) + self.dilute = dilute def _get_material(self, val): """Helper method for getting openmc material from Material instance or @@ -1188,110 +898,36 @@ def search_for_keff(self, x, step_index): # right amount root = self._search_for_keff(0) - # Update concentration vector and volumes with new value - volumes = self._calculate_volumes(root) + # Update concentration vector and volumes with new value for depletion + volumes = self._adjust_volumes(root) x = self._update_x_and_set_volumes(x, volumes) return x, root + def _set_mix_material_volume(self, param): + """ + Set volume in cc to `self.material_to_mix` as a function of `param` after + converion, based on `self.units` attribute. -class RefuelMaterialReactivityController(MaterialReactivityController): - """Reactivity control material-based class for refuelling (addition or - removal) scheme. - - A user doesn't need to call this class directly. - Instead an instance of this class is automatically created by calling - :meth:`openmc.deplete.Integrator.add_reactivity_control` method from an - integrator class, such as :class:`openmc.deplete.CECMIntegrator`. - - .. versionadded:: 0.14.1 - - Parameters - ---------- - material : openmc.Material or int or str - OpenMC Material identifier to where apply batch wise scheme - operator : openmc.deplete.Operator - OpenMC operator object - attrib_name : str - Material attribute name - mat_vector : dict - Dictionary of material composition to parameterize, where a pair key value - represents a nuclide and its weight fraction, respectively. - bracket : list of float - Initial bracketing interval to search for the solution, relative to the - solution at previous step. - bracket_limit : list of float - Absolute bracketing interval lower and upper; if during the adaptive - algorithm the search_for_keff solution lies off these limits the closest - limit will be set as new result. - density_treatment : str - Whether or not to keep constant volume or density after a depletion step - before the next one. - Default to 'constant-volume' - bracketed_method : {'brentq', 'brenth', 'ridder', 'bisect'}, optional - Solution method to use. - This is equivalent to the `bracket_method` parameter of the - `search_for_keff`. - Defaults to 'brentq'. - tol : float - Tolerance for search_for_keff method. - This is equivalent to the `tol` parameter of the `search_for_keff`. - Default to 0.01 - target : Real, optional - This is equivalent to the `target` parameter of the `search_for_keff`. - Default to 1.0. - print_iterations : Bool, Optional - Whether or not to print `search_for_keff` iterations. - Default to True - search_for_keff_output : Bool, Optional - Whether or not to print transport iterations during `search_for_keff`. - Default to False - - Attributes - ---------- - material : openmc.Material or int or str - OpenMC Material identifier to where apply batch wise scheme - mat_vector : dict - Dictionary of material composition to parameterize, where a pair key value - represents a nuclide and its weight fraction, respectively. - attrib_name : str - Material attribute name - - """ + Parameters + ---------- + param : float + grams or atoms or cc of material_to_mix to convert to cc - def __init__( - self, - material, - attrib_name, - operator, - mat_vector, - bracket, - bracket_limit, - density_treatment="constant-volume", - bracketed_method="brentq", - tol=0.01, - target=1.0, - print_iterations=True, - search_for_keff_output=True, - ): + """ + if self.units == 'grams': + multiplier = 1 / self.material_to_mix.get_mass_density() + elif self.units == 'atoms': + multiplier = ( + self.material_to_mix.average_molar_mass + / AVOGADRO + / self.material_to_mix.get_mass_density() + ) + elif self.units == 'cc' or self.units == 'cm3': + multiplier = 1 - super().__init__( - material, - operator, - mat_vector, - bracket, - bracket_limit, - density_treatment, - bracketed_method, - tol, - target, - print_iterations, - search_for_keff_output, - ) + self.material_to_mix.volume = param * multiplier - # Not needed but used for consistency with other classes - check_value("attrib_name", attrib_name, "refuel") - self.attrib_name = attrib_name def _model_builder(self, param): """Callable function which builds a model according to a passed @@ -1300,14 +936,10 @@ def _model_builder(self, param): Builds the parametric model to be passed to the :meth:`openmc.search.search_for_keff` method. - The parametrization can either be at constant volume or constant - density, according to user input. - Default is constant volume. - Parameters ---------- param : float - Model function variable, total grams of material to add or remove + Model function variable, cc of material_to_mix Returns ------- @@ -1317,68 +949,68 @@ def _model_builder(self, param): for rank in range(comm.size): number_i = comm.bcast(self.operator.number, root=rank) - for mat in number_i.materials: + for mat_id in number_i.materials: nuclides = [] densities = [] - if int(mat) == self.material.id: + if int(mat_id) == self.material.id: + self._set_mix_material_volume(param) - if self.density_treatment == "constant-density": - vol = number_i.get_mat_volume(mat) + ( - param / self.material.get_mass_density() + if self.dilute: + # increase the total volume of the material + # assuming ideal materials mixing + vol = ( + number_i.get_mat_volume(mat_id) + + self.material_to_mix.volume ) + else: + # we assume the volume after mix won't change + vol = number_i.get_mat_volume(mat_id) - elif self.density_treatment == "constant-volume": - vol = number_i.get_mat_volume(mat) + # Total atom concentration in [#atoms/cm-b] + #mat_index = number_i.index_mat[mat_id] + #tot_conc = 1.0e-24 * sum(number_i.number[mat_index]) / vol for nuc in number_i.index_nuc: # check only nuclides with cross sections data if nuc in self.operator.nuclides_with_data: - if nuc in self.mat_vector: - # units [#atoms/cm-b] - val = 1.0e-24 * ( - number_i.get_atom_density(mat, nuc) - + param - / atomic_mass(nuc) - * AVOGADRO - * self.mat_vector[nuc] - / vol - ) - - else: - # get normalized atoms density in [atoms/b-cm] - val = 1.0e-24 * number_i[mat, nuc] / vol - - if val > 0.0: + atoms = number_i[mat_id, nuc] + if nuc in self.material_to_mix.get_nuclides(): + # update atoms number + atoms += self.material_to_mix.get_nuclide_atoms()[nuc] + + atoms_per_bcm = 1.0e-24 * atoms / vol + + if atoms_per_bcm > 0.0: nuclides.append(nuc) - densities.append(val) + densities.append(atoms_per_bcm) else: # for all other materials, still check atom density limits for nuc in number_i.nuclides: if nuc in self.operator.nuclides_with_data: # get normalized atoms density in [atoms/b-cm] - val = 1.0e-24 * number_i.get_atom_density(mat, nuc) + atoms_per_bcm = 1.0e-24 * number_i.get_atom_density(mat_id, nuc) - if val > 0.0: + if atoms_per_bcm > 0.0: nuclides.append(nuc) - densities.append(val) + densities.append(atoms_per_bcm) - # set nuclides and densities to the in-memory model - openmc.lib.materials[int(mat)].set_densities(nuclides, densities) + # assign nuclides and densities to the in-memory model + openmc.lib.materials[int(mat_id)].set_densities(nuclides, densities) # always need to return a model return self.model - def _calculate_volumes(self, res): + def _adjust_volumes(self, res): """Uses :meth:`openmc.deplete.reactivity_control._search_for_keff` - solution as grams of material to add or remove to calculate new material - volume. + solution as cc to update depletable volume if `self.dilute` attribute + is set to True. Parameters ---------- res : float - Solution in grams of material, coming from + Solution in cc of material_to_mix, coming from :meth:`openmc.deplete.reactivity_control._search_for_keff` Returns @@ -1390,12 +1022,10 @@ def _calculate_volumes(self, res): number_i = self.operator.number volumes = {} - for mat in self.local_mats: - if int(mat) == self.material.id: - if self.density_treatment == "constant-density": - volumes[mat] = number_i.get_mat_volume(mat) + ( - res / self.material.get_mass_density() - ) - elif self.density_treatment == "constant-volume": - volumes[mat] = number_i.get_mat_volume(mat) + for mat_id in self.local_mats: + if int(mat_id) == self.material.id: + if self.dilute: + volumes[mat_id] = number_i.get_mat_volume(mat_id) + res + else: + volumes[mat_id] = number_i.get_mat_volume(mat_id) return volumes From fa13a813d92cde29707f4192a00fe35fe5535cb8 Mon Sep 17 00:00:00 2001 From: church89 Date: Thu, 6 Jun 2024 13:21:22 +0200 Subject: [PATCH 58/59] update tests --- .../ref_depletion_with_refuel.h5 | Bin 38432 -> 39352 bytes .../ref_depletion_with_rotation.h5 | Bin 38432 -> 39352 bytes .../ref_depletion_with_translation.h5 | Bin 38432 -> 39352 bytes .../deplete_with_reactivity_control/test.py | 51 +++-- .../test_deplete_reactivity_control.py | 176 +++++++++--------- 5 files changed, 128 insertions(+), 99 deletions(-) diff --git a/tests/regression_tests/deplete_with_reactivity_control/ref_depletion_with_refuel.h5 b/tests/regression_tests/deplete_with_reactivity_control/ref_depletion_with_refuel.h5 index 26bd4839da3a1c67c9127ed0fa5ee7126340f53f..83a19702b0ef95740453e39a79ae1c44bf364e2f 100644 GIT binary patch delta 1323 zcmZuveM}o=81KDx?Oi9OgfXgaK!rgkjJCp{d>q%7^0m2H$hI6VQ?$aGx(%3fVG{xg zh}ALa@eFNA*vc?M49>DsN7Q9>x-DQPH4HUOBTF;_(^yf2F~_{_TFM_@a_{>*&+qqq z-0wNp02*SEU5SRA%oA@ROTpl$B81gJk0_S$>ex78lMvBH#uEd>A{;68ersApV>ApJ zob06dpNBBhpl1P-&;tIcOJ$ykA>3)a*AFAYSP7jTz%pP|oX8f3Pe)(`Fp)*v`HeWX z*P#%CT46c6NeEL3IJgojdM!#}htk4&lf7t->6ItbvYQnPtPKEp?|Gv1CA^d zXJJYx&4`W&ZH=#L0m6_Zso))f*1&k)2A}RFq;2Pl^n$BQj+q#bim<%HQ%q1>&HY3& zWOu-%F%grnSiyxb%*A*ziJ<_;#dG9nD#+=nzDBTd1>QB_dqIb22Ux2U881#WIRlNM zRv67TnWxtx%L9Xi6lhRs%SLocd~QMQk<}@ zjz+kH^#TnsZtnZy#?QJN-(;4Q#tl^$$Mh4O#xE~)n43?}83QcAz?JJA)}KBngZ}N~ zy6-+#oC_>lAn1OvC-DaCNyH@;i!Mh9n;|EvxhWQPtzuoT(;-!Lfnu(ee z_c$4MqVnI8s&RkG=uU&Lxcs#G~g8aHZhYJ;CC*sT~Dyc-shWmD~ez9Vc% zmfP~jb|qjdvIzbfm`E@0A1BW&Z?+wdY$01OpE=nzbcGauWwu#Wv*d7dYIgcZtz^T} z3_8+QK@y*=Q@A-*;=Oe#gGq~|B5M5%c5wL*D(tDfmroZRr5@~0KCc7csdW$grD=KX L>+K6uuTuX3)en;< delta 1018 zcmZvYe`r%z6vyAa&!zW0Gj(++I^QGy5y{$nVj!C|*oUt0ac3-{c6 zzUOn!J@<@KoV<*~R&0((KKU6l9vF9jJ)Hg?fte>Tgasr3a_y#O_9TmPpkNcEbOfbT z(F9pt5a(PGB~E9G@=K6Bth_{gfYDaRI=I6cVbm@_&RNQZYcx3kw~H)PHe`z-Q$`?J z*4&r)h>co9y=zz)svyu-(cBlZvLI9uNZVvOT9L(Yah(mO+(8KI2-qrbMQ(OLn=J?d z2LZtm!m^Wo>sWzt&<;`OE=V;jp*su?jC0lC;=f`sLqPGDtRJ##J*2C(vs62nOrbYa z@JX$9v8sQ20cMu)Z!>}4(8wiS8W5o9!+MNdaA{>L|27L&)g3_`MCwec9j@~%5BfBI z{(*(f0g)1FyhpOA_O`TXdGho3P}Ev~2H{)_jJW5p?EoF|=n)PbgE8+MPW}L^8~WZ1 zrOi(u=Qs29S_y{vG@=+G)PYb-|`Ek1OeqbKDItIo9)g(FH0Cl|;t>}jmQ!39!joHlSXg_xjLvEXcs z-f7h><+AUnCY+DaSM7S9%NJClvL=qi3w^>twuA?`>XzPq_2sEH<;JbiH=_VZ*>cP zZ)f`;Gh4eDFaP)h{qK*Hi~iPo`wsnbY5yWW`>gF=EHSVcKRRiQemU`Xt;nMuCeL0>y5ZRd zh9`XWjOUv*_EYm8-!}bdWMd%iYd@dKZt0$d9-S$YJ`J(6B}?ba3s-8*akwk}4?i+B A4FCWD diff --git a/tests/regression_tests/deplete_with_reactivity_control/ref_depletion_with_rotation.h5 b/tests/regression_tests/deplete_with_reactivity_control/ref_depletion_with_rotation.h5 index e82060e5d037f3433336c4ac836a42fffad31c92..3831319390ece1aebcc4ee14e36bc11b913ad9e0 100644 GIT binary patch delta 1284 zcmZuveM}o=81KD>_O4StW=T(FvAjFmjUp}Y=W6vZuv1GgoUjk9G8rKQm1>r- zz*nX%ewU zUqoace0@WR z?`b%JKYnNZYFvLk?xMZ1WhCXLU!K=HD}d=S%`TIW6xz z_h8Ye+S9nJ_DQAd`-z3eb5WYoURHlR)~P8i9IO57?1riI==x9g;&N5#aJ%N``Meog z@?h=5_UntZFfrbdzoj;BsB5MNuMC)TzWcknZ_sNFy6~Cd;A^u&*p**tP1pF3VON&= zbe#)1C1G1GcE37_;|D^Y{ptKKht6#^glw5A`t2(&?_vmlq~%AJF1hh3v!U|2AA0bj zPL_~(&4rJ?FlFh>S-}sh&W{(*p1@YRV10V19;f$Be6%%nAJ4o*#}oU#IJYiwxjl#Q i7$YPjxtn7x#-}$|{$T>o>G)^-*7PQQuwYxMaQy>ragiPX delta 987 zcmZvYZAep57{~89H|DuF(Q9Q|WLspqX0)kOYc;2v%UeMW0~^wdmfC_qD~gID>4k+w zZ9Z+O5Ns(FVxgTZBdLvEd?8}?g4U-&5&KXyDoP4Qx9-$}4xICx=l}f9|2*gPQ|#-; zE;Uwp-J0{5F~if+J-wk52vj_Q9xNaMkYPJju}v(BLJ%t^N$02fcJW zVPXandxPH*Z)4fM{Vl=PHtA8w@98Q!zP`0^v*ozRc>5)ng4yklivLhlU2+!T=nm*BjbU8_?K3AK>^KaMEivpn2^r-be?FzrN+4=g@g=ea z!>o1(7ec^FT68MT5f{VYw<AgBt zuk*ZS7Nd~yYrUrJ(aY0D+vtIT+=l_T!FvCX!%L;TWE#-&Etn+9EDyX%K0UUKjIJkRg< zyubH;&pEH61rFJiXu-i2yp5Qu+0P{i>j#%4i}e|}Z1J!d(UmNp30?_~l*VwhA)zrE zW=#(6w)FlZm}Qp#1C!Ar;psof9?v8^Y9VvOk~m97ABtcaSeE8-wJ96BF#_1sn$Wr` z&6=_A7emp0W;$04>p3_$bDC!urTASX!}$t*XqTn8M6H9#gKCUs`J-TyNH7VgR7;oz zj+ye=n-=IQqQO&$LswxfN2h2JqSbI^UpoYnz7UlD^mIL>Rio z8QgH&Mgn3Uf`l!KaNTVy1UN^ES)Y{Xw+nnctbm*jZh1f=BZmJ~%|LDkw>^@K6L0>f z>Tz&SoXf+9B6^|%Pu<0)ALqzuzLQ%gQ+@nVWQrou@6E4WJKI_k!~0eg3A660Sf0rK zQr+-vTZ}*5aoTU(x)j47?|doo46{n=omcyg?i(d9d^41jkjm@G#evp4omGCaxA8I8 z7v@pYpc9=vhPRM|_1bQ6qo4fF=>O#2jcxMO<#_{l|1LFKlVP3zu!n-cdui}sTskDb zyhi0*+Qa9&vb~wqttN+}*le{=^q#&^Y__~r^%RF2&EN0PEfdeF%t^nl_mP`ln|@Y~ z;R7RUCiR(uS2e++$_ZwV+ch^?`ASRsGv{2}l~EUDTe$~LU5~r6W_%qz+7ovrYjbGG zn{JNVa=z)1{%_UA*k_N849Y+4joUJ?GZj28a>wwm%v5!L$tt;+^zxJchKY4iXi!0sB+vH?>y5h5>!MoJS gBl@u`Q!i5wZ?3p!fWY|P#IQA+)DInV{n_4s00;AeApigX delta 986 zcmZvYZAep57{_DrxE(1CNF^ZcLR`Jd;UUW&ul zu-}Lcp}>MOSmS_a_HEtrQ3M7ZfiBD;4v={RH84#KN`QoUh-wLnDk2YYtrsWE0VzZW z(vlO9*e!iWI*&#j`wHL*lLx)U93;y0tiN29JnYR4;ln7cuELxB|a>c(E}w@ zG4>WiOIZy>S4^PmGD|Q{l!94T$6yTs$(_5Xt61$pE>)vm4VqZ0HicF_^ic_} zq0uZ0NO=%-lNQ0i`ch&HB5oeI9^Ytz~IUn-B2 zLHc87hK^09RHCpvOG2qWVF!~-JqP0~s|l*oD3FV}5UoC*L0IySst?hVXXlZ_v81`{ z?9(1g%0$y}uaXgPNJs;aWym_3@uH7@GX782p zx_4&VI_E3d($tbuzI<-8WvFrG;F5)sW!aV!?jF}W-f~1(RqlM-jh4rg zOP_w6n6MT6th?9V3H7acmBSwfZFiy{o6bcI(Lxhn>(UoF*WaJsZ@F9KOgCAdrxCle zn@lR0v9{2e{rc98>z^Jv-ix!pUTz+6)choe%0>j&4f)&ENKd;fb7)iL>Btv|X!(GA T9D;4R>7(|I^G%M<4Tk71Er}@4 diff --git a/tests/regression_tests/deplete_with_reactivity_control/test.py b/tests/regression_tests/deplete_with_reactivity_control/test.py index 006187417a1..c9d0023f209 100644 --- a/tests/regression_tests/deplete_with_reactivity_control/test.py +++ b/tests/regression_tests/deplete_with_reactivity_control/test.py @@ -10,6 +10,10 @@ import openmc import openmc.lib from openmc.deplete import CoupledOperator +from openmc.deplete import ( + CellReactivityController, + MaterialReactivityController +) from tests.regression_tests import config @@ -78,13 +82,21 @@ def model(): return openmc.Model(geometry, materials, settings) -@pytest.mark.parametrize("obj, attribute, bracket_limit, axis, vec, ref_result", [ - ('trans_cell', 'translation', [-40,40], 2, None, 'depletion_with_translation'), - ('rot_cell', 'rotation', [-90,90], 2, None, 'depletion_with_rotation'), - ('f', 'refuel', [-100,100], None, {'U235':0.9, 'U238':0.1}, 'depletion_with_refuel') +@pytest.fixture +def mix_mat(): + mix_mat = openmc.Material() + mix_mat.add_element("U", 1, percent_type="ao", enrichment=90) + mix_mat.add_element("O", 2) + mix_mat.set_density("g/cc", 10.4) + return mix_mat + +@pytest.mark.parametrize("obj, attribute, bracket, bracket_limit, axis, ref_result", [ + ('trans_cell', 'translation', [-5,5], [-40,40], 2, 'depletion_with_translation'), + ('rot_cell', 'rotation', [-5,5], [-90,90], 2, 'depletion_with_rotation'), + ('f', None, [-1,2], [-100,100], None, 'depletion_with_refuel') ]) -def test_reactivity_control(run_in_tmpdir, model, obj, attribute, bracket_limit, - axis, vec, ref_result): +def test_reactivity_control(run_in_tmpdir, model, obj, attribute, bracket, + bracket_limit, axis, ref_result, mix_mat): chain_file = Path(__file__).parents[2] / 'chain_simple.xml' op = CoupledOperator(model, chain_file) @@ -92,16 +104,23 @@ def test_reactivity_control(run_in_tmpdir, model, obj, attribute, bracket_limit, integrator = openmc.deplete.PredictorIntegrator( op, [1], 174., timestep_units = 'd') - kwargs = {'bracket': [-4,4], 'bracket_limit':bracket_limit, - 'tol': 0.1,} - - if vec is not None: - kwargs['mat_vector']=vec - - if axis is not None: - kwargs['axis'] = axis - - integrator.add_reactivity_control(obj, attribute, **kwargs) + if attribute: + integrator.reactivity_control = CellReactivityController( + cell=obj, + operator=op, + attribute=attribute, + bracket=bracket, + bracket_limit=bracket_limit, + axis=axis + ) + else: + integrator.reactivity_control = MaterialReactivityController( + material=obj, + operator=op, + material_to_mix=mix_mat, + bracket=bracket, + bracket_limit=bracket_limit, + ) integrator.integrate() # Get path to test and reference results diff --git a/tests/unit_tests/test_deplete_reactivity_control.py b/tests/unit_tests/test_deplete_reactivity_control.py index 0f087be6ea0..a202226db89 100644 --- a/tests/unit_tests/test_deplete_reactivity_control.py +++ b/tests/unit_tests/test_deplete_reactivity_control.py @@ -9,9 +9,8 @@ import openmc.lib from openmc.deplete import CoupledOperator from openmc.deplete import ( - GeometricalCellReactivityController, - TemperatureCellReactivityController, - RefuelMaterialReactivityController + CellReactivityController, + MaterialReactivityController ) CHAIN_PATH = Path(__file__).parents[1] / "chain_simple.xml" @@ -74,62 +73,52 @@ def integrator(operator): return openmc.deplete.PredictorIntegrator( operator, [1,1], 0.0, timestep_units = 'd') -@pytest.mark.parametrize("case_name, obj, attribute, bracket, limit, axis, vec", [ - ('cell translation','universe_cell', 'translation', [-1,1], [-10,10], 2, None), - ('cell rotation', 'universe_cell', 'rotation', [-1,1], [-10,10], 2, None), - ('single cell temperature', 'fuel_cell', 'temperature', [-1,1], [-10,10], 2, None ), - ('multi cell', 'universe_cell', 'temperature', [-1,1], [-10,10], 2, None ), - ('material refuel', 'fuel', 'refuel', [-1,1], [-10,10], None, {'U235':0.1, 'U238':0.9}), - ('invalid_1', 'universe_cell', 'refuel', [-1,1], [-10,10], None, {'U235':0.1, 'U238':0.9}), - ('invalid_2', 'fuel', 'temperature', [-1,1], [-10,10], 2, None ), - ('invalid_3', 'fuel', 'translation', [-1,1], [-10,10], 2, None), +@pytest.fixture +def mix_mat(): + mix_mat = openmc.Material() + mix_mat.add_element("U", 1, percent_type="ao", enrichment=90) + mix_mat.add_element("O", 2) + mix_mat.set_density("g/cc", 10.4) + return mix_mat + +@pytest.mark.parametrize("case_name, obj, attribute, bracket, limit, axis", [ + ('cell translation','universe_cell', 'translation', [-1,1], [-10,10], 2), + ('cell rotation', 'universe_cell', 'rotation', [-1,1], [-10,10], 2), + ('material mix', 'fuel', None, [0,5], [-10,10], None), ]) def test_attributes(case_name, model, operator, integrator, obj, attribute, - bracket, limit, axis, vec): + bracket, limit, axis, mix_mat): """ Test classes attributes are set correctly """ + if attribute: + integrator.reactivity_control = CellReactivityController( + cell=obj, + operator=operator, + attribute=attribute, + bracket=bracket, + bracket_limit=limit, + axis=axis + ) + assert integrator.reactivity_control.depletable_cells == [cell for cell in \ + model.geometry.get_cells_by_name(obj)[0].fill.cells.values() \ + if cell.fill.depletable] + assert integrator.reactivity_control.axis == axis - kwargs = {'bracket': bracket, 'bracket_limit':limit, 'tol': 0.1,} - - if vec is not None: - kwargs['mat_vector']=vec - - if axis is not None: - kwargs['axis'] = axis - - if case_name == "invalid_1": - with pytest.raises(ValueError) as e: - integrator.add_reactivity_control(obj, attribute, **kwargs) - assert str(e.value) == 'Unable to set "Material name" to "universe_cell" '\ - 'since it is not in "[\'fuel\', \'water\']"' - elif case_name == "invalid_2": - with pytest.raises(ValueError) as e: - integrator.add_reactivity_control(obj, attribute, **kwargs) - assert str(e.value) == 'Unable to set "Cell name exists" to "fuel" since '\ - 'it is not in "[\'fuel_cell\', \'universe_cell\', \'\', \'\']"' - - elif case_name == "invalid_3": - with pytest.raises(ValueError) as e: - integrator.add_reactivity_control(obj, attribute, **kwargs) - assert str(e.value) == 'Unable to set "Cell name exists" to "fuel" since '\ - 'it is not in "[\'fuel_cell\', \'universe_cell\', \'\', \'\']"' else: - integrator.add_reactivity_control(obj, attribute, **kwargs) - if attribute in ('translation','rotation'): - assert integrator.reactivity_control.universe_cells == [cell for cell in \ - model.geometry.get_cells_by_name(obj)[0].fill.cells.values() \ - if cell.fill.depletable] - assert integrator.reactivity_control.axis == axis - - elif attribute == 'refuel': - assert integrator.reactivity_control.mat_vector == vec - - assert integrator.reactivity_control.attrib_name == attribute - assert integrator.reactivity_control.bracket == bracket - assert integrator.reactivity_control.bracket_limit == limit - assert integrator.reactivity_control.burn_mats == operator.burnable_mats - assert integrator.reactivity_control.local_mats == operator.local_mats + integrator.reactivity_control = MaterialReactivityController( + material=obj, + operator=operator, + material_to_mix=mix_mat, + bracket=bracket, + bracket_limit=limit + ) + assert integrator.reactivity_control.material_to_mix == mix_mat + + assert integrator.reactivity_control.bracket == bracket + assert integrator.reactivity_control.bracket_limit == limit + assert integrator.reactivity_control.burn_mats == operator.burnable_mats + assert integrator.reactivity_control.local_mats == operator.local_mats @pytest.mark.parametrize("obj, attribute, value_to_set", [ ('universe_cell', 'translation', 0), @@ -140,9 +129,14 @@ def test_cell_methods(run_in_tmpdir, model, operator, integrator, obj, attribute """ Test cell base class internal method """ - kwargs = {'bracket':[-1,1], 'bracket_limit':[-10,10], 'axis':2, 'tol':0.1} - - integrator.add_reactivity_control(obj, attribute, **kwargs) + integrator.reactivity_control = CellReactivityController( + cell=obj, + operator=operator, + attribute=attribute, + bracket=[-1,1], + bracket_limit=[-10,10], + axis=2 + ) model.export_to_xml() openmc.lib.init() @@ -150,63 +144,79 @@ def test_cell_methods(run_in_tmpdir, model, operator, integrator, obj, attribute integrator.reactivity_control._set_cell_attrib(value_to_set) assert integrator.reactivity_control._get_cell_attrib() == value_to_set - vol = integrator.reactivity_control._calculate_volumes() + vol = integrator.reactivity_control._adjust_volumes() integrator.reactivity_control._update_x_and_set_volumes(operator.number.number, vol) - for cell in integrator.reactivity_control.universe_cells: + for cell in integrator.reactivity_control.depletable_cells: mat_id = str(cell.fill.id) index_mat = operator.number.index_mat[mat_id] assert vol[mat_id] == operator.number.volume[index_mat] openmc.lib.finalize() -@pytest.mark.parametrize("nuclide, atoms_to_add", [ - ('U238', 1.0e22), - ('Xe135', 1.0e21) +@pytest.mark.parametrize("units, value, dilute", [ + ('cc', 1.0, True), + ('cm3', 1.0, False), + ('grams', 1.0, True), + ('grams', 1.0, False), + ('atoms', 1.0, True), + ('atoms', 1.0, False), ]) -def test_internal_methods(run_in_tmpdir, model, operator, integrator, nuclide, - atoms_to_add): +def test_internal_methods(run_in_tmpdir, model, operator, integrator, mix_mat, + units, value, dilute): """ Method to update volume in AtomNumber after depletion step. Method inheritated by all derived classes so one check is enough. """ - kwargs = {'bracket':[-1,1], 'bracket_limit':[-10,10], 'mat_vector':{}} - - integrator.add_reactivity_control('fuel', 'refuel', **kwargs) + integrator.reactivity_control = MaterialReactivityController( + material='fuel', + operator=operator, + material_to_mix=mix_mat, + bracket=[-1,1], + bracket_limit=[-10,10], + units=units, + dilute=dilute + ) model.export_to_xml() openmc.lib.init() - #Increase number of atoms of U238 in fuel by fix amount and check the - # volume increase at constant-density - #extract fuel material from model materials + # check material volume are adjusted correctly mat = integrator.reactivity_control.material mat_index = operator.number.index_mat[str(mat.id)] - nuc_index = operator.number.index_nuc[nuclide] - vol = operator.number.get_mat_volume(str(mat.id)) - operator.number.number[mat_index][nuc_index] += atoms_to_add - integrator.reactivity_control._update_volumes() - - vol_to_compare = vol + (atoms_to_add * openmc.data.atomic_mass(nuclide) /\ - openmc.data.AVOGADRO / mat.density) - - assert operator.number.get_mat_volume(str(mat.id)) == pytest.approx(vol_to_compare) + nuc_index = operator.number.index_nuc['U235'] + vol_before_mix = operator.number.get_mat_volume(str(mat.id)) + + # set mix_mat volume + integrator.reactivity_control._set_mix_material_volume(value) + mix_mat_vol = mix_mat.volume + #extract nuclide in atoms + mix_nuc_atoms = mix_mat.get_nuclide_atoms()['U235'] + operator.number.number[mat_index][nuc_index] += mix_nuc_atoms + #update volume + vol_after_mix = integrator.reactivity_control._adjust_volumes(mix_mat_vol) + + if dilute: + assert vol_before_mix + mix_mat_vol == vol_after_mix[str(mat.id)] + else: + assert vol_before_mix == vol_after_mix[str(mat.id)] + #check nuclide densities get assigned correctly in memory x = [i[:operator.number.n_nuc_burn] for i in operator.number.number] integrator.reactivity_control._update_materials(x) - nuc_index_lib = openmc.lib.materials[mat.id].nuclides.index(nuclide) - dens_to_compare = 1.0e-24 * operator.number.get_atom_density(str(mat.id), nuclide) + nuc_index_lib = openmc.lib.materials[mat.id].nuclides.index('U235') + dens_to_compare = 1.0e-24 * operator.number.get_atom_density(str(mat.id), 'U235') assert openmc.lib.materials[mat.id].densities[nuc_index_lib] == pytest.approx(dens_to_compare) - volumes = {str(mat.id): vol + 1} - new_x = integrator.reactivity_control._update_x_and_set_volumes(x, volumes) - dens_to_compare = 1.0e24 * volumes[str(mat.id)] *\ + # check volumes get assigned correctly in AtomNumber + new_x = integrator.reactivity_control._update_x_and_set_volumes(x, vol_after_mix) + dens_to_compare = 1.0e24 * vol_after_mix[str(mat.id)] *\ openmc.lib.materials[mat.id].densities[nuc_index_lib] assert new_x[mat_index][nuc_index] == pytest.approx(dens_to_compare) # assert volume in AtomNumber is set correctly - assert operator.number.get_mat_volume(str(mat.id)) == volumes[str(mat.id)] + assert operator.number.get_mat_volume(str(mat.id)) == vol_after_mix[str(mat.id)] openmc.lib.finalize() From e7c15cfa4ff573fb131bc5621fb8c7b703f75a0f Mon Sep 17 00:00:00 2001 From: church89 Date: Thu, 6 Jun 2024 13:47:02 +0200 Subject: [PATCH 59/59] add forgotten argument to StepResult save method --- openmc/deplete/abc.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py index b8d2e1ef917..b2dfdcde2a4 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -31,7 +31,7 @@ from .transfer_rates import TransferRates from openmc import Material, Cell from .reactivity_control import ( - ReactivityController, + ReactivityController, CellReactivityController, MaterialReactivityController ) @@ -868,7 +868,7 @@ def integrate( # Remove actual EOS concentration for next step n = n_list.pop() StepResult.save(self.operator, n_list, res_list, [t, t + dt], - source_rate, self._i_res + i, proc_time, root, path) + source_rate, self._i_res + i, proc_time, root, path) t += dt @@ -884,7 +884,7 @@ def integrate( root = None res_list = [self.operator(n, source_rate if final_step else 0.0)] StepResult.save(self.operator, [n], res_list, [t, t], - source_rate, self._i_res + len(self), proc_time, root) + source_rate, self._i_res + len(self), proc_time, root, path) self.operator.write_bos_data(len(self) + self._i_res) self.operator.finalize()