Resolving comments from @paulromano and updating tests accordingly

This commit is contained in:
agnelson 2021-09-29 18:51:00 -05:00
parent d7bf830028
commit dc80b799ac
8 changed files with 373 additions and 385 deletions

View file

@ -583,40 +583,12 @@ class SILEQIIntegrator(SIIntegrator):
return proc_time, [eos_conc, inter_conc], [res_bar]
def integrator_factory(method):
"""This method is a factor for the integrator sub-classes
Params
------
method : str
The type of integrator method to use. Valid values are: 'cecm',
'predictor', 'cf4', 'epc_rk4', 'si_celi', 'si_leqi', 'celi', and 'leqi'
Returns
-------
integrator : Integrator
The type of integrator
"""
if method == 'cecm':
integrator = CECMIntegrator
elif method == 'predictor':
integrator = PredictorIntegrator
elif method == 'cf4':
integrator = CF4Integrator
elif method == 'epc_rk4':
integrator = EPCRK4Integrator
elif method == 'si_celi':
integrator = SICELIIntegrator
elif method == 'si_leqi':
integrator = SILEQIIntegrator
elif method == 'celi':
integrator = CELIIntegrator
elif method == 'leqi':
integrator = LEQIIntegrator
else:
msg = "Invalid integrator method: {}!".format(method)
raise ValueError(msg)
return integrator
integrator_by_name = {
'cecm': CECMIntegrator,
'predictor': PredictorIntegrator,
'cf4': CF4Integrator,
'epc_rk4': EPCRK4Integrator,
'si_celi': SICELIIntegrator,
'si_leqi': SILEQIIntegrator,
'celi': CELIIntegrator,
'leqi': LEQIIntegrator}

View file

@ -202,7 +202,7 @@ class Operator(TransportOperator):
self.settings = settings
self.geometry = geometry
self.diff_burnable_mats = diff_burnable_mats
self.cleanup_when_done = True
self._cleanup_when_done = True
# Reduce the chain before we create more materials
if reduce_chain:
@ -537,7 +537,7 @@ class Operator(TransportOperator):
# Initialize OpenMC library
comm.barrier()
if not openmc.lib.LIB_INIT:
if not openmc.lib.is_initialized:
openmc.lib.init(intracomm=comm)
# Generate tallies in memory
@ -556,7 +556,7 @@ class Operator(TransportOperator):
def finalize(self):
"""Finalize a depletion simulation and release resources."""
if self.cleanup_when_done:
if self._cleanup_when_done:
openmc.lib.finalize()
def _update_materials(self):

View file

