diff --git a/include/openmc/capi.h b/include/openmc/capi.h index 679919dfa..0929a11f7 100644 --- a/include/openmc/capi.h +++ b/include/openmc/capi.h @@ -88,7 +88,6 @@ int openmc_material_set_densities( int openmc_material_set_id(int32_t index, int32_t id); int openmc_material_get_name(int32_t index, const char** name); int openmc_material_set_name(int32_t index, const char* name); -int openmc_material_set_temperature(int32_t index, double temperature); int openmc_material_set_volume(int32_t index, double volume); int openmc_material_filter_get_bins( int32_t index, const int32_t** bins, size_t* n); diff --git a/include/openmc/material.h b/include/openmc/material.h index b13cfe330..709d20573 100644 --- a/include/openmc/material.h +++ b/include/openmc/material.h @@ -158,10 +158,6 @@ public: double density_; //!< Total atom density in [atom/b-cm] double density_gpcc_; //!< Total atom density in [g/cm^3] double volume_ {-1.0}; //!< Volume in [cm^3] - - double temperature_ {-1}; //!< Default temperature for material - //!< A negative indicates no default - //!< temperature was specified. bool fissionable_ { false}; //!< Does this material contain fissionable nuclides bool depletable_ {false}; //!< Is the material depletable? @@ -199,6 +195,10 @@ private: // Private data members gsl::index index_; + //! \brief Default temperature for cells containing this material. + //! + //! A negative value indicates no default temperature was specified. + double temperature_ {-1}; }; //============================================================================== diff --git a/openmc/__init__.py b/openmc/__init__.py index 7366e8383..d118e6a05 100644 --- a/openmc/__init__.py +++ b/openmc/__init__.py @@ -30,7 +30,6 @@ from openmc.plotter import * from openmc.search import * from openmc.polynomial import * from . import examples -from openmc.mpi import DummyCommunicator, MPI, comm # Import a few names from the model module from openmc.model import rectangular_prism, hexagonal_prism, Model diff --git a/openmc/dummy_comm.py b/openmc/dummy_comm.py new file mode 100644 index 000000000..7ac9be6c3 --- /dev/null +++ b/openmc/dummy_comm.py @@ -0,0 +1,33 @@ +import sys + + +class DummyCommunicator: + rank = 0 + size = 1 + + def allgather(self, sendobj): + return [sendobj] + + def allreduce(self, sendobj, op=None): + return sendobj + + def barrier(self): + pass + + def bcast(self, obj, root=0): + return obj + + def gather(self, sendobj, root=0): + return [sendobj] + + def py2f(self): + return 0 + + def reduce(self, sendobj, op=None, root=0): + return sendobj + + def scatter(self, sendobj, root=0): + return sendobj[0] + + def Abort(self, exit_code_or_msg): + sys.exit(exit_code_or_msg) diff --git a/openmc/executor.py b/openmc/executor.py index ef1bc25dc..c2974722c 100644 --- a/openmc/executor.py +++ b/openmc/executor.py @@ -5,11 +5,12 @@ import subprocess import openmc -def process_CLI_arguments(volume=False, geometry_debug=False, particles=None, - plot=False, restart_file=None, threads=None, - tracks=False, event_based=None, openmc_exec='openmc', - mpi_args=None): - """Run an OpenMC simulation. +def _process_CLI_arguments(volume=False, geometry_debug=False, particles=None, + plot=False, restart_file=None, threads=None, + tracks=False, event_based=None, + openmc_exec='openmc', mpi_args=None): + """Converts user-readable flags in to command-line arguments to be run with + the OpenMC executable via subprocess. Parameters ---------- @@ -227,8 +228,8 @@ def calculate_volumes(threads=None, output=True, cwd='.', """ - args = process_CLI_arguments(volume=True, threads=threads, - openmc_exec=openmc_exec, mpi_args=mpi_args) + args = _process_CLI_arguments(volume=True, threads=threads, + openmc_exec=openmc_exec, mpi_args=mpi_args) _run(args, output, cwd) @@ -275,7 +276,7 @@ def run(particles=None, threads=None, geometry_debug=False, """ - args = process_CLI_arguments( + args = _process_CLI_arguments( volume=False, geometry_debug=geometry_debug, particles=particles, restart_file=restart_file, threads=threads, tracks=tracks, event_based=event_based, openmc_exec=openmc_exec, mpi_args=mpi_args) diff --git a/openmc/lib/core.py b/openmc/lib/core.py index 92cad3330..136e5a6d8 100644 --- a/openmc/lib/core.py +++ b/openmc/lib/core.py @@ -134,6 +134,7 @@ def export_properties(filename=None, output=True): """Export physical properties. .. versionchanged:: 0.13.0 + The *output* argument was added. Parameters ---------- @@ -233,6 +234,7 @@ def init(args=None, intracomm=None, output=True): """Initialize OpenMC .. versionchanged:: 0.13.0 + The *output* argument was added. Parameters ---------- @@ -363,7 +365,7 @@ def plot_geometry(output=True): """Plot geometry .. versionchanged:: 0.13.0 - The *output* argument was added. + The *output* argument was added. Parameters ---------- @@ -389,7 +391,7 @@ def run(output=True): """Run simulation .. versionchanged:: 0.13.0 - The *output* argument was added. + The *output* argument was added. Parameters ---------- @@ -529,6 +531,9 @@ def quiet_dll(output=True): """ + # This contextmanager is modified from that provided here: + # https://stackoverflow.com/a/14797594 + if output: yield else: diff --git a/openmc/lib/material.py b/openmc/lib/material.py index c1800cb46..fde197d3d 100644 --- a/openmc/lib/material.py +++ b/openmc/lib/material.py @@ -57,9 +57,6 @@ _dll.openmc_material_get_name.errcheck = _error_handler _dll.openmc_material_set_name.argtypes = [c_int32, c_char_p] _dll.openmc_material_set_name.restype = c_int _dll.openmc_material_set_name.errcheck = _error_handler -_dll.openmc_material_set_temperature.argtypes = [c_int32, c_double] -_dll.openmc_material_set_temperature.restype = c_int -_dll.openmc_material_set_temperature.errcheck = _error_handler _dll.openmc_material_set_volume.argtypes = [c_int32, c_double] _dll.openmc_material_set_volume.restype = c_int _dll.openmc_material_set_volume.errcheck = _error_handler @@ -168,10 +165,6 @@ class Material(_FortranObjectWithID): return None return volume.value - @temperature.setter - def temperature(self, temperature): - _dll.openmc_material_set_temperature(self._index, temperature) - @volume.setter def volume(self, volume): _dll.openmc_material_set_volume(self._index, volume) diff --git a/openmc/model/model.py b/openmc/model/model.py index ac54763c4..904c60746 100644 --- a/openmc/model/model.py +++ b/openmc/model/model.py @@ -11,6 +11,8 @@ from contextlib import contextmanager import h5py import openmc +from openmc.dummy_comm import DummyCommunicator +from openmc.executor import _process_CLI_arguments from openmc.checkvalue import check_type, check_value from openmc.exceptions import InvalidIDError @@ -39,6 +41,8 @@ class Model: materials appearing in the geometry. .. versionchanged:: 0.13.0 + The model information can now be loaded in to OpenMC directly via + openmc.lib Parameters ---------- @@ -65,10 +69,6 @@ class Model: Tallies information plots : openmc.Plots Plot information - intracomm : mpi4py.MPI.Intracomm or openmc.DummyCommunicator - MPI intracommunicator; this defaults to the mpi4py world communicator - from if present, or a DummyCommunicator otherwise. If an alternative - communicator is desired, this parameter should be modified accordingly. """ @@ -91,8 +91,6 @@ class Model: if plots is not None: self.plots = plots - self.intracomm = openmc.comm - # Store dictionaries to the materials and cells by ID and names if materials is None: mats = self.geometry.get_all_materials().values() @@ -137,10 +135,6 @@ class Model: def plots(self): return self._plots - @property - def intracomm(self): - return self._intracomm - @property def is_initialized(self): return openmc.lib.is_initialized @@ -185,16 +179,12 @@ class Model: for plot in plots: self._plots.append(plot) - @intracomm.setter - def intracomm(self, intracomm): - check_type('intracomm', intracomm, type(openmc.comm)) - self._intracomm = intracomm - @classmethod 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 and tallies. + When initializing this way, the user must manually load plots and + tallies. Parameters ---------- @@ -217,7 +207,7 @@ class Model: return cls(geometry, materials, settings) def init_lib(self, threads=None, geometry_debug=False, restart_file=None, - tracks=False, output=True, event_based=None): + tracks=False, output=True, event_based=None, intracomm=None): """Initializes the model in memory via the C API .. versionadded:: 0.13.0 @@ -241,6 +231,8 @@ class Model: event_based : None or bool, optional Turns on event-based parallelism if True. If None, the value in the Settings will be used. + intracomm : DummyCommnicator, mpi4py.MPI.Intracomm or None, optional + MPI intracommunicator """ # TODO: right now the only way to set most of the above parameters via @@ -249,7 +241,7 @@ class Model: # where it exists (here in init), but in the future the functionality # should be exposed so that it can be accessed via model.run(...) - args = openmc.process_CLI_arguments( + args = _process_CLI_arguments( volume=False, geometry_debug=geometry_debug, restart_file=restart_file, threads=threads, tracks=tracks, event_based=event_based) @@ -258,17 +250,18 @@ class Model: self.finalize_lib() - if self.intracomm.rank == 0: - self.export_to_xml() - self.intracomm.barrier() - - if isinstance(self.intracomm, openmc.DummyCommunicator): - # openmc.lib.init does not accept DummyCommunicator, and importing - # the DummyCommunicator class there is overkill. Filter it here - intracomm = None + # The Model object needs to be aware of the communicator so it can + # use it in certain cases, therefore lets store the communicator + if intracomm is not None: + self._intracomm = intracomm else: - intracomm = self.intracomm - openmc.lib.init(args=args, intracomm=intracomm, output=output) + self._intracomm = DummyCommunicator() + + if self._intracomm.rank == 0: + self.export_to_xml() + self._intracomm.barrier() + + openmc.lib.init(args=args, intracomm=self._intracomm, output=output) def finalize_lib(self): """Finalize simulation and free memory allocated for the C API @@ -285,8 +278,8 @@ class Model: """Deplete model using specified timesteps/power .. versionchanged:: 0.13.0 - The *final_step*, *operator_kwargs*, *directory*, and *output* - arguments were added. + The *final_step*, *operator_kwargs*, *directory*, and *output* + arguments were added. Parameters ---------- @@ -691,10 +684,14 @@ class Model: 'translation')) # The C API only allows setting density units of atom/b-cm and g/cm3 check_value('density_units', density_units, ('atom/b-cm', 'g/cm3')) - # The C API has no way to set cell volume so lets raise an exception + # The C API has no way to set cell volume or material temperature + # so lets raise exceptions as needed if obj_type == 'cell' and attrib_name == 'volume': raise NotImplementedError( 'Setting a Cell volume is not supported!') + if obj_type == 'material' and attrib_name == 'temperature': + raise NotImplementedError( + 'Setting a material temperature is not supported!') # And some items just dont make sense if obj_type == 'cell' and attrib_name == 'density': @@ -748,7 +745,7 @@ class Model: lib_obj = obj_by_id[id_] if attrib_name == 'density': lib_obj.set_density(value, density_units) - elif attrib_name == 'temperature' and obj_type == 'cell': + elif attrib_name == 'temperature': lib_obj.set_temperature(value) else: setattr(lib_obj, attrib_name, value) @@ -842,27 +839,6 @@ class Model: self._change_py_lib_attribs(names_or_ids, temperature, 'cell', 'temperature') - def update_material_temperatures(self, names_or_ids, temperature): - """Update the temperature of a set of materials to the given value - - .. note:: If applying this change to a name that is not unique, then - the change will be applied to all objects of that name. - - .. versionadded:: 0.13.0 - - Parameters - ---------- - names_or_ids : Iterable of str or int - The material names (if str) or id (if int) that are to be updated. - This parameter can include a mix of names and ids. - temperature : float - The temperature to apply in units of Kelvin - - """ - - self._change_py_lib_attribs(names_or_ids, temperature, 'material', - 'temperature') - def update_material_volumes(self, names_or_ids, volume): """Update the volume of a set of materials to the given value diff --git a/openmc/mpi.py b/openmc/mpi.py index b64a84bc6..cfc10c0d1 100644 --- a/openmc/mpi.py +++ b/openmc/mpi.py @@ -1,41 +1,8 @@ -import sys -from unittest.mock import Mock - - -class DummyCommunicator: - rank = 0 - size = 1 - - def allgather(self, sendobj): - return [sendobj] - - def allreduce(self, sendobj, op=None): - return sendobj - - def barrier(self): - pass - - def bcast(self, obj, root=0): - return obj - - def gather(self, sendobj, root=0): - return [sendobj] - - def py2f(self): - return 0 - - def reduce(self, sendobj, op=None, root=0): - return sendobj - - def scatter(self, sendobj, root=0): - return sendobj[0] - - def Abort(self, exit_code_or_msg): - sys.exit(exit_code_or_msg) - try: from mpi4py import MPI comm = MPI.COMM_WORLD except ImportError: + from unittest.mock import Mock MPI = Mock() + from openmc.dummy_comm import DummyCommunicator comm = DummyCommunicator() diff --git a/src/material.cpp b/src/material.cpp index fb4b2675e..ca29ce740 100644 --- a/src/material.cpp +++ b/src/material.cpp @@ -1480,18 +1480,6 @@ extern "C" int openmc_material_set_name(int32_t index, const char* name) return 0; } -extern "C" int openmc_material_set_temperature(int32_t index, double temperature) -{ - if (index >= 0 && index < model::materials.size()) { - auto& m {model::materials[index]}; - m->temperature_ = temperature; - return 0; - } else { - set_errmsg("Index in materials array is out of bounds."); - return OPENMC_E_OUT_OF_BOUNDS; - } -} - extern "C" int openmc_material_set_volume(int32_t index, double volume) { if (index >= 0 && index < model::materials.size()) { diff --git a/tests/unit_tests/test_lib.py b/tests/unit_tests/test_lib.py index c21f85633..ccff33ac3 100644 --- a/tests/unit_tests/test_lib.py +++ b/tests/unit_tests/test_lib.py @@ -207,11 +207,6 @@ def test_material(lib_init): m.name = "Not hot borated water" assert m.name == "Not hot borated water" - assert m.temperature == 293.6 - m.temperature = 400. - assert m.temperature == 400. - m.temperature == 293.6 - def test_properties_density(lib_init): m = openmc.lib.materials[1] diff --git a/tests/unit_tests/test_model.py b/tests/unit_tests/test_model.py index 092ccfc3c..cffa5c249 100644 --- a/tests/unit_tests/test_model.py +++ b/tests/unit_tests/test_model.py @@ -127,14 +127,11 @@ def test_init(run_in_tmpdir, pin_model_attributes, mpi_intracomm): assert test_model._cells_by_id == {} assert test_model._cells_by_name == {} assert test_model.is_initialized is False - assert hasattr(test_model.intracomm, 'rank') # 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) - if mpi_intracomm is not None: - test_model.intracomm = mpi_intracomm assert test_model.geometry is geom assert test_model.materials is mats assert test_model.settings is settings @@ -142,7 +139,7 @@ def test_init(run_in_tmpdir, pin_model_attributes, mpi_intracomm): assert test_model.plots is plots 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]]} + 'UO2': {mats[0]}, 'Zirc': {mats[1]}, 'Borated water': {mats[2]}} # The last cell is the one that contains the infinite fuel assert test_model._cells_by_id == \ {2: geom.root_universe.cells[2], 3: geom.root_universe.cells[3], @@ -151,9 +148,9 @@ def test_init(run_in_tmpdir, pin_model_attributes, mpi_intracomm): # 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[2]], - '': [geom.root_universe.cells[3], geom.root_universe.cells[4]], - 'inf fuel': [geom.root_universe.cells[2].fill.cells[1]]} + 'fuel': {geom.root_universe.cells[2]}, + '': {geom.root_universe.cells[3], geom.root_universe.cells[4]}, + 'inf fuel': {geom.root_universe.cells[2].fill.cells[1]}} assert test_model.is_initialized is False # Finally test the parameter type checking by passing bad types and @@ -202,8 +199,8 @@ def test_from_xml(run_in_tmpdir, pin_model_attributes): {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]]} + 'UO2': {test_model.materials[0]}, 'Zirc': {test_model.materials[1]}, + 'Borated water': {test_model.materials[2]}} assert test_model._cells_by_id == { 2: test_model.geometry.root_universe.cells[2], 3: test_model.geometry.root_universe.cells[3], @@ -212,10 +209,10 @@ def test_from_xml(run_in_tmpdir, pin_model_attributes): # 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[2]], - '': [test_model.geometry.root_universe.cells[3], - test_model.geometry.root_universe.cells[4]], - 'inf fuel': [test_model.geometry.root_universe.cells[2].fill.cells[1]]} + 'fuel': {test_model.geometry.root_universe.cells[2]}, + '': {test_model.geometry.root_universe.cells[3], + test_model.geometry.root_universe.cells[4]}, + 'inf fuel': {test_model.geometry.root_universe.cells[2].fill.cells[1]}} assert test_model.is_initialized is False @@ -223,9 +220,7 @@ def test_init_finalize_lib(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) - if mpi_intracomm is not None: - test_model.intracomm = mpi_intracomm - test_model.init_lib(output=False) + test_model.init_lib(output=False, intracomm=mpi_intracomm) # First check that the API is advertised as initialized assert openmc.lib.is_initialized is True @@ -252,9 +247,7 @@ def test_import_properties(run_in_tmpdir, mpi_intracomm): # Create PWR pin cell model and write XML files openmc.reset_auto_ids() model = openmc.examples.pwr_pin_cell() - if mpi_intracomm is not None: - model.intracomm = mpi_intracomm - model.init_lib(output=False) + model.init_lib(output=False, intracomm=mpi_intracomm) # Change fuel temperature and density and export properties cell = openmc.lib.cells[1] @@ -294,8 +287,6 @@ def test_import_properties(run_in_tmpdir, mpi_intracomm): 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) - if mpi_intracomm is not None: - test_model.intracomm = mpi_intracomm # 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. @@ -305,7 +296,7 @@ def test_run(run_in_tmpdir, pin_model_attributes, mpi_intracomm): cli_flux = sp.get_tally(id=1).get_values(scores=['flux'])[0, 0, 0] cli_fiss = sp.get_tally(id=1).get_values(scores=['fission'])[0, 0, 0] - test_model.init_lib(output=False) + test_model.init_lib(output=False, intracomm=mpi_intracomm) sp_path = test_model.run(output=False) with openmc.StatePoint(sp_path) as sp: lib_keff = sp.k_combined @@ -334,8 +325,6 @@ def test_run(run_in_tmpdir, pin_model_attributes, mpi_intracomm): def test_plots(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) - if mpi_intracomm is not None: - test_model.intracomm = mpi_intracomm # This test cannot check the correctness of the plot, but it can # check that a plot was made and that the expected ppm and png files are @@ -352,7 +341,7 @@ def test_plots(run_in_tmpdir, pin_model_attributes, mpi_intracomm): # We will run the test twice, the first time without C API, the second with for i in range(2): if i == 1: - test_model.init_lib(output=False) + test_model.init_lib(output=False, intracomm=mpi_intracomm) test_model.plot_geometry(output=False, convert=convert) # Now look for the files, expect to find test.ppm, plot_2.ppm, and if @@ -369,14 +358,11 @@ def test_plots(run_in_tmpdir, pin_model_attributes, mpi_intracomm): def test_py_lib_attributes(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) - if mpi_intracomm is not None: - test_model.intracomm = mpi_intracomm - test_model.init_lib(output=False) + test_model.init_lib(output=False, intracomm=mpi_intracomm) # Now we can call rotate_cells, translate_cells, update_densities, - # update_cell_temperatures, and update_material_temperatures and make sure - # the changes have taken hold. + # and update_cell_temperatures and make sure the changes have taken hold. # For each we will first try bad inputs to make sure we get the right # errors and then we do a good one which calls the material by name and # then id to make sure it worked @@ -440,14 +426,6 @@ def test_py_lib_attributes(run_in_tmpdir, pin_model_attributes, mpi_intracomm): assert abs(test_model.geometry.root_universe.cells[3].temperature - 600.) < 1e-13 - # Now lets do the material temperature updates. - # Check initial conditions - assert abs(openmc.lib.materials[1].temperature - 293.6) < 1e-13 - # Change the temperature - test_model.update_material_temperatures(['UO2'], 600.) - assert abs(openmc.lib.materials[1].temperature - 600.) < 1e-13 - assert abs(test_model.materials[0].temperature - 600.) < 1e-13 - # And finally material volume assert abs(openmc.lib.materials[1].volume - 0.4831931368640985) < 1e-13 # The temperature on the material will be None because its just the default @@ -466,8 +444,6 @@ def test_deplete(run_in_tmpdir, pin_model_attributes, mpi_intracomm): with open('test_chain.xml', 'w') as f: f.write(chain_file_xml) test_model = openmc.Model(geom, mats, settings, tals, plots) - if mpi_intracomm is not None: - test_model.intracomm = mpi_intracomm initial_mat = mats[0].clone() initial_u = initial_mat.get_nuclide_atom_densities()['U235'][1] @@ -497,7 +473,7 @@ def test_deplete(run_in_tmpdir, pin_model_attributes, mpi_intracomm): mats[0].set_density('atom/b-cm', tot_density) # Now we can re-run with the pre-initialized API - test_model.init_lib(output=False) + test_model.init_lib(output=False, intracomm=mpi_intracomm) test_model.deplete([1e6], 'predictor', final_step=False, operator_kwargs=op_kwargs, power=1., output=False) @@ -518,8 +494,6 @@ def test_calc_volumes(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) - if mpi_intracomm is not None: - test_model.intracomm = mpi_intracomm # With no vol calcs, it should fail with pytest.raises(ValueError): @@ -555,13 +529,10 @@ def test_calc_volumes(run_in_tmpdir, pin_model_attributes, mpi_intracomm): for file in ['volume_1.h5', 'volume_2.h5']: file = Path(file) file.unlink() - test_model.init_lib(output=False) + test_model.init_lib(output=False, intracomm=mpi_intracomm) test_model.calculate_volumes(output=False, apply_volumes=True) assert mats[2].volume > 0. assert geom.root_universe.cells[3].volume > 0. assert openmc.lib.materials[3].volume == mats[2].volume test_model.finalize_lib() - - -