Delayed mpi4py import until the user clearly indicated intent to use a communicator (or they already have created one and thus have already imported it); corrected versionchanged doc statements to include comments about what changed; added stackoverflow reference

This commit is contained in:
agnelson 2021-10-01 09:21:41 -05:00
parent e1365bd036
commit e4476fc0b7
12 changed files with 102 additions and 175 deletions

View file

@ -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);

View file

@ -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};
};
//==============================================================================

View file

@ -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

33
openmc/dummy_comm.py Normal file
View file

@ -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)

View file

@ -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)

View file

@ -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:

View file

@ -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)

View file

@ -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

View file

@ -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()

View file

@ -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()) {

View file

@ -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]

View file

@ -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()