diff --git a/openmc/model/model.py b/openmc/model/model.py index ae09361838..aa00d40b4f 100644 --- a/openmc/model/model.py +++ b/openmc/model/model.py @@ -193,6 +193,8 @@ class Model: def from_xml(cls, geometry='geometry.xml', materials='materials.xml', settings='settings.xml'): """Create model from existing XML files + When initializing this way, the user must manually load plots, tallies, + the chain_file and fission_q attributes. Parameters ---------- @@ -428,7 +430,9 @@ class Model: Parameters ---------- **kwargs - Keyword arguments passed to :func:`openmc.run` + Keyword arguments passed to :func:`openmc.run`. Note that these are + ignored if running via the C-API. Instead the parameters should be + set via the Settings object. Returns ------- diff --git a/tests/unit_tests/conftest.py b/tests/unit_tests/conftest.py index 51d1b19a33..a1c4a24dab 100644 --- a/tests/unit_tests/conftest.py +++ b/tests/unit_tests/conftest.py @@ -1,9 +1,129 @@ +from math import pi import openmc import pytest from tests.regression_tests import config +@pytest.fixture(scope='module') +def pin_model_attributes(): + uo2 = openmc.Material(name='UO2') + uo2.set_density('g/cm3', 10.29769) + uo2.add_element('U', 1., enrichment=2.4) + uo2.add_element('O', 2.) + + zirc = openmc.Material(name='Zirc') + zirc.set_density('g/cm3', 6.55) + zirc.add_element('Zr', 1.) + + borated_water = openmc.Material(name='Borated water') + borated_water.set_density('g/cm3', 0.740582) + borated_water.add_element('B', 4.0e-5) + borated_water.add_element('H', 5.0e-2) + borated_water.add_element('O', 2.4e-2) + borated_water.add_s_alpha_beta('c_H_in_H2O') + + mats = openmc.Materials([uo2, zirc, borated_water]) + + pitch = 1.25984 + fuel_or = openmc.ZCylinder(r=0.39218, name='Fuel OR') + clad_or = openmc.ZCylinder(r=0.45720, name='Clad OR') + box = openmc.model.rectangular_prism(pitch, pitch, boundary_type='reflective') + + # Define cells + fuel = openmc.Cell(name='fuel', fill=uo2, region=-fuel_or) + clad = openmc.Cell(fill=zirc, region=+fuel_or & -clad_or) + water = openmc.Cell(fill=borated_water, region=+clad_or & box) + + # Define overall geometry + geom = openmc.Geometry([fuel, clad, water]) + uo2.volume = pi * fuel_or.r**2 + + settings = openmc.Settings() + settings.batches = 100 + settings.inactive = 10 + settings.particles = 1000 + + # Create an initial uniform spatial source distribution over fissionable zones + bounds = [-0.62992, -0.62992, -1, 0.62992, 0.62992, 1] + uniform_dist = openmc.stats.Box(bounds[:3], bounds[3:], only_fissionable=True) + settings.source = openmc.source.Source(space=uniform_dist) + + entropy_mesh = openmc.RegularMesh() + entropy_mesh.lower_left = [-0.39218, -0.39218, -1.e50] + entropy_mesh.upper_right = [0.39218, 0.39218, 1.e50] + entropy_mesh.dimension = [10, 10, 1] + settings.entropy_mesh = entropy_mesh + + tals = openmc.Tallies() + tal = openmc.Tally(name='test') + tal.scores = ['flux'] + tals.append(tal) + + plot = openmc.Plot() + plot.origin = (0., 0., 0.) + plot.width = (pitch, pitch) + plot.pixels = (300, 300) + plot.color_by = 'material' + plots = openmc.Plots((plot,)) + + chain = './chain_simple.xml' + fission_q = {'U235': 200e6} + + chain_file_xml = """ + + + + + + + + + + + + + + + + + + + + + 2.53000e-02 + + Gd157 Gd156 I135 Xe135 Xe136 Cs135 + 1.093250e-04 2.087260e-04 2.780820e-02 6.759540e-03 2.392300e-02 4.356330e-05 + + + + + + + 2.53000e-02 + + Gd157 Gd156 I135 Xe135 Xe136 Cs135 + 6.142710e-5 1.483250e-04 0.0292737 0.002566345 0.0219242 4.9097e-6 + + + + + + + 2.53000e-02 + + Gd157 Gd156 I135 Xe135 Xe136 Cs135 + 4.141120e-04 7.605360e-04 0.0135457 0.00026864 0.0024432 3.7100E-07 + + + + +""" + + return (mats, geom, settings, tals, plots, chain, fission_q, + chain_file_xml) + @pytest.fixture(scope='module') def mpi_intracomm(): if config['mpi']: diff --git a/tests/unit_tests/test_model.py b/tests/unit_tests/test_model.py index 4b658d5ab0..b616856b01 100644 --- a/tests/unit_tests/test_model.py +++ b/tests/unit_tests/test_model.py @@ -1,18 +1,162 @@ +from typing import Type import pytest +from pathlib import Path import openmc import openmc.lib +def test_init(run_in_tmpdir, pin_model_attributes): + mats, geom, settings, tals, plots, chain, fission_q, _ = \ + pin_model_attributes + + openmc.reset_auto_ids() + # Check blank initialization of a model + test_model = openmc.Model() + assert test_model.geometry.root_universe is None + assert len(test_model.materials) == 0 + ref_settings = openmc.Settings() + assert sorted(test_model.settings.__dict__.keys()) == \ + sorted(ref_settings.__dict__.keys()) + for ref_k, ref_v in ref_settings.__dict__.items(): + assert test_model.settings.__dict__[ref_k] == ref_v + assert len(test_model.tallies) == 0 + assert len(test_model.plots) == 0 + assert test_model.chain_file is None + assert test_model.fission_q is None + assert test_model._materials_by_id == {} + assert test_model._materials_by_name == {} + assert test_model._cells_by_id == {} + assert test_model._cells_by_name == {} + assert test_model.C_init is False + + # Now check proper init of an actual model. Assume no interference between + # parameters and so we can apply them all at once instead of testing one + # parameter initialization at a time + test_model = openmc.Model(geom, mats, settings, tals, plots, chain, + fission_q) + assert test_model.geometry is geom + assert test_model.materials is mats + assert test_model.settings is settings + assert test_model.tallies is tals + assert test_model.plots is plots + assert test_model.chain_file == Path(chain).resolve() + assert test_model.fission_q is fission_q + assert test_model._materials_by_id == {1: mats[0], 2: mats[1], 3: mats[2]} + assert test_model._materials_by_name == {'UO2': mats[0], 'Zirc': mats[1], + 'Borated water': mats[2]} + assert test_model._cells_by_id == {1: geom.root_universe.cells[1], + 2: geom.root_universe.cells[2], + 3: geom.root_universe.cells[3]} + # No cell name for 2 and 3, so we expect a blank name to be assigned to + # cell 3 due to overwriting + assert test_model._cells_by_name == { + 'fuel': geom.root_universe.cells[1], '': geom.root_universe.cells[3]} + assert test_model.C_init is False + + # Finally test the parameter type checking by passing bad types and + # obtaining the right exception types + def_params = [geom, mats, settings, tals, plots, chain, fission_q] + for i in range(len(def_params)): + args = def_params.copy() + # Try an integer, as that is a bad type for all arguments + args[i] = i + with pytest.raises(TypeError): + test_model = openmc.Model(*args) + + +def test_from_xml(run_in_tmpdir, pin_model_attributes): + mats, geom, settings, tals, plots, _, _, _ = pin_model_attributes + + # This test will write the individual files to xml and then init that way + # and run the same sort of test as in test_init + mats.export_to_xml() + geom.export_to_xml() + settings.export_to_xml() + tals.export_to_xml() + plots.export_to_xml() + + # This from_xml method cannot load chain and fission_q + test_model = openmc.Model.from_xml() + assert test_model.geometry.root_universe.cells.keys() == \ + geom.root_universe.cells.keys() + assert [c.fill.name for c in test_model.geometry.root_universe.cells.values()] == \ + [c.fill.name for c in geom.root_universe.cells.values()] + assert [mat.name for mat in test_model.materials] == [mat.name for mat in mats] + # We will assume the attributes of settings that are custom objects are + # OK if the others are so we dotn need to implement explicit comparisons + no_test = ['_source', '_entropy_mesh'] + assert sorted(k for k in test_model.settings.__dict__.keys() if k not in no_test) == \ + sorted(k for k in settings.__dict__.keys() if k not in no_test) + keys = sorted(k for k in settings.__dict__.keys() if k not in no_test) + for ref_k in keys: + assert test_model.settings.__dict__[ref_k] == settings.__dict__[ref_k] + assert len(test_model.tallies) == 0 + assert len(test_model.plots) == 0 + assert test_model.chain_file is None + assert test_model.fission_q is None + assert test_model._materials_by_id == \ + {1: test_model.materials[0], 2: test_model.materials[1], + 3: test_model.materials[2]} + assert test_model._materials_by_name == { + 'UO2': test_model.materials[0], 'Zirc': test_model.materials[1], + 'Borated water': test_model.materials[2]} + assert test_model._cells_by_id == {\ + 1: test_model.geometry.root_universe.cells[1], + 2: test_model.geometry.root_universe.cells[2], + 3: test_model.geometry.root_universe.cells[3]} + # No cell name for 2 and 3, so we expect a blank name to be assigned to + # cell 3 due to overwriting + assert test_model._cells_by_name == { + 'fuel': test_model.geometry.root_universe.cells[1], + '': test_model.geometry.root_universe.cells[3]} + assert test_model.C_init is False + + +def test_init_clear_C_api(run_in_tmpdir, pin_model_attributes, mpi_intracomm): + # We are going to init and then make sure data is loaded + mats, geom, settings, tals, plots, _, _, _ = pin_model_attributes + test_model = openmc.Model(geom, mats, settings, tals, plots) + # Set the depletion module to use the test intracomm as the depletion mods + # intracomm is the one that init uses. We will not perturb system state + # outside of this test by re-setting the openmc.deplete.comm to what it was + # before when we exist + if mpi_intracomm is not None: + orig_comm = openmc.deplete.comm + openmc.deplete.comm = mpi_intracomm + test_model.init_C_api() + + # First check that the API is advertised as initialized + assert openmc.lib.LIB_INIT is True + assert test_model.C_init is True + # Now make sure it actually is initialized by making a call to the lib + c_mat = openmc.lib.find_material((0.6, 0., 0.)) + # This should be Borated water + assert c_mat.name == 'Borated water' + assert c_mat.id == 3 + + # Ok, now lets test that we can clear the data and check that it is cleared + test_model.clear_C_api() + + # First check that the API is advertised as initialized + assert openmc.lib.LIB_INIT is False + assert test_model.C_init is False + # Note we cant actually test that a sys call fails because we should get a + # seg fault + + # And before done, reset the deplete communicator + if mpi_intracomm is not None: + openmc.deplete.comm = orig_comm + + def test_import_properties(run_in_tmpdir, mpi_intracomm): """Test importing properties on the Model class """ # Create PWR pin cell model and write XML files openmc.reset_auto_ids() model = openmc.examples.pwr_pin_cell() - model.export_to_xml() + model.init_C_api() # Change fuel temperature and density and export properties - openmc.lib.init(intracomm=mpi_intracomm) cell = openmc.lib.cells[1] cell.set_temperature(600.0) cell.fill.set_density(5.0, 'g/cm3') @@ -32,3 +176,40 @@ def test_import_properties(run_in_tmpdir, mpi_intracomm): cell = model_with_properties.geometry.get_all_cells()[1] assert cell.temperature == [600.0] assert cell.fill.get_mass_density() == pytest.approx(5.0) + + +def test_run(run_in_tmpdir, pin_model_attributes, mpi_intracomm): + mats, geom, settings, tals, plots, _, _, _ = pin_model_attributes + test_model = openmc.Model(geom, mats, settings, tals, plots) + # Set the depletion module to use the test intracomm as the depletion mods + # intracomm is the one that init uses. We will not perturb system state + # outside of this test by re-setting the openmc.deplete.comm to what it was + # before when we exist + + # This case will run by getting the k-eff and tallies for command-line and + # C-API execution modes and ensuring they give the same result. + sp_path = test_model.run(output=False) + with openmc.StatePoint(sp_path) as sp: + cli_keff = sp.k_combined + cli_flux = sp.get_tally(id=1).get_values()[0, 0, 0] + + if mpi_intracomm is not None: + orig_comm = openmc.deplete.comm + openmc.deplete.comm = mpi_intracomm + test_model.settings.verbosity = 1 + test_model.init_C_api() + sp_path = test_model.run() + with openmc.StatePoint(sp_path) as sp: + C_keff = sp.k_combined + C_flux = sp.get_tally(id=1).get_values()[0, 0, 0] + test_model.clear_C_api() + + # and lets compare results + assert (C_keff - cli_keff) < 1e-15 + assert (C_flux - cli_flux) < 1e-15 + + # And before done, reset the deplete communicator + if mpi_intracomm is not None: + openmc.deplete.comm = orig_comm + +