mirror of
https://github.com/openmc-dev/openmc.git
synced 2026-07-29 06:35:48 -04:00
Merge pull request #2291 from pshriwise/model-xml
Support for a single `model.xml` file
This commit is contained in:
commit
3f8f8f6701
32 changed files with 1284 additions and 372 deletions
|
|
@ -1,22 +1,40 @@
|
|||
def clean_indentation(element, level=0, spaces_per_level=2):
|
||||
"""
|
||||
copy and paste from https://effbot.org/zone/element-lib.htm#prettyprint
|
||||
it basically walks your tree and adds spaces and newlines so the tree is
|
||||
printed in a nice way
|
||||
def clean_indentation(element, level=0, spaces_per_level=2, trailing_indent=True):
|
||||
"""Set indentation of XML element and its sub-elements.
|
||||
Copied and pasted from https://effbot.org/zone/element-lib.htm#prettyprint.
|
||||
It walks your tree and adds spaces and newlines so the tree is
|
||||
printed in a nice way.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
level : int
|
||||
Indentation level for the element passed in (default 0)
|
||||
spaces_per_level : int
|
||||
Number of spaces per indentation level (default 2)
|
||||
trailing_indent : bool
|
||||
Whether or not to add indentation after closing the element
|
||||
|
||||
"""
|
||||
i = "\n" + level*spaces_per_level*" "
|
||||
|
||||
# ensure there's always some tail for the element passed in
|
||||
if not element.tail:
|
||||
element.tail = ""
|
||||
|
||||
if len(element):
|
||||
if not element.text or not element.text.strip():
|
||||
element.text = i + spaces_per_level*" "
|
||||
if not element.tail or not element.tail.strip():
|
||||
if trailing_indent and (not element.tail or not element.tail.strip()):
|
||||
element.tail = i
|
||||
for sub_element in element:
|
||||
# `trailing_indent` is intentionally not forwarded to the recursive
|
||||
# call. Any child element of the topmost element should add
|
||||
# indentation at the end to ensure its parent's indentation is
|
||||
# correct.
|
||||
clean_indentation(sub_element, level+1, spaces_per_level)
|
||||
if not sub_element.tail or not sub_element.tail.strip():
|
||||
sub_element.tail = i
|
||||
else:
|
||||
if level and (not element.tail or not element.tail.strip()):
|
||||
if trailing_indent and level and (not element.tail or not element.tail.strip()):
|
||||
element.tail = i
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -650,7 +650,7 @@ class Cell(IDManagerMixin):
|
|||
surfaces : dict
|
||||
Dictionary mapping surface IDs to :class:`openmc.Surface` instances
|
||||
materials : dict
|
||||
Dictionary mapping material IDs to :class:`openmc.Material`
|
||||
Dictionary mapping material ID strings to :class:`openmc.Material`
|
||||
instances (defined in :math:`openmc.Geometry.from_xml`)
|
||||
get_universe : function
|
||||
Function returning universe (defined in
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ from .plots import _get_plot_image
|
|||
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):
|
||||
openmc_exec='openmc', mpi_args=None, path_input=None):
|
||||
"""Converts user-readable flags in to command-line arguments to be run with
|
||||
the OpenMC executable via subprocess.
|
||||
|
||||
|
|
@ -42,6 +42,9 @@ def _process_CLI_arguments(volume=False, geometry_debug=False, particles=None,
|
|||
mpi_args : list of str, optional
|
||||
MPI execute command and any additional MPI arguments to pass,
|
||||
e.g. ['mpiexec', '-n', '8'].
|
||||
path_input : str or Pathlike
|
||||
Path to a single XML file or a directory containing XML files for the
|
||||
OpenMC executable to read.
|
||||
|
||||
.. versionadded:: 0.13.0
|
||||
|
||||
|
|
@ -82,6 +85,9 @@ def _process_CLI_arguments(volume=False, geometry_debug=False, particles=None,
|
|||
if mpi_args is not None:
|
||||
args = mpi_args + args
|
||||
|
||||
if path_input is not None:
|
||||
args += [path_input]
|
||||
|
||||
return args
|
||||
|
||||
|
||||
|
|
@ -118,7 +124,7 @@ def _run(args, output, cwd):
|
|||
raise RuntimeError(error_msg)
|
||||
|
||||
|
||||
def plot_geometry(output=True, openmc_exec='openmc', cwd='.'):
|
||||
def plot_geometry(output=True, openmc_exec='openmc', cwd='.', path_input=None):
|
||||
"""Run OpenMC in plotting mode
|
||||
|
||||
Parameters
|
||||
|
|
@ -129,6 +135,9 @@ def plot_geometry(output=True, openmc_exec='openmc', cwd='.'):
|
|||
Path to OpenMC executable
|
||||
cwd : str, optional
|
||||
Path to working directory to run in
|
||||
path_input : str
|
||||
Path to a single XML file or a directory containing XML files for the
|
||||
OpenMC executable to read.
|
||||
|
||||
Raises
|
||||
------
|
||||
|
|
@ -136,10 +145,13 @@ def plot_geometry(output=True, openmc_exec='openmc', cwd='.'):
|
|||
If the `openmc` executable returns a non-zero status
|
||||
|
||||
"""
|
||||
_run([openmc_exec, '-p'], output, cwd)
|
||||
args = [openmc_exec, '-p']
|
||||
if path_input is not None:
|
||||
args += [path_input]
|
||||
_run(args, output, cwd)
|
||||
|
||||
|
||||
def plot_inline(plots, openmc_exec='openmc', cwd='.'):
|
||||
def plot_inline(plots, openmc_exec='openmc', cwd='.', path_input=None):
|
||||
"""Display plots inline in a Jupyter notebook.
|
||||
|
||||
.. versionchanged:: 0.13.0
|
||||
|
|
@ -155,6 +167,9 @@ def plot_inline(plots, openmc_exec='openmc', cwd='.'):
|
|||
Path to OpenMC executable
|
||||
cwd : str, optional
|
||||
Path to working directory to run in
|
||||
path_input : str
|
||||
Path to a single XML file or a directory containing XML files for the
|
||||
OpenMC executable to read.
|
||||
|
||||
Raises
|
||||
------
|
||||
|
|
@ -171,7 +186,7 @@ def plot_inline(plots, openmc_exec='openmc', cwd='.'):
|
|||
openmc.Plots(plots).export_to_xml(cwd)
|
||||
|
||||
# Run OpenMC in geometry plotting mode
|
||||
plot_geometry(False, openmc_exec, cwd)
|
||||
plot_geometry(False, openmc_exec, cwd, path_input)
|
||||
|
||||
if plots is not None:
|
||||
images = [_get_plot_image(p, cwd) for p in plots]
|
||||
|
|
@ -179,7 +194,8 @@ def plot_inline(plots, openmc_exec='openmc', cwd='.'):
|
|||
|
||||
|
||||
def calculate_volumes(threads=None, output=True, cwd='.',
|
||||
openmc_exec='openmc', mpi_args=None):
|
||||
openmc_exec='openmc', mpi_args=None,
|
||||
path_input=None):
|
||||
"""Run stochastic volume calculations in OpenMC.
|
||||
|
||||
This function runs OpenMC in stochastic volume calculation mode. To specify
|
||||
|
|
@ -210,6 +226,10 @@ def calculate_volumes(threads=None, output=True, cwd='.',
|
|||
cwd : str, optional
|
||||
Path to working directory to run in. Defaults to the current working
|
||||
directory.
|
||||
path_input : str or Pathlike
|
||||
Path to a single XML file or a directory containing XML files for the
|
||||
OpenMC executable to read.
|
||||
|
||||
|
||||
Raises
|
||||
------
|
||||
|
|
@ -223,14 +243,16 @@ def calculate_volumes(threads=None, output=True, cwd='.',
|
|||
"""
|
||||
|
||||
args = _process_CLI_arguments(volume=True, threads=threads,
|
||||
openmc_exec=openmc_exec, mpi_args=mpi_args)
|
||||
openmc_exec=openmc_exec, mpi_args=mpi_args,
|
||||
path_input=path_input)
|
||||
|
||||
_run(args, output, cwd)
|
||||
|
||||
|
||||
def run(particles=None, threads=None, geometry_debug=False,
|
||||
restart_file=None, tracks=False, output=True, cwd='.',
|
||||
openmc_exec='openmc', mpi_args=None, event_based=False):
|
||||
openmc_exec='openmc', mpi_args=None, event_based=False,
|
||||
path_input=None):
|
||||
"""Run an OpenMC simulation.
|
||||
|
||||
Parameters
|
||||
|
|
@ -239,17 +261,17 @@ 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.
|
||||
restart_file : str, optional
|
||||
Path to restart file to use
|
||||
tracks : bool, optional
|
||||
Enables the writing of particles tracks. The number of particle
|
||||
tracks written to tracks.h5 is limited to 1000 unless
|
||||
Settings.max_tracks is set. Defaults to False.
|
||||
Enables the writing of particles tracks. The number of particle tracks
|
||||
written to tracks.h5 is limited to 1000 unless Settings.max_tracks is
|
||||
set. Defaults to False.
|
||||
output : bool
|
||||
Capture OpenMC output from standard out
|
||||
cwd : str, optional
|
||||
|
|
@ -258,13 +280,17 @@ def run(particles=None, threads=None, geometry_debug=False,
|
|||
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'].
|
||||
MPI execute command and any additional MPI arguments to pass, e.g.
|
||||
['mpiexec', '-n', '8'].
|
||||
event_based : bool, optional
|
||||
Turns on event-based parallelism, instead of default history-based
|
||||
|
||||
.. versionadded:: 0.12
|
||||
|
||||
path_input : str or Pathlike
|
||||
Path to a single XML file or a directory containing XML files for the
|
||||
OpenMC executable to read.
|
||||
|
||||
Raises
|
||||
------
|
||||
RuntimeError
|
||||
|
|
@ -275,6 +301,7 @@ def run(particles=None, threads=None, geometry_debug=False,
|
|||
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)
|
||||
event_based=event_based, openmc_exec=openmc_exec, mpi_args=mpi_args,
|
||||
path_input=path_input)
|
||||
|
||||
_run(args, output, cwd)
|
||||
|
|
|
|||
|
|
@ -103,6 +103,39 @@ class Geometry:
|
|||
if universe.id in volume_calc.volumes:
|
||||
universe.add_volume_information(volume_calc)
|
||||
|
||||
def to_xml_element(self, remove_surfs=False):
|
||||
"""Creates a 'geometry' element to be written to an XML file.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
remove_surfs : bool
|
||||
Whether or not to remove redundant surfaces from the geometry when
|
||||
exporting
|
||||
|
||||
"""
|
||||
# Find and remove redundant surfaces from the geometry
|
||||
if remove_surfs:
|
||||
warnings.warn("remove_surfs kwarg will be deprecated soon, please "
|
||||
"set the Geometry.merge_surfaces attribute instead.")
|
||||
self.merge_surfaces = True
|
||||
|
||||
if self.merge_surfaces:
|
||||
self.remove_redundant_surfaces()
|
||||
|
||||
# Create XML representation
|
||||
element = ET.Element("geometry")
|
||||
self.root_universe.create_xml_subelement(element, memo=set())
|
||||
|
||||
# Sort the elements in the file
|
||||
element[:] = sorted(element, key=lambda x: (
|
||||
x.tag, int(x.get('id'))))
|
||||
|
||||
# Clean the indentation in the file to be user-readable
|
||||
xml.clean_indentation(element)
|
||||
xml.reorder_attributes(element) # TODO: Remove when support is Python 3.8+
|
||||
|
||||
return element
|
||||
|
||||
def export_to_xml(self, path='geometry.xml', remove_surfs=False):
|
||||
"""Export geometry to an XML file.
|
||||
|
||||
|
|
@ -117,25 +150,7 @@ class Geometry:
|
|||
.. versionadded:: 0.12
|
||||
|
||||
"""
|
||||
# Find and remove redundant surfaces from the geometry
|
||||
if remove_surfs:
|
||||
warnings.warn("remove_surfs kwarg will be deprecated soon, please "
|
||||
"set the Geometry.merge_surfaces attribute instead.")
|
||||
self.merge_surfaces = True
|
||||
|
||||
if self.merge_surfaces:
|
||||
self.remove_redundant_surfaces()
|
||||
|
||||
# Create XML representation
|
||||
root_element = ET.Element("geometry")
|
||||
self.root_universe.create_xml_subelement(root_element, memo=set())
|
||||
|
||||
# Sort the elements in the file
|
||||
root_element[:] = sorted(root_element, key=lambda x: (
|
||||
x.tag, int(x.get('id'))))
|
||||
|
||||
# Clean the indentation in the file to be user-readable
|
||||
xml.clean_indentation(root_element)
|
||||
root_element = self.to_xml_element(remove_surfs)
|
||||
|
||||
# Check if path is a directory
|
||||
p = Path(path)
|
||||
|
|
@ -143,10 +158,101 @@ class Geometry:
|
|||
p /= 'geometry.xml'
|
||||
|
||||
# Write the XML Tree to the geometry.xml file
|
||||
xml.reorder_attributes(root_element) # TODO: Remove when support is Python 3.8+
|
||||
tree = ET.ElementTree(root_element)
|
||||
tree.write(str(p), xml_declaration=True, encoding='utf-8')
|
||||
|
||||
@classmethod
|
||||
def from_xml_element(cls, elem, materials=None):
|
||||
"""Generate geometry from an XML element
|
||||
|
||||
Parameters
|
||||
----------
|
||||
elem : xml.etree.ElementTree.Element
|
||||
XML element
|
||||
materials : openmc.Materials or None
|
||||
Materials used to assign to cells. If None, an attempt is made to
|
||||
generate it from the materials.xml file.
|
||||
|
||||
Returns
|
||||
-------
|
||||
openmc.Geometry
|
||||
Geometry object
|
||||
|
||||
"""
|
||||
mats = dict()
|
||||
if materials is not None:
|
||||
mats.update({str(m.id): m for m in materials})
|
||||
mats['void'] = None
|
||||
|
||||
# Helper function for keeping a cache of Universe instances
|
||||
universes = {}
|
||||
def get_universe(univ_id):
|
||||
if univ_id not in universes:
|
||||
univ = openmc.Universe(univ_id)
|
||||
universes[univ_id] = univ
|
||||
return universes[univ_id]
|
||||
|
||||
# Get surfaces
|
||||
surfaces = {}
|
||||
periodic = {}
|
||||
for surface in elem.findall('surface'):
|
||||
s = openmc.Surface.from_xml_element(surface)
|
||||
surfaces[s.id] = s
|
||||
|
||||
# Check for periodic surface
|
||||
other_id = xml.get_text(surface, 'periodic_surface_id')
|
||||
if other_id is not None:
|
||||
periodic[s.id] = int(other_id)
|
||||
|
||||
# Apply periodic surfaces
|
||||
for s1, s2 in periodic.items():
|
||||
surfaces[s1].periodic_surface = surfaces[s2]
|
||||
|
||||
# Add any DAGMC universes
|
||||
for e in elem.findall('dagmc_universe'):
|
||||
dag_univ = openmc.DAGMCUniverse.from_xml_element(e)
|
||||
universes[dag_univ.id] = dag_univ
|
||||
|
||||
# Dictionary that maps each universe to a list of cells/lattices that
|
||||
# contain it (needed to determine which universe is the elem)
|
||||
child_of = defaultdict(list)
|
||||
|
||||
for e in elem.findall('lattice'):
|
||||
lat = openmc.RectLattice.from_xml_element(e, get_universe)
|
||||
universes[lat.id] = lat
|
||||
if lat.outer is not None:
|
||||
child_of[lat.outer].append(lat)
|
||||
for u in lat.universes.ravel():
|
||||
child_of[u].append(lat)
|
||||
|
||||
for e in elem.findall('hex_lattice'):
|
||||
lat = openmc.HexLattice.from_xml_element(e, get_universe)
|
||||
universes[lat.id] = lat
|
||||
if lat.outer is not None:
|
||||
child_of[lat.outer].append(lat)
|
||||
if lat.ndim == 2:
|
||||
for ring in lat.universes:
|
||||
for u in ring:
|
||||
child_of[u].append(lat)
|
||||
else:
|
||||
for axial_slice in lat.universes:
|
||||
for ring in axial_slice:
|
||||
for u in ring:
|
||||
child_of[u].append(lat)
|
||||
|
||||
for e in elem.findall('cell'):
|
||||
c = openmc.Cell.from_xml_element(e, surfaces, mats, get_universe)
|
||||
if c.fill_type in ('universe', 'lattice'):
|
||||
child_of[c.fill].append(c)
|
||||
|
||||
# Determine which universe is the root by finding one which is not a
|
||||
# child of any other object
|
||||
for u in universes.values():
|
||||
if not child_of[u]:
|
||||
return cls(u)
|
||||
else:
|
||||
raise ValueError('Error determining root universe.')
|
||||
|
||||
@classmethod
|
||||
def from_xml(cls, path='geometry.xml', materials=None):
|
||||
"""Generate geometry from XML file
|
||||
|
|
@ -165,84 +271,15 @@ class Geometry:
|
|||
Geometry object
|
||||
|
||||
"""
|
||||
# Helper function for keeping a cache of Universe instances
|
||||
universes = {}
|
||||
def get_universe(univ_id):
|
||||
if univ_id not in universes:
|
||||
univ = openmc.Universe(univ_id)
|
||||
universes[univ_id] = univ
|
||||
return universes[univ_id]
|
||||
|
||||
tree = ET.parse(path)
|
||||
root = tree.getroot()
|
||||
|
||||
# Get surfaces
|
||||
surfaces = {}
|
||||
periodic = {}
|
||||
for surface in root.findall('surface'):
|
||||
s = openmc.Surface.from_xml_element(surface)
|
||||
surfaces[s.id] = s
|
||||
|
||||
# Check for periodic surface
|
||||
other_id = xml.get_text(surface, 'periodic_surface_id')
|
||||
if other_id is not None:
|
||||
periodic[s.id] = int(other_id)
|
||||
|
||||
# Apply periodic surfaces
|
||||
for s1, s2 in periodic.items():
|
||||
surfaces[s1].periodic_surface = surfaces[s2]
|
||||
|
||||
# Add any DAGMC universes
|
||||
for elem in root.findall('dagmc_universe'):
|
||||
dag_univ = openmc.DAGMCUniverse.from_xml_element(elem)
|
||||
universes[dag_univ.id] = dag_univ
|
||||
|
||||
# Dictionary that maps each universe to a list of cells/lattices that
|
||||
# contain it (needed to determine which universe is the root)
|
||||
child_of = defaultdict(list)
|
||||
|
||||
for elem in root.findall('lattice'):
|
||||
lat = openmc.RectLattice.from_xml_element(elem, get_universe)
|
||||
universes[lat.id] = lat
|
||||
if lat.outer is not None:
|
||||
child_of[lat.outer].append(lat)
|
||||
for u in lat.universes.ravel():
|
||||
child_of[u].append(lat)
|
||||
|
||||
for elem in root.findall('hex_lattice'):
|
||||
lat = openmc.HexLattice.from_xml_element(elem, get_universe)
|
||||
universes[lat.id] = lat
|
||||
if lat.outer is not None:
|
||||
child_of[lat.outer].append(lat)
|
||||
if lat.ndim == 2:
|
||||
for ring in lat.universes:
|
||||
for u in ring:
|
||||
child_of[u].append(lat)
|
||||
else:
|
||||
for axial_slice in lat.universes:
|
||||
for ring in axial_slice:
|
||||
for u in ring:
|
||||
child_of[u].append(lat)
|
||||
|
||||
# Create dictionary to easily look up materials
|
||||
if materials is None:
|
||||
filename = Path(path).parent / 'materials.xml'
|
||||
materials = openmc.Materials.from_xml(str(filename))
|
||||
mats = {str(m.id): m for m in materials}
|
||||
mats['void'] = None
|
||||
|
||||
for elem in root.findall('cell'):
|
||||
c = openmc.Cell.from_xml_element(elem, surfaces, mats, get_universe)
|
||||
if c.fill_type in ('universe', 'lattice'):
|
||||
child_of[c.fill].append(c)
|
||||
tree = ET.parse(path)
|
||||
root = tree.getroot()
|
||||
|
||||
# Determine which universe is the root by finding one which is not a
|
||||
# child of any other object
|
||||
for u in universes.values():
|
||||
if not child_of[u]:
|
||||
return cls(u)
|
||||
else:
|
||||
raise ValueError('Error determining root universe.')
|
||||
return cls.from_xml_element(root, materials)
|
||||
|
||||
def find(self, point):
|
||||
"""Find cells/universes/lattices which contain a given point
|
||||
|
|
|
|||
|
|
@ -1449,6 +1449,57 @@ class Materials(cv.CheckedList):
|
|||
for material in self:
|
||||
material.make_isotropic_in_lab()
|
||||
|
||||
def _write_xml(self, file, header=True, level=0, spaces_per_level=2, trailing_indent=True):
|
||||
"""Writes XML content of the materials to an open file handle.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
file : IOTextWrapper
|
||||
Open file handle to write content into.
|
||||
header : bool
|
||||
Whether or not to write the XML header
|
||||
level : int
|
||||
Indentation level of materials element
|
||||
spaces_per_level : int
|
||||
Number of spaces per indentation
|
||||
trailing_indentation : bool
|
||||
Whether or not to write a trailing indentation for the materials element
|
||||
|
||||
"""
|
||||
indentation = level*spaces_per_level*' '
|
||||
# Write the header and the opening tag for the root element.
|
||||
if header:
|
||||
file.write("<?xml version='1.0' encoding='utf-8'?>\n")
|
||||
file.write(indentation+'<materials>\n')
|
||||
|
||||
# Write the <cross_sections> element.
|
||||
if self.cross_sections is not None:
|
||||
element = ET.Element('cross_sections')
|
||||
element.text = str(self.cross_sections)
|
||||
clean_indentation(element, level=level+1)
|
||||
element.tail = element.tail.strip(' ')
|
||||
file.write((level+1)*spaces_per_level*' ')
|
||||
reorder_attributes(element) # TODO: Remove when support is Python 3.8+
|
||||
ET.ElementTree(element).write(file, encoding='unicode')
|
||||
|
||||
# Write the <material> elements.
|
||||
for material in sorted(self, key=lambda x: x.id):
|
||||
element = material.to_xml_element()
|
||||
clean_indentation(element, level=level+1)
|
||||
element.tail = element.tail.strip(' ')
|
||||
file.write((level+1)*spaces_per_level*' ')
|
||||
reorder_attributes(element) # TODO: Remove when support is Python 3.8+
|
||||
ET.ElementTree(element).write(file, encoding='unicode')
|
||||
|
||||
# Write the closing tag for the root element.
|
||||
file.write(indentation+'</materials>\n')
|
||||
|
||||
# Write a trailing indentation for the next element
|
||||
# at this level if needed
|
||||
if trailing_indent:
|
||||
file.write(indentation)
|
||||
|
||||
|
||||
def export_to_xml(self, path: PathLike = 'materials.xml'):
|
||||
"""Export material collection to an XML file.
|
||||
|
||||
|
|
@ -1468,32 +1519,34 @@ class Materials(cv.CheckedList):
|
|||
# one go.
|
||||
with open(str(p), 'w', encoding='utf-8',
|
||||
errors='xmlcharrefreplace') as fh:
|
||||
self._write_xml(fh)
|
||||
|
||||
# Write the header and the opening tag for the root element.
|
||||
fh.write("<?xml version='1.0' encoding='utf-8'?>\n")
|
||||
fh.write('<materials>\n')
|
||||
@classmethod
|
||||
def from_xml_element(cls, elem):
|
||||
"""Generate materials collection from XML file
|
||||
|
||||
# Write the <cross_sections> element.
|
||||
if self.cross_sections is not None:
|
||||
element = ET.Element('cross_sections')
|
||||
element.text = str(self.cross_sections)
|
||||
clean_indentation(element, level=1)
|
||||
element.tail = element.tail.strip(' ')
|
||||
fh.write(' ')
|
||||
reorder_attributes(element) # TODO: Remove when support is Python 3.8+
|
||||
ET.ElementTree(element).write(fh, encoding='unicode')
|
||||
Parameters
|
||||
----------
|
||||
elem : xml.etree.ElementTree.Element
|
||||
XML element
|
||||
|
||||
# Write the <material> elements.
|
||||
for material in sorted(self, key=lambda x: x.id):
|
||||
element = material.to_xml_element()
|
||||
clean_indentation(element, level=1)
|
||||
element.tail = element.tail.strip(' ')
|
||||
fh.write(' ')
|
||||
reorder_attributes(element) # TODO: Remove when support is Python 3.8+
|
||||
ET.ElementTree(element).write(fh, encoding='unicode')
|
||||
Returns
|
||||
-------
|
||||
openmc.Materials
|
||||
Materials collection
|
||||
|
||||
# Write the closing tag for the root element.
|
||||
fh.write('</materials>\n')
|
||||
"""
|
||||
# Generate each material
|
||||
materials = cls()
|
||||
for material in elem.findall('material'):
|
||||
materials.append(Material.from_xml_element(material))
|
||||
|
||||
# Check for cross sections settings
|
||||
xs = elem.find('cross_sections')
|
||||
if xs is not None:
|
||||
materials.cross_sections = xs.text
|
||||
|
||||
return materials
|
||||
|
||||
@classmethod
|
||||
def from_xml(cls, path: PathLike = 'materials.xml'):
|
||||
|
|
@ -1513,14 +1566,4 @@ class Materials(cv.CheckedList):
|
|||
tree = ET.parse(path)
|
||||
root = tree.getroot()
|
||||
|
||||
# Generate each material
|
||||
materials = cls()
|
||||
for material in root.findall('material'):
|
||||
materials.append(Material.from_xml_element(material))
|
||||
|
||||
# Check for cross sections settings
|
||||
xs = tree.find('cross_sections')
|
||||
if xs is not None:
|
||||
materials.cross_sections = xs.text
|
||||
|
||||
return materials
|
||||
return cls.from_xml_element(root)
|
||||
|
|
@ -6,10 +6,12 @@ from pathlib import Path
|
|||
from numbers import Integral
|
||||
from tempfile import NamedTemporaryFile
|
||||
import warnings
|
||||
from xml.etree import ElementTree as ET
|
||||
|
||||
import h5py
|
||||
|
||||
import openmc
|
||||
import openmc._xml as xml
|
||||
from openmc.dummy_comm import DummyCommunicator
|
||||
from openmc.executor import _process_CLI_arguments
|
||||
from openmc.checkvalue import check_type, check_value
|
||||
|
|
@ -238,6 +240,35 @@ class Model:
|
|||
plots = openmc.Plots.from_xml(plots) if Path(plots).exists() else None
|
||||
return cls(geometry, materials, settings, tallies, plots)
|
||||
|
||||
@classmethod
|
||||
def from_model_xml(cls, path='model.xml'):
|
||||
"""Create model from single XML file
|
||||
|
||||
.. vesionadded:: 0.13.3
|
||||
|
||||
Parameters
|
||||
----------
|
||||
path : str or Pathlike
|
||||
Path to model.xml file
|
||||
"""
|
||||
tree = ET.parse(path)
|
||||
root = tree.getroot()
|
||||
|
||||
model = cls()
|
||||
|
||||
meshes = {}
|
||||
model.settings = openmc.Settings.from_xml_element(root.find('settings'), meshes)
|
||||
model.materials = openmc.Materials.from_xml_element(root.find('materials'))
|
||||
model.geometry = openmc.Geometry.from_xml_element(root.find('geometry'), model.materials)
|
||||
|
||||
if root.find('tallies'):
|
||||
model.tallies = openmc.Tallies.from_xml_element(root.find('tallies'), meshes)
|
||||
|
||||
if root.find('plots'):
|
||||
model.plots = openmc.Plots.from_xml_element(root.find('plots'))
|
||||
|
||||
return model
|
||||
|
||||
def init_lib(self, threads=None, geometry_debug=False, restart_file=None,
|
||||
tracks=False, output=True, event_based=None, intracomm=None):
|
||||
"""Initializes the model in memory via the C API
|
||||
|
|
@ -399,7 +430,7 @@ class Model:
|
|||
depletion_operator.finalize()
|
||||
|
||||
def export_to_xml(self, directory='.', remove_surfs=False):
|
||||
"""Export model to XML files.
|
||||
"""Export model to separate XML files.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
|
|
@ -418,14 +449,7 @@ class Model:
|
|||
d.mkdir(parents=True)
|
||||
|
||||
self.settings.export_to_xml(d)
|
||||
if remove_surfs:
|
||||
warnings.warn("remove_surfs kwarg will be deprecated soon, please "
|
||||
"set the Geometry.merge_surfaces attribute instead.")
|
||||
self.geometry.merge_surfaces = True
|
||||
# Can be used to modify tallies in case any surfaces are redundant
|
||||
redundant_surfaces = self.geometry.remove_redundant_surfaces()
|
||||
|
||||
self.geometry.export_to_xml(d)
|
||||
self.geometry.export_to_xml(d, remove_surfs=remove_surfs)
|
||||
|
||||
# If a materials collection was specified, export it. Otherwise, look
|
||||
# for all materials in the geometry and use that to automatically build
|
||||
|
|
@ -442,6 +466,78 @@ class Model:
|
|||
if self.plots:
|
||||
self.plots.export_to_xml(d)
|
||||
|
||||
def export_to_model_xml(self, path='model.xml', remove_surfs=False):
|
||||
"""Export model to a single XML file.
|
||||
|
||||
.. versionadded:: 0.13.3
|
||||
|
||||
Parameters
|
||||
----------
|
||||
path : str or Pathlike
|
||||
Location of the XML file to write (default is 'model.xml'). Can be a
|
||||
directory or file path.
|
||||
remove_surfs : bool
|
||||
Whether or not to remove redundant surfaces from the geometry when
|
||||
exporting.
|
||||
|
||||
"""
|
||||
xml_path = Path(path)
|
||||
# if the provided path doesn't end with the XML extension, assume the
|
||||
# input path is meant to be a directory. If the directory does not
|
||||
# exist, create it and place a 'model.xml' file there.
|
||||
if not str(xml_path).endswith('.xml') and not xml_path.exists():
|
||||
os.mkdir(xml_path)
|
||||
xml_path /= 'model.xml'
|
||||
# if this is an XML file location and the file's parent directory does
|
||||
# not exist, create it before continuing
|
||||
elif not xml_path.parent.exists():
|
||||
os.mkdir(xml_path.parent)
|
||||
|
||||
if remove_surfs:
|
||||
warnings.warn("remove_surfs kwarg will be deprecated soon, please "
|
||||
"set the Geometry.merge_surfaces attribute instead.")
|
||||
self.geometry.merge_surfaces = True
|
||||
# Can be used to modify tallies in case any surfaces are redundant
|
||||
redundant_surfaces = self.geometry.remove_redundant_surfaces()
|
||||
|
||||
# provide a memo to track which meshes have been written
|
||||
mesh_memo = set()
|
||||
settings_element = self.settings.to_xml_element(mesh_memo)
|
||||
geometry_element = self.geometry.to_xml_element()
|
||||
|
||||
xml.clean_indentation(geometry_element, level=1)
|
||||
xml.clean_indentation(settings_element, level=1)
|
||||
|
||||
# If a materials collection was specified, export it. Otherwise, look
|
||||
# for all materials in the geometry and use that to automatically build
|
||||
# a collection.
|
||||
if self.materials:
|
||||
materials = self.materials
|
||||
else:
|
||||
materials = openmc.Materials(self.geometry.get_all_materials()
|
||||
.values())
|
||||
|
||||
with open(xml_path, 'w', encoding='utf-8', errors='xmlcharrefreplace') as fh:
|
||||
# write the XML header
|
||||
fh.write("<?xml version='1.0' encoding='utf-8'?>\n")
|
||||
fh.write("<model>\n")
|
||||
# Write the materials collection to the open XML file first.
|
||||
# This will write the XML header also
|
||||
materials._write_xml(fh, False, level=1)
|
||||
# Write remaining elements as a tree
|
||||
ET.ElementTree(geometry_element).write(fh, encoding='unicode')
|
||||
ET.ElementTree(settings_element).write(fh, encoding='unicode')
|
||||
|
||||
if self.tallies:
|
||||
tallies_element = self.tallies.to_xml_element(mesh_memo)
|
||||
xml.clean_indentation(tallies_element, level=1, trailing_indent=self.plots)
|
||||
ET.ElementTree(tallies_element).write(fh, encoding='unicode')
|
||||
if self.plots:
|
||||
plots_element = self.plots.to_xml_element()
|
||||
xml.clean_indentation(plots_element, level=1, trailing_indent=False)
|
||||
ET.ElementTree(plots_element).write(fh, encoding='unicode')
|
||||
fh.write("</model>\n")
|
||||
|
||||
def import_properties(self, filename):
|
||||
"""Import physical properties
|
||||
|
||||
|
|
|
|||
|
|
@ -909,13 +909,13 @@ class Plots(cv.CheckedList):
|
|||
|
||||
self._plots_file.append(xml_element)
|
||||
|
||||
def export_to_xml(self, path='plots.xml'):
|
||||
"""Export plot specifications to an XML file.
|
||||
def to_xml_element(self):
|
||||
"""Create a 'plots' element to be written to an XML file.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
path : str
|
||||
Path to file to write. Defaults to 'plots.xml'.
|
||||
Returns
|
||||
-------
|
||||
element : xml.etree.ElementTree.Element
|
||||
XML element containing all plot elements
|
||||
|
||||
"""
|
||||
# Reset xml element tree
|
||||
|
|
@ -925,17 +925,50 @@ class Plots(cv.CheckedList):
|
|||
|
||||
# Clean the indentation in the file to be user-readable
|
||||
clean_indentation(self._plots_file)
|
||||
reorder_attributes(self._plots_file) # TODO: Remove when support is Python 3.8+
|
||||
|
||||
return self._plots_file
|
||||
|
||||
def export_to_xml(self, path='plots.xml'):
|
||||
"""Export plot specifications to an XML file.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
path : str
|
||||
Path to file to write. Defaults to 'plots.xml'.
|
||||
|
||||
"""
|
||||
# Check if path is a directory
|
||||
p = Path(path)
|
||||
if p.is_dir():
|
||||
p /= 'plots.xml'
|
||||
|
||||
self.to_xml_element()
|
||||
# Write the XML Tree to the plots.xml file
|
||||
reorder_attributes(self._plots_file) # TODO: Remove when support is Python 3.8+
|
||||
tree = ET.ElementTree(self._plots_file)
|
||||
tree.write(str(p), xml_declaration=True, encoding='utf-8')
|
||||
|
||||
@classmethod
|
||||
def from_xml_element(cls, elem):
|
||||
"""Generate plots collection from XML file
|
||||
|
||||
Parameters
|
||||
----------
|
||||
elem : xml.etree.ElementTree.Element
|
||||
XML element
|
||||
|
||||
Returns
|
||||
-------
|
||||
openmc.Plots
|
||||
Plots collection
|
||||
|
||||
"""
|
||||
# Generate each plot
|
||||
plots = cls()
|
||||
for e in elem.findall('plot'):
|
||||
plots.append(Plot.from_xml_element(e))
|
||||
return plots
|
||||
|
||||
@classmethod
|
||||
def from_xml(cls, path='plots.xml'):
|
||||
"""Generate plots collection from XML file
|
||||
|
|
@ -953,9 +986,6 @@ class Plots(cv.CheckedList):
|
|||
"""
|
||||
tree = ET.parse(path)
|
||||
root = tree.getroot()
|
||||
return cls.from_xml_element(root)
|
||||
|
||||
|
||||
# Generate each plot
|
||||
plots = cls()
|
||||
for elem in root.findall('plot'):
|
||||
plots.append(Plot.from_xml_element(elem))
|
||||
return plots
|
||||
|
|
|
|||
|
|
@ -1087,25 +1087,35 @@ class Settings:
|
|||
subelement = ET.SubElement(element, key)
|
||||
subelement.text = str(value)
|
||||
|
||||
def _create_entropy_mesh_subelement(self, root):
|
||||
if self.entropy_mesh is not None:
|
||||
# use default heuristic for entropy mesh if not set by user
|
||||
if self.entropy_mesh.dimension is None:
|
||||
if self.particles is None:
|
||||
raise RuntimeError("Number of particles must be set in order to " \
|
||||
"use entropy mesh dimension heuristic")
|
||||
else:
|
||||
n = ceil((self.particles / 20.0)**(1.0 / 3.0))
|
||||
d = len(self.entropy_mesh.lower_left)
|
||||
self.entropy_mesh.dimension = (n,)*d
|
||||
def _create_entropy_mesh_subelement(self, root, mesh_memo=None):
|
||||
if self.entropy_mesh is None:
|
||||
return
|
||||
|
||||
# See if a <mesh> element already exists -- if not, add it
|
||||
path = f"./mesh[@id='{self.entropy_mesh.id}']"
|
||||
if root.find(path) is None:
|
||||
root.append(self.entropy_mesh.to_xml_element())
|
||||
# use default heuristic for entropy mesh if not set by user
|
||||
if self.entropy_mesh.dimension is None:
|
||||
if self.particles is None:
|
||||
raise RuntimeError("Number of particles must be set in order to " \
|
||||
"use entropy mesh dimension heuristic")
|
||||
else:
|
||||
n = ceil((self.particles / 20.0)**(1.0 / 3.0))
|
||||
d = len(self.entropy_mesh.lower_left)
|
||||
self.entropy_mesh.dimension = (n,)*d
|
||||
|
||||
subelement = ET.SubElement(root, "entropy_mesh")
|
||||
subelement.text = str(self.entropy_mesh.id)
|
||||
# add mesh ID to this element
|
||||
subelement = ET.SubElement(root, "entropy_mesh")
|
||||
subelement.text = str(self.entropy_mesh.id)
|
||||
|
||||
# If this mesh has already been written outside the
|
||||
# settings element, skip writing it again
|
||||
if mesh_memo and self.entropy_mesh.id in mesh_memo:
|
||||
return
|
||||
|
||||
# See if a <mesh> element already exists -- if not, add it
|
||||
path = f"./mesh[@id='{self.entropy_mesh.id}']"
|
||||
if root.find(path) is None:
|
||||
root.append(self.entropy_mesh.to_xml_element())
|
||||
if mesh_memo is not None:
|
||||
mesh_memo.add(self.entropy_mesh.id)
|
||||
|
||||
def _create_trigger_subelement(self, root):
|
||||
if self._trigger_active is not None:
|
||||
|
|
@ -1156,15 +1166,21 @@ class Settings:
|
|||
element = ET.SubElement(root, "track")
|
||||
element.text = ' '.join(map(str, itertools.chain(*self._track)))
|
||||
|
||||
def _create_ufs_mesh_subelement(self, root):
|
||||
if self.ufs_mesh is not None:
|
||||
# See if a <mesh> element already exists -- if not, add it
|
||||
path = f"./mesh[@id='{self.ufs_mesh.id}']"
|
||||
if root.find(path) is None:
|
||||
root.append(self.ufs_mesh.to_xml_element())
|
||||
def _create_ufs_mesh_subelement(self, root, mesh_memo=None):
|
||||
if self.ufs_mesh is None:
|
||||
return
|
||||
|
||||
subelement = ET.SubElement(root, "ufs_mesh")
|
||||
subelement.text = str(self.ufs_mesh.id)
|
||||
subelement = ET.SubElement(root, "ufs_mesh")
|
||||
subelement.text = str(self.ufs_mesh.id)
|
||||
|
||||
if mesh_memo and self.ufs_mesh.id in mesh_memo:
|
||||
return
|
||||
|
||||
# See if a <mesh> element already exists -- if not, add it
|
||||
path = f"./mesh[@id='{self.ufs_mesh.id}']"
|
||||
if root.find(path) is None:
|
||||
root.append(self.ufs_mesh.to_xml_element())
|
||||
if mesh_memo is not None: mesh_memo.add(self.ufs_mesh.id)
|
||||
|
||||
def _create_resonance_scattering_subelement(self, root):
|
||||
res = self.resonance_scattering
|
||||
|
|
@ -1221,15 +1237,21 @@ class Settings:
|
|||
elem = ET.SubElement(root, "write_initial_source")
|
||||
elem.text = str(self._write_initial_source).lower()
|
||||
|
||||
def _create_weight_windows_subelement(self, root):
|
||||
def _create_weight_windows_subelement(self, root, mesh_memo=None):
|
||||
for ww in self._weight_windows:
|
||||
# Add weight window information
|
||||
root.append(ww.to_xml_element())
|
||||
|
||||
# if this mesh has already been written,
|
||||
# skip writing the mesh element
|
||||
if mesh_memo and ww.mesh.id in mesh_memo:
|
||||
continue
|
||||
|
||||
# See if a <mesh> element already exists -- if not, add it
|
||||
path = f"./mesh[@id='{ww.mesh.id}']"
|
||||
if root.find(path) is None:
|
||||
root.append(ww.mesh.to_xml_element())
|
||||
if mesh_memo is not None: mesh_memo.add(ww.mesh.id)
|
||||
|
||||
if self._weight_windows_on is not None:
|
||||
elem = ET.SubElement(root, "weight_windows_on")
|
||||
|
|
@ -1412,13 +1434,15 @@ class Settings:
|
|||
if value is not None:
|
||||
self.cutoff[key] = float(value)
|
||||
|
||||
def _entropy_mesh_from_xml_element(self, root):
|
||||
def _entropy_mesh_from_xml_element(self, root, meshes=None):
|
||||
text = get_text(root, 'entropy_mesh')
|
||||
if text is not None:
|
||||
path = f"./mesh[@id='{int(text)}']"
|
||||
elem = root.find(path)
|
||||
if elem is not None:
|
||||
self.entropy_mesh = RegularMesh.from_xml_element(elem)
|
||||
if meshes is not None and self.entropy_mesh is not None:
|
||||
meshes[self.entropy_mesh.id] = self.entropy_mesh
|
||||
|
||||
def _trigger_from_xml_element(self, root):
|
||||
elem = root.find('trigger')
|
||||
|
|
@ -1478,13 +1502,15 @@ class Settings:
|
|||
values = [int(x) for x in text.split()]
|
||||
self.track = list(zip(values[::3], values[1::3], values[2::3]))
|
||||
|
||||
def _ufs_mesh_from_xml_element(self, root):
|
||||
def _ufs_mesh_from_xml_element(self, root, meshes=None):
|
||||
text = get_text(root, 'ufs_mesh')
|
||||
if text is not None:
|
||||
path = f"./mesh[@id='{int(text)}']"
|
||||
elem = root.find(path)
|
||||
if elem is not None:
|
||||
self.ufs_mesh = RegularMesh.from_xml_element(elem)
|
||||
if meshes is not None and self.ufs_mesh is not None:
|
||||
meshes[self.ufs_mesh.id] = self.ufs_mesh
|
||||
|
||||
def _resonance_scattering_from_xml_element(self, root):
|
||||
elem = root.find('resonance_scattering')
|
||||
|
|
@ -1536,7 +1562,7 @@ class Settings:
|
|||
if text is not None:
|
||||
self.write_initial_source = text in ('true', '1')
|
||||
|
||||
def _weight_windows_from_xml_element(self, root):
|
||||
def _weight_windows_from_xml_element(self, root, meshes=None):
|
||||
for elem in root.findall('weight_windows'):
|
||||
ww = WeightWindows.from_xml_element(elem, root)
|
||||
self.weight_windows.append(ww)
|
||||
|
|
@ -1545,6 +1571,9 @@ class Settings:
|
|||
if text is not None:
|
||||
self.weight_windows_on = text in ('true', '1')
|
||||
|
||||
if meshes is not None and self.weight_windows:
|
||||
meshes.update({ww.mesh.id: ww.mesh for ww in self.weight_windows})
|
||||
|
||||
def _max_splits_from_xml_element(self, root):
|
||||
text = get_text(root, 'max_splits')
|
||||
if text is not None:
|
||||
|
|
@ -1555,6 +1584,68 @@ class Settings:
|
|||
if text is not None:
|
||||
self.max_tracks = int(text)
|
||||
|
||||
def to_xml_element(self, mesh_memo=None):
|
||||
"""Create a 'settings' element to be written to an XML file.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
mesh_memo : set of ints
|
||||
A set of mesh IDs to keep track of whether a mesh has already been written.
|
||||
"""
|
||||
# Reset xml element tree
|
||||
element = ET.Element("settings")
|
||||
|
||||
self._create_run_mode_subelement(element)
|
||||
self._create_particles_subelement(element)
|
||||
self._create_batches_subelement(element)
|
||||
self._create_inactive_subelement(element)
|
||||
self._create_max_lost_particles_subelement(element)
|
||||
self._create_rel_max_lost_particles_subelement(element)
|
||||
self._create_generations_per_batch_subelement(element)
|
||||
self._create_keff_trigger_subelement(element)
|
||||
self._create_source_subelement(element)
|
||||
self._create_output_subelement(element)
|
||||
self._create_statepoint_subelement(element)
|
||||
self._create_sourcepoint_subelement(element)
|
||||
self._create_surf_source_read_subelement(element)
|
||||
self._create_surf_source_write_subelement(element)
|
||||
self._create_confidence_intervals(element)
|
||||
self._create_electron_treatment_subelement(element)
|
||||
self._create_energy_mode_subelement(element)
|
||||
self._create_max_order_subelement(element)
|
||||
self._create_photon_transport_subelement(element)
|
||||
self._create_ptables_subelement(element)
|
||||
self._create_seed_subelement(element)
|
||||
self._create_survival_biasing_subelement(element)
|
||||
self._create_cutoff_subelement(element)
|
||||
self._create_entropy_mesh_subelement(element, mesh_memo)
|
||||
self._create_trigger_subelement(element)
|
||||
self._create_no_reduce_subelement(element)
|
||||
self._create_verbosity_subelement(element)
|
||||
self._create_tabular_legendre_subelements(element)
|
||||
self._create_temperature_subelements(element)
|
||||
self._create_trace_subelement(element)
|
||||
self._create_track_subelement(element)
|
||||
self._create_ufs_mesh_subelement(element, mesh_memo)
|
||||
self._create_resonance_scattering_subelement(element)
|
||||
self._create_volume_calcs_subelement(element)
|
||||
self._create_create_fission_neutrons_subelement(element)
|
||||
self._create_delayed_photon_scaling_subelement(element)
|
||||
self._create_event_based_subelement(element)
|
||||
self._create_max_particles_in_flight_subelement(element)
|
||||
self._create_material_cell_offsets_subelement(element)
|
||||
self._create_log_grid_bins_subelement(element)
|
||||
self._create_write_initial_source_subelement(element)
|
||||
self._create_weight_windows_subelement(element, mesh_memo)
|
||||
self._create_max_splits_subelement(element)
|
||||
self._create_max_tracks_subelement(element)
|
||||
|
||||
# Clean the indentation in the file to be user-readable
|
||||
clean_indentation(element)
|
||||
reorder_attributes(element) # TODO: Remove when support is Python 3.8+
|
||||
|
||||
return element
|
||||
|
||||
def export_to_xml(self, path: PathLike = 'settings.xml'):
|
||||
"""Export simulation settings to an XML file.
|
||||
|
||||
|
|
@ -1564,57 +1655,7 @@ class Settings:
|
|||
Path to file to write. Defaults to 'settings.xml'.
|
||||
|
||||
"""
|
||||
|
||||
# Reset xml element tree
|
||||
root_element = ET.Element("settings")
|
||||
|
||||
self._create_run_mode_subelement(root_element)
|
||||
self._create_particles_subelement(root_element)
|
||||
self._create_batches_subelement(root_element)
|
||||
self._create_inactive_subelement(root_element)
|
||||
self._create_max_lost_particles_subelement(root_element)
|
||||
self._create_rel_max_lost_particles_subelement(root_element)
|
||||
self._create_generations_per_batch_subelement(root_element)
|
||||
self._create_keff_trigger_subelement(root_element)
|
||||
self._create_source_subelement(root_element)
|
||||
self._create_output_subelement(root_element)
|
||||
self._create_statepoint_subelement(root_element)
|
||||
self._create_sourcepoint_subelement(root_element)
|
||||
self._create_surf_source_read_subelement(root_element)
|
||||
self._create_surf_source_write_subelement(root_element)
|
||||
self._create_confidence_intervals(root_element)
|
||||
self._create_electron_treatment_subelement(root_element)
|
||||
self._create_energy_mode_subelement(root_element)
|
||||
self._create_max_order_subelement(root_element)
|
||||
self._create_photon_transport_subelement(root_element)
|
||||
self._create_ptables_subelement(root_element)
|
||||
self._create_seed_subelement(root_element)
|
||||
self._create_survival_biasing_subelement(root_element)
|
||||
self._create_cutoff_subelement(root_element)
|
||||
self._create_entropy_mesh_subelement(root_element)
|
||||
self._create_trigger_subelement(root_element)
|
||||
self._create_no_reduce_subelement(root_element)
|
||||
self._create_verbosity_subelement(root_element)
|
||||
self._create_tabular_legendre_subelements(root_element)
|
||||
self._create_temperature_subelements(root_element)
|
||||
self._create_trace_subelement(root_element)
|
||||
self._create_track_subelement(root_element)
|
||||
self._create_ufs_mesh_subelement(root_element)
|
||||
self._create_resonance_scattering_subelement(root_element)
|
||||
self._create_volume_calcs_subelement(root_element)
|
||||
self._create_create_fission_neutrons_subelement(root_element)
|
||||
self._create_delayed_photon_scaling_subelement(root_element)
|
||||
self._create_event_based_subelement(root_element)
|
||||
self._create_max_particles_in_flight_subelement(root_element)
|
||||
self._create_material_cell_offsets_subelement(root_element)
|
||||
self._create_log_grid_bins_subelement(root_element)
|
||||
self._create_write_initial_source_subelement(root_element)
|
||||
self._create_weight_windows_subelement(root_element)
|
||||
self._create_max_splits_subelement(root_element)
|
||||
self._create_max_tracks_subelement(root_element)
|
||||
|
||||
# Clean the indentation in the file to be user-readable
|
||||
clean_indentation(root_element)
|
||||
root_element = self.to_xml_element()
|
||||
|
||||
# Check if path is a directory
|
||||
p = Path(path)
|
||||
|
|
@ -1622,10 +1663,78 @@ class Settings:
|
|||
p /= 'settings.xml'
|
||||
|
||||
# Write the XML Tree to the settings.xml file
|
||||
reorder_attributes(root_element) # TODO: Remove when support is Python 3.8+
|
||||
tree = ET.ElementTree(root_element)
|
||||
tree.write(str(p), xml_declaration=True, encoding='utf-8')
|
||||
|
||||
@classmethod
|
||||
def from_xml_element(cls, elem, meshes=None):
|
||||
"""Generate settings from XML element
|
||||
|
||||
Parameters
|
||||
----------
|
||||
elem : xml.etree.ElementTree.Element
|
||||
XML element
|
||||
meshes : dict or None
|
||||
A dictionary with mesh IDs as keys and mesh instances as values that
|
||||
have already been read from XML. Pre-existing meshes are used
|
||||
and new meshes are added to when creating tally objects.
|
||||
|
||||
Returns
|
||||
-------
|
||||
openmc.Settings
|
||||
Settings object
|
||||
|
||||
"""
|
||||
settings = cls()
|
||||
settings._eigenvalue_from_xml_element(elem)
|
||||
settings._run_mode_from_xml_element(elem)
|
||||
settings._particles_from_xml_element(elem)
|
||||
settings._batches_from_xml_element(elem)
|
||||
settings._inactive_from_xml_element(elem)
|
||||
settings._max_lost_particles_from_xml_element(elem)
|
||||
settings._rel_max_lost_particles_from_xml_element(elem)
|
||||
settings._generations_per_batch_from_xml_element(elem)
|
||||
settings._keff_trigger_from_xml_element(elem)
|
||||
settings._source_from_xml_element(elem)
|
||||
settings._volume_calcs_from_xml_element(elem)
|
||||
settings._output_from_xml_element(elem)
|
||||
settings._statepoint_from_xml_element(elem)
|
||||
settings._sourcepoint_from_xml_element(elem)
|
||||
settings._surf_source_read_from_xml_element(elem)
|
||||
settings._surf_source_write_from_xml_element(elem)
|
||||
settings._confidence_intervals_from_xml_element(elem)
|
||||
settings._electron_treatment_from_xml_element(elem)
|
||||
settings._energy_mode_from_xml_element(elem)
|
||||
settings._max_order_from_xml_element(elem)
|
||||
settings._photon_transport_from_xml_element(elem)
|
||||
settings._ptables_from_xml_element(elem)
|
||||
settings._seed_from_xml_element(elem)
|
||||
settings._survival_biasing_from_xml_element(elem)
|
||||
settings._cutoff_from_xml_element(elem)
|
||||
settings._entropy_mesh_from_xml_element(elem, meshes)
|
||||
settings._trigger_from_xml_element(elem)
|
||||
settings._no_reduce_from_xml_element(elem)
|
||||
settings._verbosity_from_xml_element(elem)
|
||||
settings._tabular_legendre_from_xml_element(elem)
|
||||
settings._temperature_from_xml_element(elem)
|
||||
settings._trace_from_xml_element(elem)
|
||||
settings._track_from_xml_element(elem)
|
||||
settings._ufs_mesh_from_xml_element(elem, meshes)
|
||||
settings._resonance_scattering_from_xml_element(elem)
|
||||
settings._create_fission_neutrons_from_xml_element(elem)
|
||||
settings._delayed_photon_scaling_from_xml_element(elem)
|
||||
settings._event_based_from_xml_element(elem)
|
||||
settings._max_particles_in_flight_from_xml_element(elem)
|
||||
settings._material_cell_offsets_from_xml_element(elem)
|
||||
settings._log_grid_bins_from_xml_element(elem)
|
||||
settings._write_initial_source_from_xml_element(elem)
|
||||
settings._weight_windows_from_xml_element(elem, meshes)
|
||||
settings._max_splits_from_xml_element(elem)
|
||||
settings._max_tracks_from_xml_element(elem)
|
||||
|
||||
# TODO: Get volume calculations
|
||||
return settings
|
||||
|
||||
@classmethod
|
||||
def from_xml(cls, path: PathLike = 'settings.xml'):
|
||||
"""Generate settings from XML file
|
||||
|
|
@ -1645,54 +1754,4 @@ class Settings:
|
|||
"""
|
||||
tree = ET.parse(path)
|
||||
root = tree.getroot()
|
||||
|
||||
settings = cls()
|
||||
settings._eigenvalue_from_xml_element(root)
|
||||
settings._run_mode_from_xml_element(root)
|
||||
settings._particles_from_xml_element(root)
|
||||
settings._batches_from_xml_element(root)
|
||||
settings._inactive_from_xml_element(root)
|
||||
settings._max_lost_particles_from_xml_element(root)
|
||||
settings._rel_max_lost_particles_from_xml_element(root)
|
||||
settings._generations_per_batch_from_xml_element(root)
|
||||
settings._keff_trigger_from_xml_element(root)
|
||||
settings._source_from_xml_element(root)
|
||||
settings._volume_calcs_from_xml_element(root)
|
||||
settings._output_from_xml_element(root)
|
||||
settings._statepoint_from_xml_element(root)
|
||||
settings._sourcepoint_from_xml_element(root)
|
||||
settings._surf_source_read_from_xml_element(root)
|
||||
settings._surf_source_write_from_xml_element(root)
|
||||
settings._confidence_intervals_from_xml_element(root)
|
||||
settings._electron_treatment_from_xml_element(root)
|
||||
settings._energy_mode_from_xml_element(root)
|
||||
settings._max_order_from_xml_element(root)
|
||||
settings._photon_transport_from_xml_element(root)
|
||||
settings._ptables_from_xml_element(root)
|
||||
settings._seed_from_xml_element(root)
|
||||
settings._survival_biasing_from_xml_element(root)
|
||||
settings._cutoff_from_xml_element(root)
|
||||
settings._entropy_mesh_from_xml_element(root)
|
||||
settings._trigger_from_xml_element(root)
|
||||
settings._no_reduce_from_xml_element(root)
|
||||
settings._verbosity_from_xml_element(root)
|
||||
settings._tabular_legendre_from_xml_element(root)
|
||||
settings._temperature_from_xml_element(root)
|
||||
settings._trace_from_xml_element(root)
|
||||
settings._track_from_xml_element(root)
|
||||
settings._ufs_mesh_from_xml_element(root)
|
||||
settings._resonance_scattering_from_xml_element(root)
|
||||
settings._create_fission_neutrons_from_xml_element(root)
|
||||
settings._delayed_photon_scaling_from_xml_element(root)
|
||||
settings._event_based_from_xml_element(root)
|
||||
settings._max_particles_in_flight_from_xml_element(root)
|
||||
settings._material_cell_offsets_from_xml_element(root)
|
||||
settings._log_grid_bins_from_xml_element(root)
|
||||
settings._write_initial_source_from_xml_element(root)
|
||||
settings._weight_windows_from_xml_element(root)
|
||||
settings._max_splits_from_xml_element(root)
|
||||
settings._max_tracks_from_xml_element(root)
|
||||
|
||||
# TODO: Get volume calculations
|
||||
|
||||
return settings
|
||||
return cls.from_xml_element(root)
|
||||
|
|
|
|||
|
|
@ -3119,17 +3119,17 @@ class Tallies(cv.CheckedList):
|
|||
for tally in self:
|
||||
root_element.append(tally.to_xml_element())
|
||||
|
||||
def _create_mesh_subelements(self, root_element):
|
||||
already_written = set()
|
||||
def _create_mesh_subelements(self, root_element, memo=None):
|
||||
already_written = memo if memo else set()
|
||||
for tally in self:
|
||||
for f in tally.filters:
|
||||
if isinstance(f, openmc.MeshFilter):
|
||||
if f.mesh.id not in already_written:
|
||||
if len(f.mesh.name) > 0:
|
||||
root_element.append(ET.Comment(f.mesh.name))
|
||||
|
||||
root_element.append(f.mesh.to_xml_element())
|
||||
already_written.add(f.mesh.id)
|
||||
if f.mesh.id in already_written:
|
||||
continue
|
||||
if len(f.mesh.name) > 0:
|
||||
root_element.append(ET.Comment(f.mesh.name))
|
||||
root_element.append(f.mesh.to_xml_element())
|
||||
already_written.add(f.mesh.id)
|
||||
|
||||
def _create_filter_subelements(self, root_element):
|
||||
already_written = dict()
|
||||
|
|
@ -3155,6 +3155,22 @@ class Tallies(cv.CheckedList):
|
|||
for d in derivs:
|
||||
root_element.append(d.to_xml_element())
|
||||
|
||||
def to_xml_element(self, memo=None):
|
||||
"""Creates a 'tallies' element to be written to an XML file.
|
||||
"""
|
||||
element = ET.Element("tallies")
|
||||
self._create_mesh_subelements(element, memo)
|
||||
self._create_filter_subelements(element)
|
||||
self._create_tally_subelements(element)
|
||||
self._create_derivative_subelements(element)
|
||||
|
||||
# Clean the indentation in the file to be user-readable
|
||||
clean_indentation(element)
|
||||
reorder_attributes(element) # TODO: Remove when support is Python 3.8+
|
||||
|
||||
return element
|
||||
|
||||
|
||||
def export_to_xml(self, path='tallies.xml'):
|
||||
"""Create a tallies.xml file that can be used for a simulation.
|
||||
|
||||
|
|
@ -3164,15 +3180,7 @@ class Tallies(cv.CheckedList):
|
|||
Path to file to write. Defaults to 'tallies.xml'.
|
||||
|
||||
"""
|
||||
|
||||
root_element = ET.Element("tallies")
|
||||
self._create_mesh_subelements(root_element)
|
||||
self._create_filter_subelements(root_element)
|
||||
self._create_tally_subelements(root_element)
|
||||
self._create_derivative_subelements(root_element)
|
||||
|
||||
# Clean the indentation in the file to be user-readable
|
||||
clean_indentation(root_element)
|
||||
root_element = self.to_xml_element()
|
||||
|
||||
# Check if path is a directory
|
||||
p = Path(path)
|
||||
|
|
@ -3180,10 +3188,56 @@ class Tallies(cv.CheckedList):
|
|||
p /= 'tallies.xml'
|
||||
|
||||
# Write the XML Tree to the tallies.xml file
|
||||
reorder_attributes(root_element) # TODO: Remove when support is Python 3.8+
|
||||
tree = ET.ElementTree(root_element)
|
||||
tree.write(str(p), xml_declaration=True, encoding='utf-8')
|
||||
|
||||
@classmethod
|
||||
def from_xml_element(cls, elem, meshes=None):
|
||||
"""Generate tallies from an XML element
|
||||
|
||||
Parameters
|
||||
----------
|
||||
elem : xml.etree.ElementTree.Element
|
||||
XML element
|
||||
meshes : dict or None
|
||||
A dictionary with mesh IDs as keys and mesh instances as values that
|
||||
have already been read from XML. Pre-existing meshes are used
|
||||
and new meshes are added to when creating tally objects.
|
||||
|
||||
Returns
|
||||
-------
|
||||
openmc.Tallies
|
||||
Tallies object
|
||||
|
||||
"""
|
||||
# Read mesh elements
|
||||
meshes = {} if meshes is None else meshes
|
||||
for e in elem.findall('mesh'):
|
||||
mesh = MeshBase.from_xml_element(e)
|
||||
meshes[mesh.id] = mesh
|
||||
|
||||
# Read filter elements
|
||||
filters = {}
|
||||
for e in elem.findall('filter'):
|
||||
filter = openmc.Filter.from_xml_element(e, meshes=meshes)
|
||||
filters[filter.id] = filter
|
||||
|
||||
# Read derivative elements
|
||||
derivatives = {}
|
||||
for e in elem.findall('derivative'):
|
||||
deriv = openmc.TallyDerivative.from_xml_element(e)
|
||||
derivatives[deriv.id] = deriv
|
||||
|
||||
# Read tally elements
|
||||
tallies = []
|
||||
for e in elem.findall('tally'):
|
||||
tally = openmc.Tally.from_xml_element(
|
||||
e, filters=filters, derivatives=derivatives
|
||||
)
|
||||
tallies.append(tally)
|
||||
|
||||
return cls(tallies)
|
||||
|
||||
@classmethod
|
||||
def from_xml(cls, path='tallies.xml'):
|
||||
"""Generate tallies from XML file
|
||||
|
|
@ -3201,31 +3255,4 @@ class Tallies(cv.CheckedList):
|
|||
"""
|
||||
tree = ET.parse(path)
|
||||
root = tree.getroot()
|
||||
|
||||
# Read mesh elements
|
||||
meshes = {}
|
||||
for elem in root.findall('mesh'):
|
||||
mesh = MeshBase.from_xml_element(elem)
|
||||
meshes[mesh.id] = mesh
|
||||
|
||||
# Read filter elements
|
||||
filters = {}
|
||||
for elem in root.findall('filter'):
|
||||
filter = openmc.Filter.from_xml_element(elem, meshes=meshes)
|
||||
filters[filter.id] = filter
|
||||
|
||||
# Read derivative elements
|
||||
derivatives = {}
|
||||
for elem in root.findall('derivative'):
|
||||
deriv = openmc.TallyDerivative.from_xml_element(elem)
|
||||
derivatives[deriv.id] = deriv
|
||||
|
||||
# Read tally elements
|
||||
tallies = []
|
||||
for elem in root.findall('tally'):
|
||||
tally = openmc.Tally.from_xml_element(
|
||||
elem, filters=filters, derivatives=derivatives
|
||||
)
|
||||
tallies.append(tally)
|
||||
|
||||
return cls(tallies)
|
||||
return cls.from_xml_element(root)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue