diff --git a/openmc/deplete/keff_search_control.py b/openmc/deplete/keff_search_control.py index 49f7cc4dff3..5cd50ed6555 100644 --- a/openmc/deplete/keff_search_control.py +++ b/openmc/deplete/keff_search_control.py @@ -54,6 +54,12 @@ def run(self, x): root : float Parameter value that achieves target keff """ + # This runs before the operator is called for this step, so + # openmc.lib.materials and the operator's AtomNumber still hold the + # previous step's compositions. Push `x` in first, otherwise the search + # runs on stale materials and _update_vec() reverts `x` to them. + self.operator._update_materials_and_nuclides(x) + root = self._search_for_keff() self._update_vec(x) return root diff --git a/tests/regression_tests/deplete_with_keff_search_control/test.py b/tests/regression_tests/deplete_with_keff_search_control/test.py index 82e6012809f..d326e69ad13 100644 --- a/tests/regression_tests/deplete_with_keff_search_control/test.py +++ b/tests/regression_tests/deplete_with_keff_search_control/test.py @@ -138,3 +138,9 @@ def test_keff_search_control(run_in_tmpdir, model, function, x0, x1, bracket, re # Use high tolerance here assert res_test[0].keff_search_root == pytest.approx(res_ref[0].keff_search_root, rel=2) + + # The keff search must not clobber the depleted compositions. If the search + # runs against the previous step's materials, the depletion vector is + # reverted to its beginning-of-step values and no fission products appear. + _, xe135 = res_test.get_atoms(model.materials[0], 'Xe135') + assert xe135[-1] > 0.0 diff --git a/tests/unit_tests/test_deplete_keff_search_control.py b/tests/unit_tests/test_deplete_keff_search_control.py index 425b8f84053..b21397338d6 100644 --- a/tests/unit_tests/test_deplete_keff_search_control.py +++ b/tests/unit_tests/test_deplete_keff_search_control.py @@ -1,6 +1,7 @@ """ Tests for KeffSearchControl class """ from pathlib import Path +from unittest.mock import Mock import pytest import numpy as np @@ -8,6 +9,7 @@ import openmc import openmc.lib from openmc.deplete import CoupledOperator +from openmc.deplete.keff_search_control import _KeffSearchControl CHAIN_PATH = Path(__file__).parents[1] / "chain_simple.xml" @@ -114,3 +116,26 @@ def test_integrator_add_keff_search_control(run_in_tmpdir, function, x0, x1, bra assert integrator._keff_search_control.search_kwargs['x_max'] == bracket[1] assert integrator._keff_search_control.search_kwargs['k_tol'] == 0.1 assert not integrator._keff_search_control.search_kwargs['output'] + + +def test_materials_updated_before_search(monkeypatch): + """Test that compositions reach the operator before the keff search runs + + The search happens at the beginning of a depletion step, before the + transport operator is called. Without the update, the search uses the + previous step's materials and _update_vec() reverts the depletion vector + to them. + """ + recorder = Mock() + recorder.search.return_value = 0.5 + control = _KeffSearchControl(recorder.operator, lambda x: None, + x0=0.0, x1=1.0, bracket=[0.0, 2.0]) + monkeypatch.setattr(control, '_search_for_keff', recorder.search) + monkeypatch.setattr(control, '_update_vec', recorder.update_vec) + + n = [np.array([1.0, 2.0, 3.0])] + + assert control.run(n) == 0.5 + assert [c[0] for c in recorder.mock_calls] == [ + 'operator._update_materials_and_nuclides', 'search', 'update_vec'] + assert recorder.mock_calls[0].args[0] is n