From f50a5a70da71bad127a55585276a3d266effcc76 Mon Sep 17 00:00:00 2001 From: John Tramm Date: Fri, 17 Jul 2026 20:40:03 +0000 Subject: [PATCH] Resolve convert_to_multigroup settings by layering user overrides onto defaults Replaces the mgxs_generation_settings getter (and its recorded-method inference) with merge semantics: the user passes a sparse Settings object and only its populated attributes override the generation defaults, via the restored Settings.update() helper. The run mode is the one attribute whose default value makes it undetectable as user-populated, so it is owned by the generation method: material_wise always takes it from the model and the surrogate methods always force fixed source mode. Co-Authored-By: Claude Fable 5 --- docs/source/usersguide/random_ray.rst | 25 +-- openmc/model/model.py | 185 +++++++----------- openmc/settings.py | 51 ++++- .../random_ray_auto_convert/test.py | 5 +- .../random_ray_auto_convert_bootstrap/test.py | 8 +- .../test.py | 5 +- .../test.py | 5 +- .../test.py | 6 +- .../random_ray_diagonal_stabilization/test.py | 6 +- .../dagmc/test_convert_to_multigroup.py | 4 +- tests/unit_tests/test_model.py | 96 +++------ tests/unit_tests/test_settings.py | 29 +++ tests/unit_tests/weightwindows/test_ww_gen.py | 4 +- 13 files changed, 188 insertions(+), 241 deletions(-) diff --git a/docs/source/usersguide/random_ray.rst b/docs/source/usersguide/random_ray.rst index c793cf22ff..5a81495dc8 100644 --- a/docs/source/usersguide/random_ray.rst +++ b/docs/source/usersguide/random_ray.rst @@ -700,23 +700,13 @@ existing MGXS library file, or ``False`` to skip generation and use an existing library file. The continuous energy simulations used to generate the cross section library -can be customized via the ``settings`` parameter. To do so, start from the -default settings returned by :meth:`openmc.Model.mgxs_generation_settings`, -modify them as desired, and pass the result back. For example, the number of -particles per batch (2,000 by default) can be increased to improve the fidelity -of the generated cross section library as:: +can be customized via the ``settings`` parameter. Only the attributes that are +populated on the passed :class:`openmc.Settings` object override the +generation defaults, so a sparse object adjusts just the fields you set. For +example, the number of particles per batch (2,000 by default) can be increased +to improve the fidelity of the generated cross section library as:: - settings = model.mgxs_generation_settings() - settings.particles = 100_000 - model.convert_to_multigroup(settings=settings) - -The settings returned by :meth:`openmc.Model.mgxs_generation_settings` record -the method they were generated for, so a non-default method only needs to be -given once:: - - settings = model.mgxs_generation_settings("stochastic_slab") - settings.particles = 100_000 - model.convert_to_multigroup(settings=settings) + model.convert_to_multigroup(settings=openmc.Settings(particles=100_000)) .. note:: MGXS transport correction (via setting the ``correction`` parameter in the @@ -807,8 +797,7 @@ on the generation settings:: # Then, bootstrap a higher fidelity material-wise library, applying those # weight windows during the continuous energy solve so that particles can # reach materials far from the source. - settings = model.mgxs_generation_settings() - settings.weight_windows_file = "weight_windows.h5" + settings = openmc.Settings(weight_windows_file="weight_windows.h5") model.convert_to_multigroup(settings=settings, overwrite_mgxs_library=True) A weight windows file on the generation settings is only used with the diff --git a/openmc/model/model.py b/openmc/model/model.py index 8728e3aa9c..ac33c246e1 100644 --- a/openmc/model/model.py +++ b/openmc/model/model.py @@ -2604,66 +2604,9 @@ class Model: mgxs_file.add_xsdata(mgxs_set) mgxs_file.export_to_hdf5(mgxs_path) - def mgxs_generation_settings( - self, method: str = "material_wise" - ) -> openmc.Settings: - """Default settings for the simulations used to generate a MGXS library. - - Returns the :class:`openmc.Settings` object that - :meth:`Model.convert_to_multigroup` would use by default for the given - generation method: a copy of the model's own settings for the - ``"material_wise"`` method, or a fresh Settings object for the - surrogate-geometry methods (``"stochastic_slab"`` and - ``"infinite_medium"``, which also inherit the model's temperature - settings), with the generation defaults for batches, inactive - batches, particles, and output applied. To customize MGXS - generation, modify the returned object and pass it back via the - ``settings`` argument:: - - settings = model.mgxs_generation_settings() - settings.particles = 100_000 - model.convert_to_multigroup(settings=settings) - - The returned object records the method it was generated for, which - :meth:`Model.convert_to_multigroup` uses as its default ``method``, - so a non-default method only needs to be given here. - - .. versionadded:: 0.15.4 - - Parameters - ---------- - method : {"material_wise", "stochastic_slab", "infinite_medium"}, optional - MGXS generation method the settings are intended for. - - Returns - ------- - openmc.Settings - Default settings for the MGXS generation run(s). - """ - check_value('method', method, - ('material_wise', 'stochastic_slab', 'infinite_medium')) - if method == 'material_wise': - settings = copy.deepcopy(self.settings) - else: - settings = openmc.Settings() - settings.temperature = copy.deepcopy(self.settings.temperature) - # The surrogate-geometry methods always run in fixed source mode - # with fission treated as capture (nu-fission is still tallied) - settings.run_mode = 'fixed source' - settings.create_fission_neutrons = False - - settings.batches = 100 if method == 'infinite_medium' else 200 - if method != 'infinite_medium': - settings.inactive = 100 - settings.particles = 2000 - settings.output = {'summary': True, 'tallies': False} - # Record the method so convert_to_multigroup can default to it - settings._mgxs_generation_method = method - return settings - def convert_to_multigroup( self, - method: str | None = None, + method: str = "material_wise", groups: str | Sequence[float] | openmc.mgxs.EnergyGroups = "CASMO-2", nparticles: int | None = None, overwrite_mgxs_library: bool = False, @@ -2682,11 +2625,7 @@ class Model: Parameters ---------- method : {"material_wise", "stochastic_slab", "infinite_medium"}, optional - Method to generate the MGXS. If not given, defaults to the - method that the ``settings`` object was generated for by - :meth:`Model.mgxs_generation_settings`, or ``"material_wise"`` - otherwise. Giving a method here that conflicts with the one the - settings were generated for is an error. + Method to generate the MGXS. groups : openmc.mgxs.EnergyGroups, str, or sequence of float, optional Energy group structure for the MGXS. Can be an :class:`openmc.mgxs.EnergyGroups` object, a string name of a @@ -2739,15 +2678,27 @@ class Model: Set ``temperature`` on the object passed via the ``settings`` argument instead. settings : openmc.Settings, optional - Settings used verbatim for the continuous energy simulation(s) - that generate the MGXS library. If not provided, defaults are - taken from :meth:`Model.mgxs_generation_settings`; to customize - a run, start from those defaults, modify them, and pass the - result here. Note that the ``"stochastic_slab"`` and - ``"infinite_medium"`` methods construct their own fixed source - and set ``run_mode``, ``source``, and - ``create_fission_neutrons`` accordingly. If the settings include - a ``weight_windows_file`` (e.g., ``"weight_windows.h5"``), the + Settings for customizing the continuous energy simulation(s) + used to generate the MGXS library. Only attributes that are + populated override the generation defaults, so a sparse object + may be used to adjust just a few fields, e.g. + ``settings=openmc.Settings(particles=100_000)`` only increases + the particle count. The settings of the generation run are + resolved in three layers, with later layers taking precedence: + (1) the model's own settings for the ``"material_wise"`` method, + or a fresh :class:`openmc.Settings` object for the + surrogate-geometry methods (which also inherit the model's + temperature settings); (2) the generation defaults (200 batches + with 100 inactive for ``"material_wise"`` and + ``"stochastic_slab"``, 100 batches for ``"infinite_medium"``, + 2000 particles, and summary-only output); (3) all populated + attributes of this object (see :meth:`openmc.Settings.update`). + The run mode cannot be set here: ``"material_wise"`` always + takes it from the model, while the ``"stochastic_slab"`` and + ``"infinite_medium"`` methods always run in fixed source mode + with ``create_fission_neutrons`` disabled and construct their + own sources. If the resolved settings include a + ``weight_windows_file`` (e.g., ``"weight_windows.h5"``), the ``"material_wise"`` method loads and applies those weight windows during the continuous energy generation simulation (``weight_windows_on`` is enabled automatically). Applying @@ -2758,35 +2709,20 @@ class Model: first generate weight windows with the ``"stochastic_slab"`` method and the random ray solver, then "bootstrap" a higher-fidelity ``"material_wise"`` library by setting those - weight windows here; a warning is issued and the file is ignored - for the ``"stochastic_slab"`` and ``"infinite_medium"`` methods. - Cannot be combined with the deprecated ``nparticles`` or - ``temperature_settings`` arguments. + weight windows here; a warning is issued and the file is + ignored for the ``"stochastic_slab"`` and ``"infinite_medium"`` + methods. Cannot be combined with the deprecated ``nparticles`` + or ``temperature_settings`` arguments. .. versionadded:: 0.15.4 """ if not isinstance(groups, openmc.mgxs.EnergyGroups): groups = openmc.mgxs.EnergyGroups(groups) - # Resolve the generation method: an explicit argument wins, otherwise - # the method the provided settings were generated for (recorded by - # mgxs_generation_settings) is used, defaulting to "material_wise". - # Conflicting specifications are rejected, since the generation - # defaults differ by method. - if settings is not None: - check_type('settings', settings, openmc.Settings) - settings_method = (settings._mgxs_generation_method - if settings is not None else None) - if method is None: - method = settings_method or 'material_wise' - elif settings_method is not None and method != settings_method: - raise ValueError( - f'The "{method}" generation method conflicts with the ' - 'provided settings, which were generated for the ' - f'"{settings_method}" method by ' - 'Model.mgxs_generation_settings().') check_value('method', method, ('material_wise', 'stochastic_slab', 'infinite_medium')) + if settings is not None: + check_type('settings', settings, openmc.Settings) # The model may reference its materials only through the geometry. # The materials are converted in place and library-wide attributes @@ -2798,46 +2734,61 @@ class Model: self.geometry.get_all_materials().values(), key=lambda mat: mat.id)) - # Resolve the settings for the MGXS generation run(s): the provided - # settings are used verbatim, otherwise the generation defaults are - # used (with the deprecated arguments applied, if given). if nparticles is not None or temperature_settings is not None: warnings.warn( 'The "nparticles" and "temperature_settings" arguments are ' - 'deprecated. Customize MGXS generation by modifying the ' - 'Settings object returned by Model.mgxs_generation_settings()' - ' and passing it via the "settings" argument.', FutureWarning) + 'deprecated. Pass a Settings object with the desired ' + 'attributes via the "settings" argument instead.', + FutureWarning) if settings is not None: raise ValueError( 'The deprecated "nparticles" and "temperature_settings" ' 'arguments cannot be combined with the "settings" ' 'argument.') - if settings is None: - settings = self.mgxs_generation_settings(method) - if nparticles is not None: - settings.particles = nparticles - if temperature_settings is not None: - settings.temperature = temperature_settings + # Resolve the settings for the MGXS generation run(s) in three + # layers, with later layers taking precedence: the model's own + # settings ("material_wise") or a fresh Settings object (surrogate + # methods), then the generation defaults, then any attributes the + # user populated on the provided settings. + user_settings = settings + if method == 'material_wise': + settings = copy.deepcopy(self.settings) else: - # batches and particles are required for any OpenMC transport - # run; catch their absence here so a hand-built settings object - # fails with a pointer to the defaults rather than a mysterious - # error from inside the generation run - if settings.batches is None or settings.particles is None: - raise ValueError( - 'The provided settings are missing required attributes ' - '(batches, particles). Start from the defaults returned ' - 'by Model.mgxs_generation_settings() and modify them ' - 'rather than building a Settings object from scratch.') - settings = copy.deepcopy(settings) + settings = openmc.Settings() + settings.temperature = copy.deepcopy(self.settings.temperature) + # The surrogate-geometry methods treat fission as capture + # (nu-fission is still tallied) + settings.create_fission_neutrons = False + settings.batches = 100 if method == 'infinite_medium' else 200 + if method != 'infinite_medium': + settings.inactive = 100 + settings.particles = 2000 + settings.output = {'summary': True, 'tallies': False} + if nparticles is not None: + settings.particles = nparticles + if temperature_settings is not None: + settings.temperature = temperature_settings + + if user_settings is not None: # The surrogate-geometry methods construct their own sources - if method != "material_wise" and len(settings.source) > 0: + if method != "material_wise" and len(user_settings.source) > 0: warnings.warn( 'The sources defined in "settings" are ignored by the ' f'"{method}" MGXS generation method, which constructs ' 'its own sources.') + settings.update(user_settings) + + # The run mode is the one attribute that cannot be detected as + # user-populated (a fresh Settings object defaults it to + # 'eigenvalue'), so it is owned by the generation method: + # "material_wise" always takes it from the model, while the + # surrogate-geometry methods always run in fixed source mode. + if method == 'material_wise': + settings.run_mode = self.settings.run_mode + else: + settings.run_mode = 'fixed source' # A weight windows file on the generation settings is loaded and # applied during the "material_wise" method's continuous energy diff --git a/openmc/settings.py b/openmc/settings.py index 1ecd483c12..81ade60630 100644 --- a/openmc/settings.py +++ b/openmc/settings.py @@ -1,4 +1,5 @@ from collections.abc import Iterable, Mapping, MutableSequence, Sequence +import copy from enum import Enum import itertools from math import ceil @@ -501,11 +502,6 @@ class Settings: self._random_ray = {} - # MGXS generation method recorded by Model.mgxs_generation_settings() - # and read back by Model.convert_to_multigroup; provenance only, not - # written to XML - self._mgxs_generation_method = None - for key, value in kwargs.items(): setattr(self, key, value) @@ -519,6 +515,51 @@ class Settings: warnings.warn(msg, stacklevel=2) super().__setattr__(name, value) + def update(self, other: 'Settings'): + """Update this object with all populated attributes of another instance. + + Every attribute of `other` that has been populated -- i.e., that is + not None and not an empty collection -- is copied to this object, + overwriting any existing value. Attributes that were never set on + `other` leave the corresponding attribute of this object untouched. + This allows a sparsely-populated Settings object to be applied as a + set of overrides on top of fully-populated defaults. + + Note that :attr:`run_mode` always carries a value, so an explicitly + assigned 'eigenvalue' run mode cannot be distinguished from the + default; the run mode is therefore only copied from `other` when it + differs from 'eigenvalue'. + + .. versionadded:: 0.15.4 + + Parameters + ---------- + other : openmc.Settings + Settings object whose populated attributes are applied to this + object. + + """ + cv.check_type('other', other, Settings) + for name, value in vars(other).items(): + # run_mode defaults to 'eigenvalue' rather than to an "unset" + # state, so only a non-default value is detectable (see note in + # the docstring). + if name == '_run_mode': + if value is not RunMode.EIGENVALUE: + self._run_mode = value + continue + + # None or an empty collection means the attribute was never set + # on `other` -- the same convention to_xml_element() relies on + # to decide which elements to export. + if value is None or (hasattr(value, '__len__') and len(value) == 0): + continue + + # Values on `other` were already validated by its property + # setters; deepcopy so later mutations of `other` cannot leak + # into this object. + setattr(self, name, copy.deepcopy(value)) + @property def run_mode(self) -> str: return self._run_mode.value diff --git a/tests/regression_tests/random_ray_auto_convert/test.py b/tests/regression_tests/random_ray_auto_convert/test.py index a1570c64be..c9df2d6c72 100644 --- a/tests/regression_tests/random_ray_auto_convert/test.py +++ b/tests/regression_tests/random_ray_auto_convert/test.py @@ -26,10 +26,9 @@ def test_random_ray_auto_convert(method): model = pwr_pin_cell() # Convert to a multi-group model - mgxs_settings = model.mgxs_generation_settings(method) - mgxs_settings.particles = 100 model.convert_to_multigroup( - method=method, groups='CASMO-2', settings=mgxs_settings, + method=method, groups='CASMO-2', + settings=openmc.Settings(particles=100), overwrite_mgxs_library=False, mgxs_path="mgxs.h5" ) diff --git a/tests/regression_tests/random_ray_auto_convert_bootstrap/test.py b/tests/regression_tests/random_ray_auto_convert_bootstrap/test.py index 6e43d86592..89b734def3 100644 --- a/tests/regression_tests/random_ray_auto_convert_bootstrap/test.py +++ b/tests/regression_tests/random_ray_auto_convert_bootstrap/test.py @@ -72,18 +72,16 @@ def test_random_ray_auto_convert_bootstrap(): # Generate weight windows covering the whole problem with the # stochastic_slab method and a random ray FW-CADIS solve slab_model = copy.deepcopy(model) - slab_settings = slab_model.mgxs_generation_settings('stochastic_slab') - slab_settings.particles = 50 slab_model.convert_to_multigroup( - groups=GROUPS, settings=slab_settings, + method='stochastic_slab', groups=GROUPS, + settings=openmc.Settings(particles=50), overwrite_mgxs_library=True, mgxs_path='mgxs.h5') generate_weight_windows(slab_model, mesh) # Bootstrap the material-wise MGXS generation with those weight windows, # then regenerate the weight windows from the higher-fidelity library boot_model = copy.deepcopy(model) - boot_settings = boot_model.mgxs_generation_settings() - boot_settings.particles = 1 + boot_settings = openmc.Settings(particles=1) boot_settings.weight_windows_file = Path('weight_windows.h5').resolve() boot_model.convert_to_multigroup( groups=GROUPS, settings=boot_settings, diff --git a/tests/regression_tests/random_ray_auto_convert_kappa_fission/test.py b/tests/regression_tests/random_ray_auto_convert_kappa_fission/test.py index 3f2468532e..b3b8e84c95 100644 --- a/tests/regression_tests/random_ray_auto_convert_kappa_fission/test.py +++ b/tests/regression_tests/random_ray_auto_convert_kappa_fission/test.py @@ -26,10 +26,9 @@ def test_random_ray_auto_convert(method): model = pwr_pin_cell() # Convert to a multi-group model - mgxs_settings = model.mgxs_generation_settings(method) - mgxs_settings.particles = 100 model.convert_to_multigroup( - method=method, groups='CASMO-2', settings=mgxs_settings, + method=method, groups='CASMO-2', + settings=openmc.Settings(particles=100), overwrite_mgxs_library=False, mgxs_path="mgxs.h5" ) diff --git a/tests/regression_tests/random_ray_auto_convert_source_energy/test.py b/tests/regression_tests/random_ray_auto_convert_source_energy/test.py index bfea6ae9f9..8dd95e73ee 100644 --- a/tests/regression_tests/random_ray_auto_convert_source_energy/test.py +++ b/tests/regression_tests/random_ray_auto_convert_source_energy/test.py @@ -37,10 +37,9 @@ def test_random_ray_auto_convert_source_energy(method, source_type): source_energy = openmc.stats.delta_function(1.0e4) # Convert to a multi-group model - mgxs_settings = model.mgxs_generation_settings(method) - mgxs_settings.particles = 100 model.convert_to_multigroup( - method=method, groups='CASMO-8', settings=mgxs_settings, + method=method, groups='CASMO-8', + settings=openmc.Settings(particles=100), overwrite_mgxs_library=False, mgxs_path="mgxs.h5", source_energy=source_energy ) diff --git a/tests/regression_tests/random_ray_auto_convert_temperature/test.py b/tests/regression_tests/random_ray_auto_convert_temperature/test.py index 2cd7133e5c..e6047613be 100644 --- a/tests/regression_tests/random_ray_auto_convert_temperature/test.py +++ b/tests/regression_tests/random_ray_auto_convert_temperature/test.py @@ -33,11 +33,9 @@ def test_random_ray_auto_convert(method): } # Convert to a multi-group model - mgxs_settings = model.mgxs_generation_settings(method) - mgxs_settings.particles = 100 - mgxs_settings.temperature = temp_settings model.convert_to_multigroup( - method=method, groups='CASMO-2', settings=mgxs_settings, + method=method, groups='CASMO-2', + settings=openmc.Settings(particles=100, temperature=temp_settings), overwrite_mgxs_library=False, mgxs_path="mgxs.h5", temperatures=[294.0, 394.0] ) diff --git a/tests/regression_tests/random_ray_diagonal_stabilization/test.py b/tests/regression_tests/random_ray_diagonal_stabilization/test.py index 08d8a50847..76d2c70bd3 100644 --- a/tests/regression_tests/random_ray_diagonal_stabilization/test.py +++ b/tests/regression_tests/random_ray_diagonal_stabilization/test.py @@ -1,5 +1,6 @@ import os +import openmc from openmc.examples import pwr_pin_cell from openmc import RegularMesh @@ -22,10 +23,9 @@ def test_random_ray_diagonal_stabilization(): # and transport correction enabled. This will generate # MGXS data with some negatives on the diagonal, in order # to trigger diagonal correction. - mgxs_settings = model.mgxs_generation_settings('material_wise') - mgxs_settings.particles = 13 model.convert_to_multigroup( - method='material_wise', groups='CASMO-70', settings=mgxs_settings, + method='material_wise', groups='CASMO-70', + settings=openmc.Settings(particles=13), overwrite_mgxs_library=True, mgxs_path="mgxs.h5", correction='P0' ) diff --git a/tests/unit_tests/dagmc/test_convert_to_multigroup.py b/tests/unit_tests/dagmc/test_convert_to_multigroup.py index 4d6243c7cc..d66ebae395 100644 --- a/tests/unit_tests/dagmc/test_convert_to_multigroup.py +++ b/tests/unit_tests/dagmc/test_convert_to_multigroup.py @@ -42,12 +42,10 @@ def test_convert_to_multigroup_without_particles_batches(run_in_tmpdir): # This should work without requiring particles/batches to be set # convert_to_multigroup handles initialization internally using non-transport mode - mgxs_settings = model.mgxs_generation_settings('material_wise') - mgxs_settings.particles = 10 model.convert_to_multigroup( method='material_wise', groups='CASMO-2', - settings=mgxs_settings, + settings=openmc.Settings(particles=10), overwrite_mgxs_library=True ) diff --git a/tests/unit_tests/test_model.py b/tests/unit_tests/test_model.py index abc6a29ffd..53a5b25251 100644 --- a/tests/unit_tests/test_model.py +++ b/tests/unit_tests/test_model.py @@ -1104,72 +1104,44 @@ def _capture_generation_settings(monkeypatch, model, **kwargs): return captured['settings'] -def test_mgxs_generation_settings(): - model = _steel_water_model() - model.settings.run_mode = 'fixed source' - model.settings.photon_transport = True - model.settings.particles = 50 # tuned for the final multigroup solve - model.settings.temperature = {'method': 'interpolation'} - - # material_wise: the model's own settings plus the generation defaults - s = model.mgxs_generation_settings('material_wise') - assert s.batches == 200 - assert s.inactive == 100 - assert s.particles == 2000 - assert s.output == {'summary': True, 'tallies': False} - assert s.run_mode == 'fixed source' - assert s.photon_transport is True - assert s.temperature == {'method': 'interpolation'} - - # The returned object is a copy: modifying it leaves the model untouched - s.particles = 100_000 - assert model.settings.particles == 50 - - # Surrogate methods: fresh settings that only inherit the temperature - for method, batches in (('stochastic_slab', 200), ('infinite_medium', 100)): - s = model.mgxs_generation_settings(method) - assert s.batches == batches - assert s.particles == 2000 - assert s.run_mode == 'fixed source' - assert s.create_fission_neutrons is False - assert s.temperature == {'method': 'interpolation'} - assert s.photon_transport is None - - with pytest.raises(ValueError): - model.mgxs_generation_settings('not_a_method') - - def test_convert_to_multigroup_settings_material_wise(run_in_tmpdir, monkeypatch): model = _steel_water_model() model.settings.run_mode = 'fixed source' model.settings.source = openmc.IndependentSource(space=openmc.stats.Point()) model.settings.photon_transport = True + model.settings.batches = 1200 # tuned for the final multigroup solve - user = model.mgxs_generation_settings('material_wise') - user.particles = 12345 - user.seed = 7 + user = openmc.Settings(particles=12345, seed=7) gen = _capture_generation_settings( monkeypatch, model, method='material_wise', settings=user) - # The provided settings are used verbatim... + # User-populated attributes override the generation defaults... assert gen.particles == 12345 assert gen.seed == 7 + # ...the generation defaults override the model's own settings... assert gen.batches == 200 assert gen.inactive == 100 assert gen.output == {'summary': True, 'tallies': False} - # ...including the model settings baked in by mgxs_generation_settings() + # ...and everything else is inherited from the model assert gen.run_mode == 'fixed source' assert gen.photon_transport is True assert len(gen.source) == 1 + # The caller's object is never mutated + assert user.batches is None + + # The run mode is owned by the generation method: material_wise always + # takes it from the model, even when set on the provided settings + model.settings.run_mode = 'eigenvalue' + gen = _capture_generation_settings( + monkeypatch, model, method='material_wise', + settings=openmc.Settings(run_mode='fixed source')) + assert gen.run_mode == 'eigenvalue' def test_convert_to_multigroup_settings_stochastic_slab(run_in_tmpdir, monkeypatch): model = _steel_water_model() - user = model.mgxs_generation_settings('stochastic_slab') - user.particles = 999 - user.batches = 50 - user.max_history_splits = 42 + user = openmc.Settings(particles=999, batches=50, max_history_splits=42) user.source = openmc.IndependentSource(space=openmc.stats.Point()) with pytest.warns(UserWarning, match='constructs its own'): @@ -1193,8 +1165,7 @@ def test_convert_to_multigroup_settings_weight_windows(run_in_tmpdir, monkeypatc model = _steel_water_model() ww_path = Path('ww.h5').resolve() - user = model.mgxs_generation_settings('material_wise') - user.weight_windows_file = ww_path + user = openmc.Settings(weight_windows_file=ww_path) gen = _capture_generation_settings( monkeypatch, model, method='material_wise', settings=user) @@ -1207,8 +1178,6 @@ def test_convert_to_multigroup_settings_weight_windows(run_in_tmpdir, monkeypatc assert user.weight_windows_on is None # The surrogate-geometry methods ignore the file with a warning - user = model.mgxs_generation_settings('stochastic_slab') - user.weight_windows_file = ww_path with pytest.warns(UserWarning, match='material_wise'): gen = _capture_generation_settings( monkeypatch, model, method='stochastic_slab', settings=user) @@ -1216,46 +1185,25 @@ def test_convert_to_multigroup_settings_weight_windows(run_in_tmpdir, monkeypatc assert user.weight_windows_file == ww_path -def test_convert_to_multigroup_settings_method_recorded(run_in_tmpdir, - monkeypatch): - model = _steel_water_model() - - # Settings record the method they were generated for, so a non-default - # method only needs to be given to mgxs_generation_settings() - user = model.mgxs_generation_settings('stochastic_slab') - gen = _capture_generation_settings(monkeypatch, model, settings=user) - assert gen.run_mode == 'fixed source' - assert gen.create_fission_neutrons is False - # The stochastic slab generation constructs its own source, proving the - # slab method was dispatched - assert len(gen.source) > 0 - - # An explicit method that conflicts with the settings is rejected - with pytest.raises(ValueError, match='stochastic_slab'): - model.convert_to_multigroup(method='material_wise', settings=user) - - def test_convert_to_multigroup_settings_validation(run_in_tmpdir): model = _steel_water_model() with pytest.raises(TypeError): model.convert_to_multigroup(settings={'particles': 100}) - # A hand-built settings object missing attributes required for any - # transport run is rejected with a pointer to the defaults - with pytest.raises(ValueError, match='mgxs_generation_settings'): - model.convert_to_multigroup(settings=openmc.Settings(particles=5000)) + with pytest.raises(ValueError): + model.convert_to_multigroup(method='not_a_method') # The deprecated arguments cannot be combined with settings - settings = model.mgxs_generation_settings() with pytest.raises(ValueError, match='deprecated'), \ pytest.warns(FutureWarning): - model.convert_to_multigroup(nparticles=1000, settings=settings) + model.convert_to_multigroup(nparticles=1000, + settings=openmc.Settings()) with pytest.raises(ValueError, match='deprecated'), \ pytest.warns(FutureWarning): model.convert_to_multigroup( temperature_settings={'method': 'interpolation'}, - settings=settings) + settings=openmc.Settings()) def test_convert_to_multigroup_deprecated_args(run_in_tmpdir, monkeypatch): diff --git a/tests/unit_tests/test_settings.py b/tests/unit_tests/test_settings.py index bdb3ea8fe9..1bbde93c35 100644 --- a/tests/unit_tests/test_settings.py +++ b/tests/unit_tests/test_settings.py @@ -192,6 +192,35 @@ def test_export_to_xml(run_in_tmpdir): assert s.free_gas_threshold == 800.0 +def test_update(): + base = openmc.Settings(particles=100, batches=10, + output={'tallies': False}) + other = openmc.Settings(particles=500) + other.source = openmc.IndependentSource(space=openmc.stats.Point()) + + base.update(other) + + # Populated attributes of `other` overwrite those of `base`... + assert base.particles == 500 + assert len(base.source) == 1 + # ...while unset attributes leave `base` untouched + assert base.batches == 10 + assert base.output == {'tallies': False} + # Copied values are independent of `other` + assert base.source[0] is not other.source[0] + + # run_mode always carries a value, so only a non-default ('eigenvalue') + # run mode is detectable and copied + base.run_mode = 'fixed source' + base.update(openmc.Settings()) + assert base.run_mode == 'fixed source' + base.update(openmc.Settings(run_mode='volume')) + assert base.run_mode == 'volume' + + with pytest.raises(TypeError): + base.update({'particles': 5}) + + def test_properties_file_load(tmp_path, mpi_intracomm): model = openmc.examples.pwr_assembly() diff --git a/tests/unit_tests/weightwindows/test_ww_gen.py b/tests/unit_tests/weightwindows/test_ww_gen.py index fbb316860b..8209bb8505 100644 --- a/tests/unit_tests/weightwindows/test_ww_gen.py +++ b/tests/unit_tests/weightwindows/test_ww_gen.py @@ -363,12 +363,10 @@ def test_ww_generation_with_dagmc(run_in_tmpdir): rr_model = copy.deepcopy(model) rr_model.settings.inactive = 3 - mgxs_settings = rr_model.mgxs_generation_settings("stochastic_slab") - mgxs_settings.particles = 10 rr_model.convert_to_multigroup( method="stochastic_slab", overwrite_mgxs_library=True, - settings=mgxs_settings, + settings=openmc.Settings(particles=10), groups="CASMO-2" )