@ -1,23 +1,79 @@
from collections.abc import Iterable
from numbers import Integral
import subprocess
from contextlib import contextmanager
from pathlib import Path
import os
import openmc
@contextmanager
def change_directory(working_dir):
"""A context manager for executing in a provided working directory"""
start_dir = Path().absolute()
try:
Path.mkdir(working_dir, exist_ok=True)
os.chdir(working_dir)
yield
finally:
os.chdir(start_dir)
def process_CLI_arguments(volume=False, geometry_debug=False, particles=None,
plot=False, restart_file=None, threads=None,
tracks=False, event_based=False,
openmc_exec='openmc', mpi_args=None):
"""Run an OpenMC simulation.
Parameters
----------
volume : bool, optional
Run in stochastic volume calculation mode. Defaults to False.
geometry_debug : bool, optional
Turn on geometry debugging during simulation. Defaults to False.
particles : int, optional
Number of particles to simulate per generation.
plot : bool, optional
Run in plotting mode. Defaults to False.
restart_file : str, optional
Path to restart file to use
threads : int, optional
Number of OpenMP threads. If OpenMC is compiled with OpenMP threading
enabled, the default is implementation-dependent but is usually equal
to the number of hardware threads available (or a value set by the
:envvar:`OMP_NUM_THREADS` environment variable).
tracks : bool, optional
Write tracks for all particles. Defaults to False.
event_based : bool, optional
Turns on event-based parallelism, instead of default history-based
openmc_exec : str, optional
Path to OpenMC executable. Defaults to 'openmc'.
mpi_args : list of str, optional
MPI execute command and any additional MPI arguments to pass,
e.g. ['mpiexec', '-n', '8'].
.. versionadded:: 0.13.0
Returns
-------
args : Iterable of str
The runtime flags converted to CLI arguments of the OpenMC executable
"""
args = [openmc_exec]
if volume:
args += ['--volume']
if isinstance(particles, Integral) and particles > 0:
args += ['-n', str(particles)]
if isinstance(threads, Integral) and threads > 0:
args += ['-s', str(threads)]
if geometry_debug:
args.append('-g')
if event_based:
args.append('-e')
if isinstance(restart_file, str):
args += ['-r', restart_file]
if tracks:
args.append('-t')
if mpi_args is not None:
args = mpi_args + args
return args
def _run(args, output, cwd):
@ -141,8 +197,8 @@ def calculate_volumes(threads=None, output=True, cwd='.',
----------
threads : int, optional
Number of OpenMP threads. If OpenMC is compiled with OpenMP threading
enabled, the default is implementation-dependent but is usually equal to
the number of hardware threads available (or a value set by the
enabled, the default is implementation-dependent but is usually equal
to the number of hardware threads available (or a value set by the
:envvar:`OMP_NUM_THREADS` environment variable).
output : bool, optional
Capture OpenMC output from standard out
@ -165,12 +221,9 @@ def calculate_volumes(threads=None, output=True, cwd='.',
openmc.VolumeCalculation
"""
args = [openmc_exec, '--volume']
if isinstance(threads, Integral) and threads > 0:
args += ['-s', str(threads)]
if mpi_args is not None:
args = mpi_args + args
args = process_CLI_arguments(volume=True, threads=threads,
openmc_exec=openmc_exec, mpi_args=mpi_args)
_run(args, output, cwd)
@ -186,8 +239,8 @@ def run(particles=None, threads=None, geometry_debug=False,
Number of particles to simulate per generation.
threads : int, optional
Number of OpenMP threads. If OpenMC is compiled with OpenMP threading
enabled, the default is implementation-dependent but is usually equal to
the number of hardware threads available (or a value set by the
enabled, the default is implementation-dependent but is usually equal
to the number of hardware threads available (or a value set by the
:envvar:`OMP_NUM_THREADS` environment variable).
geometry_debug : bool, optional
Turn on geometry debugging during simulation. Defaults to False.
@ -216,27 +269,10 @@ def run(particles=None, threads=None, geometry_debug=False,
If the `openmc` executable returns a non-zero status
"""
args = [openmc_exec]
if isinstance(particles, Integral) and particles > 0:
args += ['-n', str(particles)]
if isinstance(threads, Integral) and threads > 0:
args += ['-s', str(threads)]
if geometry_debug:
args.append('-g')
if event_based:
args.append('-e')
if isinstance(restart_file, str):
args += ['-r', restart_file]
if tracks:
args.append('-t')
if mpi_args is not None:
args = mpi_args + args
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)
_run(args, output, cwd)

View file

@ -60,4 +60,5 @@ from .settings import settings
from .math import *
from .plot import *
LIB_INIT = False
# Flag to denote whether or not openmc.lib.init has been called
is_initialized = False

View file

@ -148,7 +148,7 @@ def export_properties(filename=None):
def finalize():
"""Finalize simulation and free memory"""
_dll.openmc_finalize()
openmc.lib.LIB_INIT = False
openmc.lib.is_initialized = False
def find_cell(xyz):
@ -215,7 +215,7 @@ def import_properties(filename):
See Also
--------
openmc.lib.export_properties
mat
"""
_dll.openmc_properties_import(filename.encode())
@ -255,7 +255,7 @@ def init(args=None, intracomm=None):
intracomm = c_void_p(address)
_dll.openmc_init(argc, argv, intracomm)
openmc.lib.LIB_INIT = True
openmc.lib.is_initialized = True
def is_statepoint_batch():

View file

@ -1,20 +1,30 @@
from collections.abc import Iterable
import operator
import os
from pathlib import Path
from numbers import Integral
import time
import warnings
import subprocess
from contextlib import contextmanager
import h5py
import numpy as np
import openmc
from openmc.checkvalue import check_type, check_value, check_iterable_type, \
check_length
import openmc.deplete as dep
from openmc.data.library import DataLibrary
from openmc.exceptions import DataError, InvalidIDError, SetupError
from openmc.checkvalue import check_type, check_value
from openmc.exceptions import InvalidIDError
@contextmanager
def _change_directory(working_dir):
"""A context manager for executing in a provided working directory"""
start_dir = Path().cwd()
Path.mkdir(working_dir, exist_ok=True)
os.chdir(working_dir)
try:
yield
finally:
os.chdir(start_dir)
class Model:
@ -28,6 +38,8 @@ class Model:
not set, it will attempt to create a ``materials.xml`` file based on all
materials appearing in the geometry.
.. versionchanged:: 0.13.0
Parameters
----------
geometry : openmc.Geometry, optional
@ -40,14 +52,8 @@ class Model:
Tallies information
plots : openmc.Plots, optional
Plot information
chain_file : str or Path, optional
Path to the depletion chain XML file. Defaults to the chain
found under the ``depletion_chain`` in the
:envvar:`OPENMC_CROSS_SECTIONS` environment variable if it exists. If a
str is provided it will be converted to a Path object.
fission_q : dict, optional
Dictionary of nuclides and their fission Q values [eV].
If not given, values will be pulled from the ``chain_file``.
intracomm : mpi4py.MPI.Intracomm or None, optional
MPI intracommunicator
Attributes
----------
@ -61,19 +67,13 @@ class Model:
Tallies information
plots : openmc.Plots
Plot information
chain_file : str or Path
Path to the depletion chain XML file. Defaults to the chain
found under the ``depletion_chain`` in the
:envvar:`OPENMC_CROSS_SECTIONS` environment variable if it exists. If a
str is provided it will be converted to a Path object.
fission_q : dict
Dictionary of nuclides and their fission Q values [eV].
If not given, values will be pulled from the ``chain_file``.
intracomm : mpi4py.MPI.Intracomm or None
MPI intracommunicator
"""
def __init__(self, geometry=None, materials=None, settings=None,
tallies=None, plots=None, chain_file=None, fission_q=None):
tallies=None, plots=None, intracomm=None):
self.geometry = openmc.Geometry()
self.materials = openmc.Materials()
self.settings = openmc.Settings()
@ -91,18 +91,33 @@ class Model:
if plots is not None:
self.plots = plots
self.chain_file = chain_file
self.fission_q = fission_q
self.intracomm = intracomm
# Store dictionaries to the materials and cells by ID and names
if materials is None:
mats = self.geometry.get_all_materials().values()
else:
mats = self.materials
self._materials_by_id = {mat.id: mat for mat in mats}
self._materials_by_name = {mat.name: mat for mat in mats}
cells = self.geometry.get_all_cells()
# Get the ID maps
self._materials_by_id = {mat.id: mat for mat in mats}
self._cells_by_id = {cell.id: cell for cell in cells.values()}
self._cells_by_name = {cell.name: cell for cell in cells.values()}
# Get the names maps, but since names are not unique, store a list for
# each name key. In this way when the user requests a change by a name,
# the change will be applied to all of the same name.
self._cells_by_name = {}
for cell in cells.values():
if cell.name not in self._cells_by_name:
self._cells_by_name[cell.name] = [cell]
else:
self._cells_by_name[cell.name].append(cell)
self._materials_by_name = {}
for mat in mats:
if mat.name not in self._materials_by_name:
self._materials_by_name[mat.name] = [mat]
else:
self._materials_by_name[mat.name].append(mat)
@property
def geometry(self):
@ -125,16 +140,14 @@ class Model:
return self._plots
@property
def chain_file(self):
return self._chain_file
def intracomm(self):
return self._intracomm
@property
def fission_q(self):
return self._fission_q
@property
def C_init(self):
return openmc.lib.LIB_INIT
def is_initialized(self):
# TODO: Replace openmc.lib.is_initialized with a direct ctypes access
# to simulation::initialized
return openmc.lib.is_initialized
@geometry.setter
def geometry(self, geometry):
@ -176,20 +189,20 @@ class Model:
for plot in plots:
self._plots.append(plot)
@chain_file.setter
def chain_file(self, chain_file):
check_type('chain_file', chain_file, (type(None), str, Path))
if isinstance(chain_file, str):
self._chain_file = Path(chain_file).resolve()
elif isinstance(chain_file, Path):
self._chain_file = chain_file.resolve()
@intracomm.setter
def intracomm(self, intracomm):
try:
from mpi4py import MPI
mpi_avail = True
except ImportError:
mpi_avail = False
if intracomm is None or not mpi_avail:
# TODO: move dummy_comm from openmc.deplete to openmc
from openmc.deplete.dummy_comm import DummyCommunicator
self._intracomm = DummyCommunicator()
else:
self._chain_file = None
@fission_q.setter
def fission_q(self, fission_q):
check_type('fission_q', fission_q, (type(None), dict))
self._fission_q = fission_q
check_type('intracomm', intracomm, MPI.Comm)
self._intracomm = intracomm
@classmethod
def from_xml(cls, geometry='geometry.xml', materials='materials.xml',
@ -218,17 +231,20 @@ class Model:
settings = openmc.Settings.from_xml(settings)
return cls(geometry, materials, settings)
def init_C_api(self, threads=None, geometry_debug=False, restart_file=None,
tracks=False, output=True, event_based=False):
"""Initializes the model in memory via the C-API
def init_lib(self, threads=None, geometry_debug=False, restart_file=None,
tracks=False, output=True, event_based=False):
"""Initializes the model in memory via the C API
.. versionadded:: 0.13.0
Parameters
----------
threads : int, optional
Number of OpenMP threads. If OpenMC is compiled with OpenMP threading
enabled, the default is implementation-dependent but is usually equal to
the number of hardware threads available (or a value set by the
:envvar:`OMP_NUM_THREADS` environment variable).
Number of OpenMP threads. If OpenMC is compiled with OpenMP
threading enabled, the default is implementation-dependent but is
usually equal to the number of hardware threads available
(or a value set by the :envvar:`OMP_NUM_THREADS` environment
variable).
geometry_debug : bool, optional
Turn on geometry debugging during simulation. Defaults to False.
restart_file : str, optional
@ -242,7 +258,7 @@ class Model:
"""
# TODO: right now the only way to set most of the above parameters via
# the C-API are at initialization time despite use-cases existing to
# the C API are at initialization time despite use-cases existing to
# set them for individual runs. For now this functionality is exposed
# where it exists (here in init), but in the future the functionality
# should be exposed so that it can be accessed via model.run(...)
@ -267,42 +283,46 @@ class Model:
if tracks:
args.append('-t')
self.clear_C_api()
self.finalize_lib()
if dep.comm.rank == 0:
if self.intracomm.rank == 0:
self.export_to_xml()
dep.comm.barrier()
self.intracomm.barrier()
openmc.lib.init(args=args, intracomm=dep.comm)
openmc.lib.init(args=args, intracomm=self.intracomm)
def finalize_lib(self):
"""Finalize simulation and free memory allocated for the C API
.. versionadded:: 0.13.0
"""
def clear_C_api(self):
"""Finalize simulation and free memory allocated for the C-API"""
openmc.lib.finalize()
def deplete(self, timesteps, chain_file=None, method='cecm',
fission_q=None, final_step=True, directory='.',
**kwargs):
def deplete(self, timesteps, method='cecm', final_step=True,
operator_kwargs=None, integrator_kwargs=None, directory='.'):
"""Deplete model using specified timesteps/power
.. versionchanged:: 0.13.0
Parameters
----------
timesteps : iterable of float
Array of timesteps in units of [s]. Note that values are not
cumulative.
chain_file : str, optional
Path to the depletion chain XML file. Defaults to the chain
found under the ``depletion_chain`` in the
:envvar:`OPENMC_CROSS_SECTIONS` environment variable if it exists.
method : str, optional
Integration method used for depletion (e.g., 'cecm', 'predictor').
Defaults to 'cecm'.
fission_q : dict, optional
Dictionary of nuclides and their fission Q values [eV].
If not given, values will be pulled from the ``chain_file``.
Defaults to pulling from the ``chain_file``.
final_step : bool, optional
Indicate whether or not a transport solve should be run at the end
of the last timestep. Defaults to running this transport solve.
operator_kwargs : dict
Keyword arguments passed to the depletion Operator initializer
(e.g., :func:`openmc.deplete.Operator`)
integrator_kwargs : dict
Keyword arguments passed to the depletion Operator initializer
(e.g., :func:`openmc.deplete.integrator.cecm`)
directory : str, optional
Directory to write XML files to. If it doesn't exist already, it
will be created. Defaults to the current working directory
@ -312,69 +332,43 @@ class Model:
"""
# To keep Model.deplete(...) API compatibility, we will allow the
# chain_file and fission_q params to be set if provided while we set
# the depletion operator
if chain_file is not None:
this_chain_file = Path(chain_file).resolve()
warnings.warn("The chain_file argument of Model.deplete(...) "
"has been deprecated and may be removed in a "
"future version. The Model.chain_file should be"
"used instead.", DeprecationWarning)
else:
this_chain_file = self.chain_file
if fission_q is not None:
warnings.warn("The fission_q argument of Model.deplete(...) "
"has been deprecated and may be removed in a "
"future version. The Model.fission_q should be"
"used instead.", DeprecationWarning)
this_fission_q = fission_q
else:
this_fission_q = fission_q
# Import openmc.deplete here so the Model can be used even if the
# shared library is unavailable.
import openmc.deplete as dep
# Create directory if required
d = Path(directory)
if not d.is_dir():
d.mkdir(parents=True)
start_dir = Path.cwd()
os.chdir(d)
with _change_directory(Path(directory)):
depletion_operator = \
dep.Operator(self.geometry, self.settings, **operator_kwargs)
# Tell depletion_operator.finalize NOT to clear C API memory when
# it is done
depletion_operator.cleanup_when_done = False
depletion_operator = \
dep.Operator(self.geometry, self.settings,
str(this_chain_file.absolute()),
fission_q=this_fission_q)
# Tell depletion_operator.finalize NOT to clear C-API memory when it is
# done
depletion_operator.cleanup_when_done = False
# Set up the integrator
check_value('method', method,
dep.integrators.integrator_by_name.keys())
integrator_class = dep.integrators.integrator_by_name[method]
integrator = integrator_class(depletion_operator, timesteps,
**integrator_kwargs)
# Set up the integrator
integrator_class = dep.integrators.integrator_factory(method)
integrator = integrator_class(depletion_operator,
timesteps, **kwargs)
# Now perform the depletion
integrator.integrate(final_step)
# Now perform the depletion
integrator.integrate(final_step)
# If we did not perform a transport calculation on the final step,
# then make the code update the C API material inventory
if not final_step:
depletion_operator._update_materials()
# If we did not perform a transport calculation on the final step, then
# make the code update the C-API material inventory
if not final_step:
depletion_operator._update_materials()
# Now make the python Materials match the C-API material data
for mat_id, mat in self._materials_by_id.items():
if mat.depletable:
# Get the C data
c_mat = openmc.lib.materials[mat_id]
nuclides, densities = c_mat._get_densities()
# And now we can remove isotopes and add these ones in
atom_density = 0.
for nuc, density in zip(nuclides, densities):
mat.remove_nuclide(nuc) # Replace if it's there
mat.add_nuclide(nuc, density)
atom_density += density
mat.set_density('atom/b-cm', atom_density)
os.chdir(start_dir)
# Now make the python Materials match the C API material data
for mat_id, mat in self._materials_by_id.items():
if mat.depletable:
# Get the C data
c_mat = openmc.lib.materials[mat_id]
nuclides, densities = c_mat._get_densities()
# And now we can remove isotopes and add these ones in
mat.nuclides.clear()
for nuc, density in zip(nuclides, densities):
mat.add_nuclide(nuc, density)
mat.set_density('atom/b-cm', sum(densities))
def export_to_xml(self, directory='.'):
"""Export model to XML files.
@ -412,8 +406,8 @@ class Model:
def import_properties(self, filename):
"""Import physical properties
.. versionchanged:: 0.12.3
This method now updates values as loaded in memory with the C-API
.. versionchanged:: 0.13.0
This method now updates values as loaded in memory with the C API
Parameters
----------
@ -443,9 +437,9 @@ class Model:
cell = cells[cell_id]
if cell.fill_type in ('material', 'distribmat'):
cell.temperature = group['temperature'][()]
if self.C_init:
C_cell = openmc.lib.cells[cell_id]
C_cell.set_temperature(group['temperature'][()])
if self.is_initialized:
lib_cell = openmc.lib.cells[cell_id]
lib_cell.set_temperature(group['temperature'][()])
# Make sure number of materials matches
mats_group = fh['materials']
@ -459,14 +453,14 @@ class Model:
mat_id = int(name.split()[1])
atom_density = group.attrs['atom_density']
materials[mat_id].set_density('atom/b-cm', atom_density)
if self.C_init:
if self.is_initialized:
C_mat = openmc.lib.materials[mat_id]
C_mat.set_density(atom_density, 'atom/b-cm')
def run(self, particles=None, threads=None, geometry_debug=False,
restart_file=None, tracks=False, output=True, cwd='.',
openmc_exec='openmc', mpi_args=None, event_based=False):
"""Runs OpenMC. If the C-API has been initialized, then the C-API is
"""Runs OpenMC. If the C API has been initialized, then the C API is
used, otherwise, this method creates the XML files and runs OpenMC via
a system call. In both cases this method returns the path to the last
statepoint file generated.
@ -475,8 +469,8 @@ class Model:
Instead of returning the final k-effective value, this function now
returns the path to the final statepoint written.
.. versionchanged:: 0.12.3
This method can utilize the C-API for execution
.. versionchanged:: 0.13.0
This method can utilize the C API for execution
Parameters
----------
@ -520,8 +514,8 @@ class Model:
last_statepoint = None
# Operate in the provided working directory
with openmc.change_directory(Path(cwd)):
if self.C_init:
with _change_directory(Path(cwd)):
if self.is_initialized:
# Handle the run options as applicable
# First dont allow ones that must be set via init
for arg_name, arg, default in zip(
@ -529,8 +523,7 @@ class Model:
[threads, geometry_debug, restart_file, tracks],
[None, False, None, False]):
if arg != default:
msg = "{} must be set via Model.c_init(...)".format(
arg_name)
msg = f"{arg_name} must be set via Model.is_initialized(...)"
raise ValueError(msg)
if particles is not None:
@ -546,9 +539,9 @@ class Model:
# Event-based can be set at init-time or on a case-basis.
# Handle the argument here.
# TODO This will be dealt with in a future change to the C-API
# TODO This will be dealt with in a future change to the C API
# Then run using the C-API
# Then run using the C API
openmc.lib.run()
# Reset changes for the openmc.run kwargs handling
@ -577,12 +570,14 @@ class Model:
last_statepoint = sp
return last_statepoint
def calculate_volume(self, threads=None, output=True, cwd='.',
openmc_exec='openmc', mpi_args=None,
apply_volumes=True):
def calculate_volumes(self, threads=None, output=True, cwd='.',
openmc_exec='openmc', mpi_args=None,
apply_volumes=True):
"""Runs an OpenMC stochastic volume calculation and, if requested,
applies volumes to the model
.. versionadded:: 0.13.0
Parameters
----------
threads : int, optional
@ -590,16 +585,16 @@ class Model:
threading enabled, the default is implementation-dependent but is
usually equal to the number of hardware threads available (or a
value set by the :envvar:`OMP_NUM_THREADS` environment variable).
This currenty only applies to the case when not using the C-API.
This currenty only applies to the case when not using the C API.
output : bool, optional
Capture OpenMC output from standard out
openmc_exec : str, optional
Path to OpenMC executable. Defaults to 'openmc'.
This only applies to the case when not using the C-API.
This only applies to the case when not using the C API.
mpi_args : list of str, optional
MPI execute command and any additional MPI arguments to pass,
e.g. ['mpiexec', '-n', '8'].
This only applies to the case when not using the C-API.
This only applies to the case when not using the C API.
cwd : str, optional
Path to working directory to run in. Defaults to the current
working directory.
@ -613,10 +608,10 @@ class Model:
raise ValueError("The Settings.volume_calculation attribute must"
" be specified before executing this method!")
with openmc.change_directory(Path(cwd)):
if self.C_init:
with _change_directory(Path(cwd)):
if self.is_initialized:
if threads is not None:
msg = "Threads must be set via Model.c_init(...)"
msg = "Threads must be set via Model.is_initialized(...)"
raise ValueError(msg)
if mpi_args is not None:
msg = "The MPI environment must be set otherwise such as" \
@ -645,7 +640,7 @@ class Model:
if apply_volumes:
# Load the results
f_names = \
["volume_{}.h5".format(i + 1)
[f"volume_{i + 1}.h5"
for i in range(len(self.settings.volume_calculations))]
vol_calcs = [openmc.VolumeCalculation.load_results(f_name)
for f_name in f_names]
@ -654,9 +649,9 @@ class Model:
# First add them to the Python side
self.geometry.add_volume_information(vol_calc)
# And now repeat for the C-API
# And now repeat for the C API
if vol_calc.domain == 'material':
# Then we can do this in the C-API
# Then we can do this in the C API
for domain_id in vol_calc.domains:
self.update_material_volumes(
[domain_id], vol_calc.volumes[domain_id])
@ -670,6 +665,8 @@ class Model:
`ImageMagick <https://www.imagemagick.org>`_ which includes a
`convert` command.
.. versionadded:: 0.13.0
Parameters
----------
output : bool, optional
@ -679,7 +676,7 @@ class Model:
working directory.
openmc_exec : str, optional
Path to OpenMC executable. Defaults to 'openmc'.
This only applies to the case when not using the C-API.
This only applies to the case when not using the C API.
convert : bool, optional
Whether or not to attempt to convert from PPM to PNG
convert_exec : str, optional
@ -691,14 +688,14 @@ class Model:
raise ValueError("The Model.plots attribute must be specified "
"before executing this method!")
with openmc.change_directory(Path(cwd)):
# TODO: openmc_init doesnt read plots.xml unless it is in plot mode
with _change_directory(Path(cwd)):
# TODO: openmis_initialized doesnt read plots.xml unless it is in plot mode
# so the following will not work. Commented out for now and
# replacing with non-C-API code
# replacing with non-C API code
self.export_to_xml()
openmc.plot_geometry(output=output, openmc_exec=openmc_exec)
# if self.C_init:
# if self.is_initialized:
# # Apply the output settings
# if not output:
# init_verbosity = openmc.lib.settings.verbosity
@ -735,13 +732,13 @@ class Model:
check_value('attrib_name', attrib_name,
('temperature', 'volume', 'density', 'rotation',
'translation'))
# The C-API only allows setting density units of atom/b-cm and g/cm3
# 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 so lets raise an exception
if obj_type == 'cell' and attrib_name == 'volume':
raise NotImplementedError(
'Setting a Cell volume is not supported!')
# Same with setting temperatures, TODO: update C-API for this
# Same with setting temperatures, TODO: update C API for this
if obj_type == 'material' and attrib_name == 'temperature':
raise NotImplementedError(
'Setting a Material temperature is not yet supported!')
@ -756,62 +753,61 @@ class Model:
if obj_type == 'cell':
by_name = self._cells_by_name
by_id = self._cells_by_id
C_by_id = openmc.lib.cells
obj_by_id = openmc.lib.cells
else:
by_name = self._materials_by_name
by_id = self._materials_by_id
C_by_id = openmc.lib.materials
obj_by_id = openmc.lib.materials
# Get the list of ids to use if converting from names and accepting
# only values that have actual ids
ids = [None] * len(names_or_ids)
for i, name_or_id in enumerate(names_or_ids):
ids = []
for name_or_id in names_or_ids:
if isinstance(name_or_id, Integral):
if name_or_id in by_id:
ids[i] = int(name_or_id)
ids.append(int(name_or_id))
else:
msg = '{} ID {} is not present in the model!'.format(
obj_type.capitalize(), name_or_id)
cap_obj = obj_type.capitalize()
msg = f'{cap_obj} ID {name_or_id} " \
"is not present in the model!'
raise InvalidIDError(msg)
elif isinstance(name_or_id, str):
if name_or_id in by_name:
ids[i] = by_name[name_or_id].id
# Then by_name[name_or_id] is a list so we need to add all
# entries
ids.extend([obj.id for obj in by_name[name_or_id]])
else:
msg = '{} {} is not present in the model!'.format(
obj_type.capitalize(), name_or_id)
cap_obj = obj_type.capitalize()
msg = f'{cap_obj} {name_or_id} " \
"is not present in the model!'
raise InvalidIDError(msg)
# Now perform the change to both python and C-API
# Now perform the change to both python and C API
for id_ in ids:
obj = by_id[id_]
if attrib_name == 'rotation':
obj.rotation = value
elif attrib_name == 'translation':
obj.translation = value
elif attrib_name == 'volume':
obj.volume = value
elif attrib_name == 'temperature':
obj.temperature = value
elif attrib_name == 'density':
if attrib_name == 'density':
obj.set_density(density_units, value)
# Next lets keep what is in C-API memory up to date as well
if self.C_init:
C_obj = C_by_id[id_]
if attrib_name == 'rotation':
C_obj.rotation = value
elif attrib_name == 'translation':
C_obj.translation = value
elif attrib_name == 'volume':
C_obj.volume = value
elif attrib_name == 'temperature':
C_obj.set_temperature(value)
else:
setattr(obj, attrib_name, value)
# Next lets keep what is in C API memory up to date as well
if self.is_initialized:
lib_obj = obj_by_id[id_]
if attrib_name == 'temperature':
lib_obj.set_temperature(value)
elif attrib_name == 'density':
C_obj.set_density(value, density_units)
lib_obj.set_density(value, density_units)
else:
setattr(lib_obj, attrib_name, value)
def rotate_cells(self, names_or_ids, vector):
"""Rotate the identified cell(s) by the specified rotation vector.
The rotation is only applied to cells filled with a universe.
.. 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
@ -829,6 +825,11 @@ class Model:
"""Translate the identified cell(s) by the specified translation vector.
The translation is only applied to cells filled with a universe.
.. 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
@ -845,6 +846,11 @@ class Model:
def update_densities(self, names_or_ids, density, density_units='atom/b-cm'):
"""Update the density of a given set of materials to a new 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
@ -863,6 +869,11 @@ class Model:
def update_cell_temperatures(self, names_or_ids, temperature):
"""Update the temperature of a set of cells 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
@ -879,6 +890,11 @@ class Model:
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
@ -895,6 +911,11 @@ class Model:
def update_material_volumes(self, names_or_ids, volume):
"""Update the volume 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

View file

@ -129,9 +129,9 @@ def pin_model_attributes():
</nuclide>
</depletion_chain>
"""
operator_kwargs = {'chain_file': chain, 'fission_q': fission_q}
return (mats, geom, settings, tals, plots, chain, fission_q,
chain_file_xml)
return (mats, geom, settings, tals, plots, operator_kwargs, chain_file_xml)
@pytest.fixture(scope='module')
def mpi_intracomm():

View file

@ -4,10 +4,11 @@ from pathlib import Path
from shutil import which
import openmc
import openmc.lib
from openmc.deplete.dummy_comm import DummyCommunicator
def test_init(run_in_tmpdir, pin_model_attributes):
mats, geom, settings, tals, plots, chain, fission_q, _ = \
def test_init(run_in_tmpdir, pin_model_attributes, mpi_intracomm):
mats, geom, settings, tals, plots, _, _ = \
pin_model_attributes
openmc.reset_auto_ids()
@ -22,29 +23,25 @@ def test_init(run_in_tmpdir, pin_model_attributes):
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
assert test_model.is_initialized is False
assert isinstance(test_model.intracomm, DummyCommunicator)
# 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)
test_model = openmc.Model(geom, mats, settings, tals, plots, mpi_intracomm)
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._materials_by_name == {
'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],
@ -53,13 +50,21 @@ def test_init(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': geom.root_universe.cells[2], '': geom.root_universe.cells[4],
'inf fuel': geom.root_universe.cells[2].fill.cells[1]}
assert test_model.C_init is False
'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
if mpi_intracomm is None:
assert isinstance(test_model.intracomm, DummyCommunicator)
else:
assert test_model.intracomm == mpi_intracomm
# 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]
if mpi_intracomm is not None:
def_params = [geom, mats, settings, tals, plots, mpi_intracomm]
else:
def_params = [geom, mats, settings, tals, plots]
for i in range(len(def_params)):
args = def_params.copy()
# Try an integer, as that is a bad type for all arguments
@ -69,7 +74,7 @@ def test_init(run_in_tmpdir, pin_model_attributes):
def test_from_xml(run_in_tmpdir, pin_model_attributes):
mats, geom, settings, tals, plots, _, _, _ = 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
@ -99,14 +104,12 @@ def test_from_xml(run_in_tmpdir, pin_model_attributes):
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]}
'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],
@ -115,28 +118,23 @@ 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[4],
'inf fuel': test_model.geometry.root_universe.cells[2].fill.cells[1]}
assert test_model.C_init is False
'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
assert isinstance(test_model.intracomm, DummyCommunicator)
def test_init_clear_C_api(run_in_tmpdir, pin_model_attributes, mpi_intracomm):
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)
# 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()
mats, geom, settings, tals, plots, _, _ = pin_model_attributes
test_model = openmc.Model(geom, mats, settings, tals, plots, mpi_intracomm)
test_model.init_lib()
# First check that the API is advertised as initialized
assert openmc.lib.LIB_INIT is True
assert test_model.C_init is True
assert openmc.lib.is_initialized is True
assert test_model.is_initialized 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
@ -144,20 +142,14 @@ def test_init_clear_C_api(run_in_tmpdir, pin_model_attributes, mpi_intracomm):
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()
test_model.finalize_lib()
# First check that the API is advertised as initialized
assert openmc.lib.LIB_INIT is False
assert test_model.C_init is False
assert openmc.lib.is_initialized is False
assert test_model.is_initialized 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
# TODO: in above test, tests are necessary for all the arguments of init
def test_import_properties(run_in_tmpdir, mpi_intracomm):
"""Test importing properties on the Model class """
@ -165,7 +157,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()
model.init_C_api()
model.init_lib()
# Change fuel temperature and density and export properties
cell = openmc.lib.cells[1]
@ -185,7 +177,7 @@ def test_import_properties(run_in_tmpdir, mpi_intracomm):
assert openmc.lib.cells[1].get_temperature() == 600.
assert openmc.lib.materials[1].get_density('g/cm3') == pytest.approx(5.0)
# Clear the C-API
# Clear the C API
openmc.lib.finalize()
# Verify the attributes survived by exporting to XML and re-creating
@ -203,24 +195,17 @@ 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)
mats, geom, settings, tals, plots, _, _ = pin_model_attributes
test_model = openmc.Model(geom, mats, settings, tals, plots, 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.
# 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:
# Set the depletion module to use the test intracomm as the depletion
# module's 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
orig_comm = openmc.deplete.comm
openmc.deplete.comm = mpi_intracomm
test_model.init_C_api()
test_model.init_lib()
sp_path = test_model.run(output=False)
with openmc.StatePoint(sp_path) as sp:
C_keff = sp.k_combined
@ -241,24 +226,12 @@ def test_run(run_in_tmpdir, pin_model_attributes, mpi_intracomm):
with pytest.raises(ValueError):
test_model.run(tracks=True)
test_model.clear_C_api()
# And before done, reset the deplete communicator
if mpi_intracomm is not None:
openmc.deplete.comm = orig_comm
test_model.finalize_lib()
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:
# Set the depletion module to use the test intracomm as the depletion
# module's 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
orig_comm = openmc.deplete.comm
openmc.deplete.comm = mpi_intracomm
mats, geom, settings, tals, plots, _, _ = pin_model_attributes
test_model = openmc.Model(geom, mats, settings, tals, plots, 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
@ -272,10 +245,10 @@ def test_plots(run_in_tmpdir, pin_model_attributes, mpi_intracomm):
else:
exts = ['ppm']
# We will run the test twice, the first time without C-API, the second with
# 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_C_api()
test_model.init_lib()
test_model.plot_geometry(output=True, convert=convert)
# Now look for the files, expect to find test.ppm, plot_2.ppm, and if
@ -286,25 +259,14 @@ def test_plots(run_in_tmpdir, pin_model_attributes, mpi_intracomm):
assert test_file.exists()
test_file.unlink()
test_model.clear_C_api()
# And before done, reset the deplete communicator
if mpi_intracomm is not None:
openmc.deplete.comm = orig_comm
test_model.finalize_lib()
def test_py_C_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)
mats, geom, settings, tals, plots, _, _ = pin_model_attributes
test_model = openmc.Model(geom, mats, settings, tals, plots, mpi_intracomm)
if mpi_intracomm is not None:
# Set the depletion module to use the test intracomm as the depletion
# module's 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
orig_comm = openmc.deplete.comm
openmc.deplete.comm = mpi_intracomm
test_model.init_C_api()
test_model.init_lib()
# Now we can call rotate_cells, translate_cells, update_densities,
# update_cell_temperatures, and update_material_temperatures and make sure
@ -376,7 +338,7 @@ def test_py_C_attributes(run_in_tmpdir, pin_model_attributes, mpi_intracomm):
# Check initial conditions
with pytest.raises(NotImplementedError):
test_model.update_material_temperatures(['UO2'], 600.)
# TODO: When C-API material.set_temperature is implemented, uncomment below
# TODO: When C API material.set_temperature is implemented, uncomment below
# assert abs(openmc.lib.materials[1].temperature - 293.6) < 1e-13
# # The temperature on the material will be None because its just the
# # default assert test_model.materials[0].temperature is None
@ -395,8 +357,4 @@ def test_py_C_attributes(run_in_tmpdir, pin_model_attributes, mpi_intracomm):
assert abs(openmc.lib.materials[1].volume - 2.) < 1e-13
assert abs(test_model.materials[0].volume - 2.) < 1e-13
test_model.clear_C_api()
# And before done, reset the deplete communicator
if mpi_intracomm is not None:
openmc.deplete.comm = orig_comm
test_model.finalize_lib()