From 17f10c4055e1dd64b87a2beb5bac057f24ee6daa Mon Sep 17 00:00:00 2001 From: church89 Date: Mon, 19 Jan 2026 14:35:00 +0100 Subject: [PATCH] remove obsolete bits and update unit test --- openmc/deplete/abc.py | 4 +- openmc/search.py | 39 +-- .../test_deplete_reactivity_control.py | 312 ++++++++++-------- 3 files changed, 177 insertions(+), 178 deletions(-) diff --git a/openmc/deplete/abc.py b/openmc/deplete/abc.py index de76a891bc..47ee997c4a 100644 --- a/openmc/deplete/abc.py +++ b/openmc/deplete/abc.py @@ -20,10 +20,10 @@ from warnings import warn import numpy as np from uncertainties import ufloat -from openmc.checkvalue import check_value, check_type, check_greater_than, PathLike +from openmc.checkvalue import check_type, check_greater_than, PathLike from openmc.mpi import comm from openmc.utility_funcs import change_directory -from openmc import Material, Cell +from openmc import Material from .stepresult import StepResult from .chain import _get_chain from .results import Results, _SECONDS_PER_MINUTE, _SECONDS_PER_HOUR, \ diff --git a/openmc/search.py b/openmc/search.py index e372a40a0f..cff4ef14d5 100644 --- a/openmc/search.py +++ b/openmc/search.py @@ -1,6 +1,5 @@ from collections.abc import Callable from numbers import Real -from warnings import warn import scipy.optimize as sopt @@ -13,7 +12,7 @@ _SCALAR_BRACKETED_METHODS = {'brentq', 'brenth', 'ridder', 'bisect'} def _search_keff(guess, target, model_builder, model_args, print_iterations, - run_args, guesses, results, run_in_memory): + run_args, guesses, results): """Function which will actually create our model, run the calculation, and obtain the result. This function will be passed to the root finding algorithm @@ -40,8 +39,7 @@ 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 @@ -53,11 +51,7 @@ def _search_keff(guess, target, model_builder, model_args, print_iterations, model = model_builder(guess, **model_args) # Run the model and obtain keff - if run_in_memory: - openmc.lib.run(**run_args) - sp_filepath = f'statepoint.{model.settings.batches}.h5' - else: - sp_filepath = model.run(**run_args) + sp_filepath = model.run(**run_args) with openmc.StatePoint(sp_filepath) as sp: keff = sp.keff @@ -76,7 +70,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, run_in_memory=False, **kwargs): + run_args=None, **kwargs): """Function to perform a keff search by modifying a model parametrized by a single independent variable. @@ -111,11 +105,6 @@ 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 All remaining keyword arguments are passed to the root-finding method. @@ -202,26 +191,12 @@ 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_in_memory) + run_args, guesses, results) # Create a new dictionary with the arguments from args and kwargs args.update(kwargs) # Perform the search - if not run_in_memory: - zero_value = root_finder(**args) - return zero_value, guesses, results + zero_value = root_finder(**args) - else: - try: - zero_value, root_res = root_finder(**args, full_output=True, disp=False) - if root_res.converged: - return zero_value, guesses, results - - else: - warn(f'{root_res.flag}') - return guesses, results - # In case the root finder is not successful - except Exception as e: - warn(f'{e}') - return guesses, results + return zero_value, guesses, results \ No newline at end of file diff --git a/tests/unit_tests/test_deplete_reactivity_control.py b/tests/unit_tests/test_deplete_reactivity_control.py index a202226db8..506ef42641 100644 --- a/tests/unit_tests/test_deplete_reactivity_control.py +++ b/tests/unit_tests/test_deplete_reactivity_control.py @@ -8,10 +8,7 @@ import numpy as np import openmc import openmc.lib from openmc.deplete import CoupledOperator -from openmc.deplete import ( - CellReactivityController, - MaterialReactivityController -) +from openmc.deplete import ReactivityController CHAIN_PATH = Path(__file__).parents[1] / "chain_simple.xml" @@ -73,150 +70,177 @@ def integrator(operator): return openmc.deplete.PredictorIntegrator( operator, [1,1], 0.0, timestep_units = 'd') -@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, 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 - - else: - 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), - ('universe_cell', 'rotation', 0) - ]) -def test_cell_methods(run_in_tmpdir, model, operator, integrator, obj, attribute, - value_to_set): - """ - Test cell base class internal method - """ - 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() - 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._adjust_volumes() - integrator.reactivity_control._update_x_and_set_volumes(operator.number.number, vol) - - 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("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, 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. - """ - - integrator.reactivity_control = MaterialReactivityController( - material='fuel', - operator=operator, - material_to_mix=mix_mat, - bracket=[-1,1], - bracket_limit=[-10,10], - units=units, - dilute=dilute +def test_reactivity_controller_init(operator): + """Test ReactivityController initialization""" + def dummy_function(x): + return x + + # Valid initialization + controller = ReactivityController( + operator=operator, + function=dummy_function, + x0=0.0, + x1=1.0, + bracket=[0.0, 2.0] ) + + assert controller.operator == operator + assert controller.function == dummy_function + assert controller.x0 == 0.0 + assert controller.x1 == 1.0 + assert controller.kwargs['x_min'] == 0.0 + assert controller.kwargs['x_max'] == 2.0 + + # Test invalid bracket with wrong length + with pytest.raises(ValueError, match="bracket must have exactly 2 elements"): + ReactivityController(operator, dummy_function, 0.0, 1.0, [0.0]) + + # Test invalid bracket with wrong order + with pytest.raises(ValueError, match="bracket\\[0\\] must be < bracket\\[1\\]"): + ReactivityController(operator, dummy_function, 0.0, 1.0, [2.0, 1.0]) +def test_reactivity_controller_call(operator): + """Test the __call__ method acts as a proxy to the function""" + call_log = [] + + def test_function(x): + call_log.append(x) + return x * 2 + + controller = ReactivityController( + operator=operator, + function=test_function, + x0=0.0, + x1=1.0, + bracket=[0.0, 2.0] + ) + + # Test that calling the controller calls the underlying function + result = controller(5.0) + assert result == 10.0 + assert call_log == [5.0] + + # Test with multiple arguments + def multi_arg_function(x, y, z=3): + return x + y + z + + controller2 = ReactivityController( + operator=operator, + function=multi_arg_function, + x0=0.0, + x1=1.0, + bracket=[0.0, 2.0] + ) + + result = controller2(1, 2, z=4) + assert result == 7 + +def translate_cell(position): + """Helper function to translate a cell""" + cell = [c for c in openmc.lib.cells.values() if c.name == 'universe_cell'][0] + openmc.lib.cells[cell.id].translation = [0, 0, position] + return position + + +def rotate_cell(angle): + """Helper function to rotate a cell""" + cell = [c for c in openmc.lib.cells.values() if c.name == 'universe_cell'][0] + openmc.lib.cells[cell.id].rotation = [0, 0, angle] + return angle + + +def adjust_fuel_density(density_factor): + """Helper function to adjust fuel density""" + fuel = [m for m in openmc.lib.materials.values() if m.name == 'fuel'][0] + nuclides = openmc.lib.materials[fuel.id].nuclides + current_densities = openmc.lib.materials[fuel.id].densities + new_densities = [d * density_factor for d in current_densities] + openmc.lib.materials[fuel.id].set_densities(nuclides, new_densities) + return density_factor + +@pytest.mark.parametrize("function, x0, x1, bracket, test_value", [ + (translate_cell, -1.0, 1.0, [-5.0, 5.0], 0.5), + (rotate_cell, -45.0, 45.0, [-90.0, 90.0], 10.0), + (adjust_fuel_density, 0.8, 1.2, [0.5, 1.5], 1.0) +]) +def test_reactivity_controller_with_openmc_functions( + run_in_tmpdir, model, operator, function, x0, x1, bracket, test_value +): + """Test ReactivityController with actual OpenMC geometry modification functions""" + + controller = ReactivityController( + operator=operator, + function=function, + x0=x0, + x1=x1, + bracket=bracket, + kwargs={'output': False, 'k_tol': 0.1} + ) + + # Export model and initialize OpenMC library model.export_to_xml() openmc.lib.init() + + try: + # Test that the controller can be called to modify the geometry + result = controller(test_value) + assert result == test_value + + # Verify the function was actually called by checking it doesn't raise + # (actual verification would require checking geometry state) + + finally: + openmc.lib.finalize() - # 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['U235'] - vol_before_mix = operator.number.get_mat_volume(str(mat.id)) +def test_controller_kwargs_handling(operator): + """Test that additional kwargs are properly stored and accessible""" + def dummy_func(x): + return x + + custom_kwargs = { + 'target': 1.0, + 'k_tol': 1e-4, + 'output': True + } + + controller = ReactivityController( + operator=operator, + function=dummy_func, + x0=0.0, + x1=1.0, + bracket=[0.0, 2.0], + kwargs=custom_kwargs + ) + + # Check that bracket limits were added to kwargs + assert controller.kwargs['x_min'] == 0.0 + assert controller.kwargs['x_max'] == 2.0 + + # Check that custom kwargs were preserved + assert controller.kwargs['target'] == 1.0 + assert controller.kwargs['k_tol'] == 1e-4 + assert controller.kwargs['output'] == True - # 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) +def test_integrator_add_reactivity_control(run_in_tmpdir, model, operator, integrator): + """Test adding reactivity control to integrator using the add_reactivity_control method""" + + def adjust_density(factor): + # This would modify material density in practice + return factor + + # Test that add_reactivity_control method exists and works + integrator.add_reactivity_control( + function=adjust_density, + x0=0.8, + x1=1.2, + bracket=[0.5, 1.5], + kwargs={'k_tol': 0.1, 'output': False} + ) + + assert integrator.reactivity_control is not None + assert isinstance(integrator.reactivity_control, ReactivityController) + assert integrator.reactivity_control.x0 == 0.8 + assert integrator.reactivity_control.x1 == 1.2 + assert integrator.reactivity_control.kwargs['x_min'] == 0.5 + assert integrator.reactivity_control.kwargs['x_max'] == 1.5 - 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('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) - - # 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)) == vol_after_mix[str(mat.id)] - - openmc.lib.finalize()