from pathlib import Path import pytest import numpy as np import openmc from openmc.stats import Discrete, Point from openmc.utility_funcs import change_directory from tests.testing_harness import HashedPyAPITestHarness def build_model(shared_secondary): openmc.reset_auto_ids() model = openmc.Model() # materials (M4 steel alloy) m4 = openmc.Material() m4.set_density('g/cc', 2.3) m4.add_nuclide('H1', 0.168018676) m4.add_nuclide("O16", 0.561814465) m4.add_nuclide("O17", 0.00021401) m4.add_nuclide("Na23", 0.021365) m4.add_nuclide("Al27", 0.021343) m4.add_nuclide("Si28", 0.187439342) m4.add_nuclide("Si29", 0.009517714) m4.add_nuclide("Si30", 0.006273944) m4.add_nuclide("Ca40", 0.018026179) m4.add_nuclide("Ca42", 0.00012031) m4.add_nuclide("Ca44", 0.000387892) m4.add_nuclide("Fe54", 0.000248179) m4.add_nuclide("Fe56", 0.003895875) s0 = openmc.Sphere(r=240) s1 = openmc.Sphere(r=250, boundary_type='vacuum') c0 = openmc.Cell(fill=m4, region=-s0) c1 = openmc.Cell(region=+s0 & -s1) model.geometry = openmc.Geometry([c0, c1]) # settings settings = model.settings settings.run_mode = 'fixed source' settings.particles = 500 settings.batches = 2 settings.max_history_splits = 200 settings.photon_transport = True settings.shared_secondary_bank = shared_secondary settings.weight_window_checkpoints = {'surface': True, 'collision': True} space = Point((0.001, 0.001, 0.001)) energy = Discrete([14E6], [1.0]) settings.source = openmc.IndependentSource(space=space, energy=energy) # tally mesh = openmc.RegularMesh() mesh.lower_left = (-240, -240, -240) mesh.upper_right = (240, 240, 240) mesh.dimension = (5, 10, 15) mesh_filter = openmc.MeshFilter(mesh) e_bnds = [0.0, 0.5, 2E7] energy_filter = openmc.EnergyFilter(e_bnds) particle_filter = openmc.ParticleFilter(['neutron', 'photon']) tally = openmc.Tally() tally.filters = [mesh_filter, energy_filter, particle_filter] tally.scores = ['flux'] model.tallies.append(tally) # weight windows # load pre-generated weight windows from parent directory parent_dir = Path(__file__).parent ww_n_lower_bnds = np.loadtxt(parent_dir / 'ww_n.txt') ww_p_lower_bnds = np.loadtxt(parent_dir / 'ww_p.txt') # create a mesh matching the one used # to generate the weight windows ww_mesh = openmc.RegularMesh() ww_mesh.lower_left = (-240, -240, -240) ww_mesh.upper_right = (240, 240, 240) ww_mesh.dimension = (5, 6, 7) ww_n = openmc.WeightWindows(ww_mesh, ww_n_lower_bnds, None, 10.0, e_bnds, 'neutron', max_lower_bound_ratio=1.5) ww_p = openmc.WeightWindows(ww_mesh, ww_p_lower_bnds, None, 10.0, e_bnds, 'photon', max_lower_bound_ratio=1.5) model.settings.weight_windows = [ww_n, ww_p] return model @pytest.mark.parametrize("shared_secondary,subdir", [ (False, "local"), (True, "shared"), ]) def test_weightwindows(shared_secondary, subdir): with change_directory(subdir): model = build_model(shared_secondary) test = HashedPyAPITestHarness('statepoint.2.h5', model) test.main() def test_zero_bound_windows_play_no_game(tmp_path): # A weight window lower bound of zero means no weight window information # exists there (MCNP wwinp files use zero to turn the game off in a cell), # so transport must proceed as if weight windows were disabled. Previously, # zero-bound windows demanded a split at every checkpoint (weight/0 -> # max_split), multiplying the particle population until terminated by the # split or weight cutoff limits. model = build_model(False) for ww in model.settings.weight_windows: ww.lower_ww_bounds = np.zeros_like(ww.lower_ww_bounds) ww.upper_ww_bounds = np.zeros_like(ww.upper_ww_bounds) sp_zero = model.run(cwd=tmp_path / 'zero_windows') model.settings.weight_windows_on = False sp_off = model.run(cwd=tmp_path / 'windows_off') with openmc.StatePoint(sp_zero) as sp: flux_zero = list(sp.tallies.values())[0].mean with openmc.StatePoint(sp_off) as sp: flux_off = list(sp.tallies.values())[0].mean np.testing.assert_allclose(flux_zero, flux_off, rtol=1e-12) def test_zero_and_negative_bounds_equivalent(tmp_path): # Zero and negative lower bounds both mean that no weight window # information exists in a cell (generators mark such cells with -1, and # MCNP wwinp files use zero), so they must produce identical transport. # Unlike the all-zero case above, here particles are born under valid # windows and encounter the no-information region in flight; previously a # zero lower bound in that situation demanded a split at every checkpoint # in the cell (weight/0 -> max_split), multiplying the particle population, # while -1 played no game. def run_with(bound_value, subdir): model = build_model(False) for ww in model.settings.weight_windows: lb = np.array(ww.lower_ww_bounds, copy=True) ub = np.array(ww.upper_ww_bounds, copy=True) lb[3:, :, :, :] = bound_value ub[3:, :, :, :] = bound_value ww.lower_ww_bounds = lb ww.upper_ww_bounds = ub return model.run(cwd=tmp_path / subdir) sp_zero = run_with(0.0, 'zero_region') sp_negative = run_with(-1.0, 'negative_region') with openmc.StatePoint(sp_zero) as sp: flux_zero = list(sp.tallies.values())[0].mean with openmc.StatePoint(sp_negative) as sp: flux_negative = list(sp.tallies.values())[0].mean np.testing.assert_allclose(flux_zero, flux_negative, rtol=1e-12) def test_wwinp_cylindrical(): ww = openmc.WeightWindowsList.from_wwinp('ww_n_cyl.txt')[0] mesh = ww.mesh assert mesh.dimension == (8, 8, 7) # make sure that the mesh grids are correct exp_r_grid = np.hstack((np.linspace(0.0, 3.02, 3, endpoint=False), np.linspace(3.02, 6.0001, 6))).flatten() exp_phi_grid = np.hstack((np.linspace(0.0, 0.25, 2, endpoint=False), np.linspace(0.25, 1.5707, 1, endpoint=False), np.linspace(1.5707, 3.1415, 2, endpoint=False), np.linspace(3.1415, 4.7124, 4))).flatten() exp_z_grid = np.hstack((np.linspace(0.0, 8.008, 4, endpoint=False), np.linspace(8.008, 14.002, 4))).flatten() assert isinstance(mesh, openmc.CylindricalMesh) np.testing.assert_equal(mesh.r_grid, exp_r_grid) np.testing.assert_equal(mesh.phi_grid, exp_phi_grid) np.testing.assert_equal(mesh.z_grid, exp_z_grid) np.testing.assert_equal(mesh.origin, (0, 0, -9.0001)) assert ww.lower_ww_bounds.flat[0] == 0.0 assert ww.lower_ww_bounds.flat[-1] == np.prod(mesh.dimension) - 1 def test_wwinp_spherical(): ww = openmc.WeightWindowsList.from_wwinp('ww_n_sph.txt')[0] mesh = ww.mesh assert mesh.dimension == (8, 7, 8) # make sure that the mesh grids are correct exp_r_grid = np.hstack((np.linspace(0.0, 3.02, 3, endpoint=False), np.linspace(3.02, 6.0001, 6))).flatten() exp_theta_grid = np.hstack((np.linspace(0.0, 0.25, 2, endpoint=False), np.linspace(0.25, 0.5, 1, endpoint=False), np.linspace(0.5, 0.75, 2, endpoint=False), np.linspace(0.75, 1.5707, 3))).flatten() exp_phi_grid = np.hstack((np.linspace(0.0, 0.25, 2, endpoint=False), np.linspace(0.25, 0.5, 1, endpoint=False), np.linspace(0.5, 1.5707, 2, endpoint=False), np.linspace(1.5707, 3.1415, 4))).flatten() assert isinstance(mesh, openmc.SphericalMesh) np.testing.assert_equal(mesh.r_grid, exp_r_grid) np.testing.assert_equal(mesh.theta_grid, exp_theta_grid) np.testing.assert_equal(mesh.phi_grid, exp_phi_grid) np.testing.assert_equal(mesh.origin, (0, 0, -9.0001)) assert ww.lower_ww_bounds.flat[0] == 0.0 assert ww.lower_ww_bounds.flat[-1] == np.prod(mesh.dimension) - 1