from itertools import permutations from pathlib import Path import numpy as np import openmc import openmc.lib import pytest @pytest.fixture def model(): openmc.reset_auto_ids() # create a simple spherical shell shielding model ### Materials ### water = openmc.Material() water.set_density('g/cc', 1.0) water.add_nuclide("H1", 2) water.add_nuclide("O16", 1) steel = openmc.Material() steel.set_density('g/cc', 8.0) steel.add_nuclide("Fe56", 1.0) air = openmc.Material() air.set_density('g/cc', 0.001205) air.add_nuclide("N14", 0.781557629247) air.add_nuclide("O16", 0.210668126508) boron = openmc.Material() boron.set_density('g/cc', 2.52) boron.add_nuclide("B10", 0.15856) boron.add_nuclide("B11", 0.64144) boron.add_nuclide("C0", 0.2) ### Geometry ### radii = [5.0, 10.0, 30.0, 31.0, 50.0] surfs = [openmc.Sphere(r=r) for r in radii] surfs[-1].boundary_type = 'vacuum' regions = openmc.model.subdivide(surfs) mats = [air, water, steel, boron, air] cells = [openmc.Cell(fill=m, region=r) for r, m in zip(regions, mats)] geometry = openmc.Geometry(cells) ### Settings ### settings = openmc.Settings( run_mode='fixed source', particles=100, batches=10, max_splits=10, survival_biasing=False ) # 10 keV neutron point source at the origin space = openmc.stats.Point() energy = openmc.stats.Discrete(x=[1e4], p=[1.0]) settings.source = openmc.IndependentSource(space=space, energy=energy) return openmc.Model(geometry=geometry, settings=settings) # create a tally used for weight window generation mesh = openmc.RegularMesh() mesh.lower_left = [-50.0] * 3 mesh.upper_right = [50.0] * 3 # use un-equal mesh widths in each dimension to more robustly check # use of tally data mesh.dimension = (19, 20, 21) mf = openmc.MeshFilter(mesh) ef = openmc.EnergyFilter([0.0, 1e7]) pf = openmc.ParticleFilter(['neutron', 'photon']) filters = [mf, ef, pf] test_cases = list(permutations(filters)) test_cases += list(permutations(filters[:-1])) test_cases += list(permutations(filters[::2])) def labels(params): out = [] for p in params: if isinstance(p, openmc.ParticleFilter): out.append('particle') elif isinstance(p, openmc.MeshFilter): out.append('mesh') elif isinstance(p, openmc.EnergyFilter): out.append('energy') return "filters:" + '-'.join(out) @pytest.mark.parametrize("filters", test_cases, ids=labels) def test_ww_gen(filters, model): tally = openmc.Tally() tally.filters = list(filters) tally.scores = ['flux'] model.tallies = openmc.Tallies([tally]) model.export_to_model_xml() ref_lower = None ref_upper = None # test weight window generation capability with openmc.lib.run_in_memory(): # retrieve the tally we created above in memory lib_tally = openmc.lib.tallies[tally.id] # create a new weight windows object ww = openmc.lib.WeightWindows.from_tally(lib_tally) # run particle transport openmc.lib.run() # capture analog data analog_mean = np.copy(lib_tally.mean) # update the weight window values using tally results ww.update_magic(lib_tally) assert any(ww.bounds[0] != -1) assert any(ww.bounds[1] != -1) # make sure that the weight window update doesn't change tally values np.testing.assert_equal(lib_tally.mean, analog_mean) # check against weight windows from the previous iteration # the order of filters should not change the weight window values if ref_lower is None: ref_lower = ww.bounds[0].copy() else: np.testing.assert_equal(ref_lower, ww.bounds[0]) if ref_upper is None: ref_upper = ww.bounds[1].copy() else: np.testing.assert_equal(ref_upper, ww.bounds[1]) # turn on weight windows for the subsequent run openmc.lib.settings.weight_windows_on = True openmc.lib.hard_reset() openmc.lib.run() ww_mean = np.copy(lib_tally.mean) # we expect that the application of weight windows will populate more tally # bins than the analog run for the meshes in this test model assert any(ww_mean != analog_mean) assert np.count_nonzero(ww_mean) > np.count_nonzero(analog_mean) def test_ww_import_export(run_in_tmpdir, model): # create a tally for weight windows mesh = openmc.RegularMesh() mesh.lower_left = [-50.0] * 3 mesh.upper_right = [50.0] * 3 # use un-equal mesh widths in each dimension to more robustly check # use of tally data mesh.dimension = (3, 4, 5) mf = openmc.MeshFilter(mesh) e_groups = np.logspace(0, 7, 8) ef = openmc.EnergyFilter(e_groups) pf = openmc.ParticleFilter(['neutron', 'photon']) tally = openmc.Tally() tally.filters = [mf, ef, pf] tally.scores = ['flux'] model.tallies = openmc.Tallies([tally]) # first, generate some weight windows model.export_to_model_xml() openmc.lib.init() tally = openmc.lib.tallies[tally.id] ww = openmc.lib.WeightWindows.from_tally(tally) openmc.lib.run() mean_before = np.array(tally.mean) ww.update_magic(tally) mean_after = np.array(tally.mean) assert (mean_before == mean_after).all() lb_before, up_before = ww.bounds # set some additional weight windows properties after transport ww.survival_ratio = 0.7 assert ww.survival_ratio == 0.7 ww.weight_cutoff = 1e-10 assert ww.weight_cutoff == 1e-10 ww.max_lower_bound_ratio = 200.0 assert ww.max_lower_bound_ratio == 200.0 ww.max_split = 26000 assert ww.max_split == 26000 openmc.lib.export_weight_windows() assert Path('weight_windows.h5').exists() openmc.lib.import_weight_windows('weight_windows.h5') imported_ww = openmc.lib.weight_windows[2] lb_after, up_after = imported_ww.bounds assert np.allclose(lb_before, lb_after) assert np.allclose(up_before, up_after) assert ww.survival_ratio == imported_ww.survival_ratio assert ww.max_lower_bound_ratio == imported_ww.max_lower_bound_ratio assert ww.weight_cutoff == imported_ww.weight_cutoff assert ww.max_split == imported_ww.max_split openmc.lib.finalize() def test_ww_gen_roundtrip(run_in_tmpdir, model): mesh = openmc.RegularMesh.from_domain(model.geometry.root_universe) energy_bounds = np.linspace(0.0, 1e8, 11) particle_type = 'neutron' wwg = openmc.WeightWindowGenerator(mesh, energy_bounds, particle_type) wwg.update_parameters = {'ratio' : 5.0, 'threshold': 0.8, 'value' : 'mean'} model.settings.weight_window_generators = wwg model.export_to_xml() model_in = openmc.Model.from_xml() assert len(model_in.settings.weight_window_generators) == 1 wwg_in = model.settings.weight_window_generators[0] # rountrip tests model_in = openmc.Model.from_xml() assert len(model_in.settings.weight_window_generators) == 1 wwg_in = model_in.settings.weight_window_generators[0] assert wwg_in.max_realizations == 1 assert wwg_in.on_the_fly == True assert wwg_in.update_interval == 1 assert wwg_in.update_parameters == wwg.update_parameters with pytest.raises(ValueError): wwg.method = '🦍🐒' with pytest.raises(TypeError): wwg.update_parameters = {'ratio' : 'one-to-one'} with pytest.raises(ValueError): wwg.max_realizations = -1 def test_python_hdf5_roundtrip(run_in_tmpdir, model): # add a tally to the model mesh = openmc.RegularMesh.from_domain(model.geometry) # some arbitrary energy groups e_groups = np.logspace(0, 6, 4) energy_filter = openmc.EnergyFilter(e_groups) bounds = np.arange(energy_filter.num_bins * np.prod(mesh.dimension)) wws = openmc.WeightWindows(mesh, bounds, bounds, energy_bounds=e_groups) model.settings.weight_windows = [wws] model.export_to_xml() # initialize and export wws to HDF5 openmc.lib.init() openmc.lib.export_weight_windows() openmc.lib.finalize() wws_hdf5 = openmc.hdf5_to_wws()[0] # ensure assert all(wws.energy_bounds == wws_hdf5.energy_bounds) assert wws.id == wws_hdf5.id assert wws.mesh.id == wws_hdf5.mesh.id np.testing.assert_array_equal(wws.lower_ww_bounds, wws_hdf5.lower_ww_bounds) np.testing.assert_array_equal(wws.upper_ww_bounds, wws_hdf5.upper_ww_bounds) def test_ww_bounds_set_in_memory(run_in_tmpdir, model): tally = openmc.Tally() tally.filters = filters tally.scores = ['flux'] model.tallies = [tally] bounds = np.arange(ef.num_bins * np.prod(mf.mesh.dimension)) model.export_to_xml() openmc.lib.init() lib_tally = openmc.lib.tallies[tally.id] wws = openmc.lib.WeightWindows.from_tally(lib_tally) wws.bounds = (bounds, bounds) openmc.lib.finalize()