mirror of
https://github.com/openmc-dev/openmc.git
synced 2026-07-26 13:15:39 -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
|
|
@ -62,6 +62,11 @@ extern vector<Library> libraries;
|
|||
//! libraries
|
||||
void read_cross_sections_xml();
|
||||
|
||||
//! Read cross sections file (either XML or multigroup H5) and populate data
|
||||
//! libraries
|
||||
//! \param[in] root node of the cross_sections.xml
|
||||
void read_cross_sections_xml(pugi::xml_node root);
|
||||
|
||||
//! Load nuclide and thermal scattering data from HDF5 files
|
||||
//
|
||||
//! \param[in] nuc_temps Temperatures for each nuclide in [K]
|
||||
|
|
|
|||
|
|
@ -3,14 +3,31 @@
|
|||
|
||||
#include <fstream> // for ifstream
|
||||
#include <string>
|
||||
#include <sys/stat.h>
|
||||
|
||||
namespace openmc {
|
||||
|
||||
// TODO: replace with std::filesystem when switch to C++17 is made
|
||||
//! Determine if a path is a directory
|
||||
//! \param[in] path Path to check
|
||||
//! \return Whether the path is a directory
|
||||
inline bool dir_exists(const std::string& path)
|
||||
{
|
||||
struct stat s;
|
||||
if (stat(path.c_str(), &s) != 0) return false;
|
||||
|
||||
return s.st_mode & S_IFDIR;
|
||||
}
|
||||
|
||||
//! Determine if a file exists
|
||||
//! \param[in] filename Path to file
|
||||
//! \return Whether file exists
|
||||
inline bool file_exists(const std::string& filename)
|
||||
{
|
||||
// rule out file being a path to a directory
|
||||
if (dir_exists(filename))
|
||||
return false;
|
||||
|
||||
std::ifstream s {filename};
|
||||
return s.good();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@
|
|||
#include <vector>
|
||||
|
||||
#include "openmc/vector.h"
|
||||
#include "openmc/xml_interface.h"
|
||||
|
||||
namespace openmc {
|
||||
|
||||
|
|
@ -19,8 +20,13 @@ extern std::unordered_map<int32_t, std::unordered_map<int32_t, int32_t>>
|
|||
extern std::unordered_map<int32_t, int32_t> universe_level_counts;
|
||||
} // namespace model
|
||||
|
||||
//! Read geometry from XML file
|
||||
void read_geometry_xml();
|
||||
|
||||
//! Read geometry from XML node
|
||||
//! \param[in] root node of geometry XML element
|
||||
void read_geometry_xml(pugi::xml_node root);
|
||||
|
||||
//==============================================================================
|
||||
//! Replace Universe, Lattice, and Material IDs with indices.
|
||||
//==============================================================================
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
#ifndef OPENMC_INITIALIZE_H
|
||||
#define OPENMC_INITIALIZE_H
|
||||
|
||||
#include <string>
|
||||
|
||||
#ifdef OPENMC_MPI
|
||||
#include "mpi.h"
|
||||
#endif
|
||||
|
|
@ -11,7 +13,13 @@ int parse_command_line(int argc, char* argv[]);
|
|||
#ifdef OPENMC_MPI
|
||||
void initialize_mpi(MPI_Comm intracomm);
|
||||
#endif
|
||||
void read_input_xml();
|
||||
|
||||
//! Read material, geometry, settings, and tallies from a single XML file
|
||||
bool read_model_xml();
|
||||
//! Read inputs from separate XML files
|
||||
void read_separate_xml_files();
|
||||
//! Write some output that occurs right after initialization
|
||||
void initial_output();
|
||||
|
||||
} // namespace openmc
|
||||
|
||||
|
|
|
|||
|
|
@ -221,6 +221,10 @@ double density_effect(const vector<double>& f, const vector<double>& e_b_sq,
|
|||
//! Read material data from materials.xml
|
||||
void read_materials_xml();
|
||||
|
||||
//! Read material data XML node
|
||||
//! \param[in] root node of materials XML element
|
||||
void read_materials_xml(pugi::xml_node root);
|
||||
|
||||
void free_memory_material();
|
||||
|
||||
} // namespace openmc
|
||||
|
|
|
|||
|
|
@ -279,6 +279,10 @@ void voxel_finalize(hid_t dspace, hid_t dset, hid_t memspace);
|
|||
//! Read plot specifications from a plots.xml file
|
||||
void read_plots_xml();
|
||||
|
||||
//! Read plot specifications from an XML Node
|
||||
//! \param[in] XML node containing plot info
|
||||
void read_plots_xml(pugi::xml_node root);
|
||||
|
||||
//! Clear memory
|
||||
void free_memory_plot();
|
||||
|
||||
|
|
|
|||
|
|
@ -129,9 +129,12 @@ extern double weight_survive; //!< Survival weight after Russian roulette
|
|||
//==============================================================================
|
||||
|
||||
//! Read settings from XML file
|
||||
//! \param[in] root XML node for <settings>
|
||||
void read_settings_xml();
|
||||
|
||||
//! Read settings from XML node
|
||||
//! \param[in] root XML node for <settings>
|
||||
void read_settings_xml(pugi::xml_node root);
|
||||
|
||||
void free_memory_settings();
|
||||
|
||||
} // namespace openmc
|
||||
|
|
|
|||
|
|
@ -48,10 +48,10 @@ public:
|
|||
void set_nuclides(const vector<std::string>& nuclides);
|
||||
|
||||
//! returns vector of indices corresponding to the tally this is called on
|
||||
const vector<int32_t>& filters() const { return filters_; }
|
||||
const vector<int32_t>& filters() const { return filters_; }
|
||||
|
||||
//! \brief Returns the tally filter at index i
|
||||
int32_t filters(int i) const { return filters_[i]; }
|
||||
int32_t filters(int i) const { return filters_[i]; }
|
||||
|
||||
void set_filters(gsl::span<Filter*> filters);
|
||||
|
||||
|
|
@ -178,6 +178,10 @@ extern double global_tally_leakage;
|
|||
//! Read tally specification from tallies.xml
|
||||
void read_tallies_xml();
|
||||
|
||||
//! Read tally specification from an XML node
|
||||
//! \param[in] root node of tallies XML element
|
||||
void read_tallies_xml(pugi::xml_node root);
|
||||
|
||||
//! \brief Accumulate the sum of the contributions from each history within the
|
||||
//! batch to a new random variable
|
||||
void accumulate_tallies();
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -91,8 +91,7 @@ Library::Library(pugi::xml_node node, const std::string& directory)
|
|||
// Non-member functions
|
||||
//==============================================================================
|
||||
|
||||
void read_cross_sections_xml()
|
||||
{
|
||||
void read_cross_sections_xml() {
|
||||
pugi::xml_document doc;
|
||||
std::string filename = settings::path_input + "materials.xml";
|
||||
// Check if materials.xml exists
|
||||
|
|
@ -104,6 +103,11 @@ void read_cross_sections_xml()
|
|||
|
||||
auto root = doc.document_element();
|
||||
|
||||
read_cross_sections_xml(root);
|
||||
}
|
||||
|
||||
void read_cross_sections_xml(pugi::xml_node root)
|
||||
{
|
||||
// Find cross_sections.xml file -- the first place to look is the
|
||||
// materials.xml file. If no file is found there, then we check the
|
||||
// OPENMC_CROSS_SECTIONS environment variable
|
||||
|
|
|
|||
|
|
@ -40,8 +40,7 @@ void update_universe_cell_count(int32_t a, int32_t b)
|
|||
}
|
||||
}
|
||||
|
||||
void read_geometry_xml()
|
||||
{
|
||||
void read_geometry_xml() {
|
||||
// Display output message
|
||||
write_message("Reading geometry XML file...", 5);
|
||||
|
||||
|
|
@ -61,6 +60,11 @@ void read_geometry_xml()
|
|||
// Get root element
|
||||
pugi::xml_node root = doc.document_element();
|
||||
|
||||
read_geometry_xml(root);
|
||||
}
|
||||
|
||||
void read_geometry_xml(pugi::xml_node root)
|
||||
{
|
||||
// Read surfaces, cells, lattice
|
||||
read_surfaces(root);
|
||||
read_cells(root);
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@
|
|||
#include "openmc/constants.h"
|
||||
#include "openmc/cross_sections.h"
|
||||
#include "openmc/error.h"
|
||||
#include "openmc/file_utils.h"
|
||||
#include "openmc/geometry_aux.h"
|
||||
#include "openmc/hdf5_interface.h"
|
||||
#include "openmc/material.h"
|
||||
|
|
@ -105,7 +106,10 @@ int openmc_init(int argc, char* argv[], const void* intracomm)
|
|||
openmc::openmc_set_seed(DEFAULT_SEED);
|
||||
|
||||
// Read XML input files
|
||||
read_input_xml();
|
||||
if (!read_model_xml()) read_separate_xml_files();
|
||||
|
||||
// Write some initial output under the header if needed
|
||||
initial_output();
|
||||
|
||||
// Check for particle restart run
|
||||
if (settings::particle_restart_run)
|
||||
|
|
@ -177,10 +181,8 @@ int parse_command_line(int argc, char* argv[])
|
|||
|
||||
} else if (arg == "-e" || arg == "--event") {
|
||||
settings::event_based = true;
|
||||
|
||||
} else if (arg == "-r" || arg == "--restart") {
|
||||
i += 1;
|
||||
|
||||
// Check what type of file this is
|
||||
hid_t file_id = file_open(argv[i], 'r', true);
|
||||
std::string filetype;
|
||||
|
|
@ -280,7 +282,15 @@ int parse_command_line(int argc, char* argv[])
|
|||
if (argc > 1 && last_flag < argc - 1) {
|
||||
settings::path_input = std::string(argv[last_flag + 1]);
|
||||
|
||||
// Add slash at end of directory if it isn't there
|
||||
// check that the path is either a valid directory or file
|
||||
if (!dir_exists(settings::path_input) &&
|
||||
!file_exists(settings::path_input)) {
|
||||
fatal_error(fmt::format(
|
||||
"The path specified to the OpenMC executable '{}' does not exist.",
|
||||
settings::path_input));
|
||||
}
|
||||
|
||||
// Add slash at end of directory if it isn't the
|
||||
if (!ends_with(settings::path_input, "/")) {
|
||||
settings::path_input += "/";
|
||||
}
|
||||
|
|
@ -289,7 +299,101 @@ int parse_command_line(int argc, char* argv[])
|
|||
return 0;
|
||||
}
|
||||
|
||||
void read_input_xml()
|
||||
bool read_model_xml() {
|
||||
std::string model_filename =
|
||||
settings::path_input.empty() ? "." : settings::path_input;
|
||||
|
||||
// some string cleanup
|
||||
// a trailing "/" is applied to path_input if it's specified,
|
||||
// remove it for the first attempt at reading the input file
|
||||
if (ends_with(model_filename, "/"))
|
||||
model_filename.pop_back();
|
||||
|
||||
// if the current filename is a directory, append the default model filename
|
||||
if (dir_exists(model_filename))
|
||||
model_filename += "/model.xml";
|
||||
|
||||
// if this file doesn't exist, stop here
|
||||
if (!file_exists(model_filename)) return false;
|
||||
|
||||
// try to process the path input as an XML file
|
||||
pugi::xml_document doc;
|
||||
if (!doc.load_file(model_filename.c_str())) {
|
||||
fatal_error(fmt::format(
|
||||
"Error reading from single XML input file '{}'", model_filename));
|
||||
}
|
||||
|
||||
pugi::xml_node root = doc.document_element();
|
||||
|
||||
// Read settings
|
||||
if (!check_for_node(root, "settings")) {
|
||||
fatal_error("No <settings> node present in the model.xml file.");
|
||||
}
|
||||
auto settings_root = root.child("settings");
|
||||
|
||||
// Verbosity
|
||||
if (check_for_node(settings_root, "verbosity")) {
|
||||
settings::verbosity = std::stoi(get_node_value(settings_root, "verbosity"));
|
||||
}
|
||||
|
||||
// To this point, we haven't displayed any output since we didn't know what
|
||||
// the verbosity is. Now that we checked for it, show the title if necessary
|
||||
if (mpi::master) {
|
||||
if (settings::verbosity >= 2)
|
||||
title();
|
||||
}
|
||||
|
||||
write_message(fmt::format("Reading model XML file '{}' ...", model_filename), 5);
|
||||
|
||||
read_settings_xml(settings_root);
|
||||
|
||||
// If other XML files are present, display warning
|
||||
// that they will be ignored
|
||||
auto other_inputs = {"materials.xml", "geometry.xml", "settings.xml", "tallies.xml", "plots.xml"};
|
||||
for (const auto& input : other_inputs) {
|
||||
if (file_exists(settings::path_input + input)) {
|
||||
warning((fmt::format("Other XML file input(s) are present. These files "
|
||||
"will be ignored in favor of the {} file.",
|
||||
model_filename)));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Read materials and cross sections
|
||||
if (!check_for_node(root, "materials")) {
|
||||
fatal_error(fmt::format(
|
||||
"No <materials> node present in the {} file.", model_filename));
|
||||
}
|
||||
|
||||
read_cross_sections_xml(root.child("materials"));
|
||||
read_materials_xml(root.child("materials"));
|
||||
|
||||
// Read geometry
|
||||
if (!check_for_node(root, "geometry")) {
|
||||
fatal_error(fmt::format(
|
||||
"No <geometry> node present in the {} file.", model_filename));
|
||||
}
|
||||
read_geometry_xml(root.child("geometry"));
|
||||
|
||||
// Final geometry setup and assign temperatures
|
||||
finalize_geometry();
|
||||
|
||||
// Finalize cross sections having assigned temperatures
|
||||
finalize_cross_sections();
|
||||
|
||||
if (check_for_node(root, "tallies"))
|
||||
read_tallies_xml(root.child("tallies"));
|
||||
|
||||
// Initialize distribcell_filters
|
||||
prepare_distribcell();
|
||||
|
||||
if (check_for_node(root, "plots"))
|
||||
read_plots_xml(root.child("plots"));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void read_separate_xml_files()
|
||||
{
|
||||
read_settings_xml();
|
||||
read_cross_sections_xml();
|
||||
|
|
@ -310,6 +414,10 @@ void read_input_xml()
|
|||
// Read the plots.xml regardless of plot mode in case plots are requested
|
||||
// via the API
|
||||
read_plots_xml();
|
||||
}
|
||||
|
||||
void initial_output() {
|
||||
// write initial output
|
||||
if (settings::run_mode == RunMode::PLOTTING) {
|
||||
// Read plots.xml if it exists
|
||||
if (mpi::master && settings::verbosity >= 5)
|
||||
|
|
|
|||
|
|
@ -1264,8 +1264,7 @@ double density_effect(const vector<double>& f, const vector<double>& e_b_sq,
|
|||
return delta - w_sq * (1.0 - beta_sq);
|
||||
}
|
||||
|
||||
void read_materials_xml()
|
||||
{
|
||||
void read_materials_xml() {
|
||||
write_message("Reading materials XML file...", 5);
|
||||
|
||||
pugi::xml_document doc;
|
||||
|
|
@ -1281,6 +1280,12 @@ void read_materials_xml()
|
|||
|
||||
// Loop over XML material elements and populate the array.
|
||||
pugi::xml_node root = doc.document_element();
|
||||
|
||||
read_materials_xml(root);
|
||||
}
|
||||
|
||||
void read_materials_xml(pugi::xml_node root)
|
||||
{
|
||||
for (pugi::xml_node material_node : root.children("material")) {
|
||||
model::materials.push_back(make_unique<Material>(material_node));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -124,8 +124,7 @@ extern "C" int openmc_plot_geometry()
|
|||
return 0;
|
||||
}
|
||||
|
||||
void read_plots_xml()
|
||||
{
|
||||
void read_plots_xml() {
|
||||
// Check if plots.xml exists; this is only necessary when the plot runmode is
|
||||
// initiated. Otherwise, we want to read plots.xml because it may be called
|
||||
// later via the API. In that case, its ok for a plots.xml to not exist
|
||||
|
|
@ -141,6 +140,12 @@ void read_plots_xml()
|
|||
doc.load_file(filename.c_str());
|
||||
|
||||
pugi::xml_node root = doc.document_element();
|
||||
|
||||
read_plots_xml(root);
|
||||
}
|
||||
|
||||
void read_plots_xml(pugi::xml_node root)
|
||||
{
|
||||
for (auto node : root.children("plot")) {
|
||||
model::plots.emplace_back(node);
|
||||
model::plot_map[model::plots.back().id_] = model::plots.size() - 1;
|
||||
|
|
|
|||
|
|
@ -214,20 +214,19 @@ void get_run_parameters(pugi::xml_node node_base)
|
|||
}
|
||||
}
|
||||
|
||||
void read_settings_xml()
|
||||
{
|
||||
void read_settings_xml() {
|
||||
using namespace settings;
|
||||
using namespace pugi;
|
||||
|
||||
// Check if settings.xml exists
|
||||
std::string filename = path_input + "settings.xml";
|
||||
std::string filename = settings::path_input + "settings.xml";
|
||||
if (!file_exists(filename)) {
|
||||
if (run_mode != RunMode::PLOTTING) {
|
||||
fatal_error(
|
||||
fmt::format("Settings XML file '{}' does not exist! In order "
|
||||
"to run OpenMC, you first need a set of input files; at a "
|
||||
"minimum, this "
|
||||
"includes settings.xml, geometry.xml, and materials.xml. "
|
||||
"includes settings.xml, geometry.xml, and materials.xml "
|
||||
"or a single XML file containing all of these files. "
|
||||
"Please consult "
|
||||
"the user's guide at https://docs.openmc.org for further "
|
||||
"information.",
|
||||
|
|
@ -259,8 +258,17 @@ void read_settings_xml()
|
|||
if (verbosity >= 2)
|
||||
title();
|
||||
}
|
||||
|
||||
write_message("Reading settings XML file...", 5);
|
||||
|
||||
read_settings_xml(root);
|
||||
}
|
||||
|
||||
void read_settings_xml(pugi::xml_node root)
|
||||
{
|
||||
using namespace settings;
|
||||
using namespace pugi;
|
||||
|
||||
// Find if a multi-group or continuous-energy simulation is desired
|
||||
if (check_for_node(root, "energy_mode")) {
|
||||
std::string temp_str = get_node_value(root, "energy_mode", true, true);
|
||||
|
|
|
|||
|
|
@ -705,8 +705,7 @@ std::string Tally::nuclide_name(int nuclide_idx) const
|
|||
// Non-member functions
|
||||
//==============================================================================
|
||||
|
||||
void read_tallies_xml()
|
||||
{
|
||||
void read_tallies_xml() {
|
||||
// Check if tallies.xml exists. If not, just return since it is optional
|
||||
std::string filename = settings::path_input + "tallies.xml";
|
||||
if (!file_exists(filename))
|
||||
|
|
@ -719,6 +718,11 @@ void read_tallies_xml()
|
|||
doc.load_file(filename.c_str());
|
||||
pugi::xml_node root = doc.document_element();
|
||||
|
||||
read_tallies_xml(root);
|
||||
}
|
||||
|
||||
void read_tallies_xml(pugi::xml_node root)
|
||||
{
|
||||
// Check for <assume_separate> setting
|
||||
if (check_for_node(root, "assume_separate")) {
|
||||
settings::assume_separate = get_node_value_bool(root, "assume_separate");
|
||||
|
|
|
|||
0
tests/regression_tests/model_xml/__init__.py
Normal file
0
tests/regression_tests/model_xml/__init__.py
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<model>
|
||||
<materials>
|
||||
<material depletable="true" id="1">
|
||||
<density units="g/cc" value="10.0" />
|
||||
<nuclide ao="1.0" name="U235" />
|
||||
</material>
|
||||
<material id="2">
|
||||
<density units="g/cc" value="0.1" />
|
||||
<nuclide ao="0.1" name="H1" />
|
||||
</material>
|
||||
</materials>
|
||||
<geometry>
|
||||
<cell id="1" material="1" region="-1" universe="1" />
|
||||
<cell id="2" material="2" region="1" universe="1" />
|
||||
<cell fill="1" id="3" region="2 -3 4 -5 6 -8" rotation="10 20 30" universe="2" />
|
||||
<cell fill="1" id="4" region="2 -3 4 -5 8 -7" translation="0 0 15" universe="2" />
|
||||
<surface coeffs="1.0 0.0 0.0 5.0" id="1" type="sphere" />
|
||||
<surface boundary="vacuum" coeffs="-7.5" id="2" name="minimum x" type="x-plane" />
|
||||
<surface boundary="vacuum" coeffs="7.5" id="3" name="maximum x" type="x-plane" />
|
||||
<surface boundary="vacuum" coeffs="-7.5" id="4" name="minimum y" type="y-plane" />
|
||||
<surface boundary="vacuum" coeffs="7.5" id="5" name="maximum y" type="y-plane" />
|
||||
<surface boundary="vacuum" coeffs="-7.5" id="6" type="z-plane" />
|
||||
<surface boundary="vacuum" coeffs="22.5" id="7" type="z-plane" />
|
||||
<surface coeffs="7.5" id="8" type="z-plane" />
|
||||
</geometry>
|
||||
<settings>
|
||||
<run_mode>eigenvalue</run_mode>
|
||||
<particles>10000</particles>
|
||||
<batches>10</batches>
|
||||
<inactive>5</inactive>
|
||||
<source strength="1.0">
|
||||
<space type="box">
|
||||
<parameters>-4.0 -4.0 -4.0 4.0 4.0 4.0</parameters>
|
||||
</space>
|
||||
</source>
|
||||
</settings>
|
||||
</model>
|
||||
23
tests/regression_tests/model_xml/energy_laws_inputs_true.dat
Normal file
23
tests/regression_tests/model_xml/energy_laws_inputs_true.dat
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<model>
|
||||
<materials>
|
||||
<material depletable="true" id="1">
|
||||
<density units="g/cm3" value="20.0" />
|
||||
<nuclide ao="1.0" name="U233" />
|
||||
<nuclide ao="1.0" name="Am244" />
|
||||
<nuclide ao="1.0" name="H2" />
|
||||
<nuclide ao="1.0" name="Na23" />
|
||||
<nuclide ao="1.0" name="Ta181" />
|
||||
</material>
|
||||
</materials>
|
||||
<geometry>
|
||||
<cell id="1" material="1" region="-1" universe="1" />
|
||||
<surface boundary="reflective" coeffs="0.0 0.0 0.0 100.0" id="1" type="sphere" />
|
||||
</geometry>
|
||||
<settings>
|
||||
<run_mode>eigenvalue</run_mode>
|
||||
<particles>1000</particles>
|
||||
<batches>10</batches>
|
||||
<inactive>5</inactive>
|
||||
</settings>
|
||||
</model>
|
||||
67
tests/regression_tests/model_xml/inputs_true.dat
Normal file
67
tests/regression_tests/model_xml/inputs_true.dat
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<model>
|
||||
<materials>
|
||||
<material id="6">
|
||||
<density units="g/cm3" value="2.6989" />
|
||||
<nuclide ao="1.0" name="Al27" />
|
||||
</material>
|
||||
</materials>
|
||||
<geometry>
|
||||
<cell id="12" material="void" region="-16 17 -18" universe="10" />
|
||||
<cell id="13" material="6" region="-16 18 -19" universe="10" />
|
||||
<cell id="14" material="void" region="~(-16 17 -19)" universe="10" />
|
||||
<surface id="16" type="x-cylinder" boundary="vacuum" coeffs="0.0 0.0 1.0" />
|
||||
<surface id="17" type="x-plane" boundary="vacuum" coeffs="-1.0" />
|
||||
<surface id="18" type="x-plane" coeffs="1.0" />
|
||||
<surface id="19" type="x-plane" boundary="vacuum" coeffs="1000000000.0" />
|
||||
</geometry>
|
||||
<settings>
|
||||
<run_mode>fixed source</run_mode>
|
||||
<particles>10000</particles>
|
||||
<batches>1</batches>
|
||||
<source strength="1.0">
|
||||
<space type="point">
|
||||
<parameters>0 0 0</parameters>
|
||||
</space>
|
||||
<angle type="monodirectional" reference_uvw="1.0 0.0 0.0" />
|
||||
<energy type="discrete">
|
||||
<parameters>14000000.0 1.0</parameters>
|
||||
</energy>
|
||||
</source>
|
||||
<electron_treatment>ttb</electron_treatment>
|
||||
<photon_transport>true</photon_transport>
|
||||
<cutoff>
|
||||
<energy_photon>1000.0</energy_photon>
|
||||
</cutoff>
|
||||
</settings>
|
||||
<tallies>
|
||||
<filter id="1" type="surface">
|
||||
<bins>16</bins>
|
||||
</filter>
|
||||
<filter id="2" type="particle">
|
||||
<bins>neutron photon electron positron</bins>
|
||||
</filter>
|
||||
<tally id="1">
|
||||
<filters>1 2</filters>
|
||||
<scores>current</scores>
|
||||
</tally>
|
||||
<tally id="2">
|
||||
<filters>2</filters>
|
||||
<nuclides>Al27 total</nuclides>
|
||||
<scores>total (n,gamma)</scores>
|
||||
<estimator>tracklength</estimator>
|
||||
</tally>
|
||||
<tally id="3">
|
||||
<filters>2</filters>
|
||||
<nuclides>Al27 total</nuclides>
|
||||
<scores>total heating (n,gamma)</scores>
|
||||
<estimator>collision</estimator>
|
||||
</tally>
|
||||
<tally id="4">
|
||||
<filters>2</filters>
|
||||
<nuclides>Al27 total</nuclides>
|
||||
<scores>total heating (n,gamma)</scores>
|
||||
<estimator>analog</estimator>
|
||||
</tally>
|
||||
</tallies>
|
||||
</model>
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<model>
|
||||
<materials>
|
||||
<material depletable="true" id="1" name="UO2">
|
||||
<density units="g/cm3" value="10.0" />
|
||||
<nuclide ao="1.0" name="U235" />
|
||||
<nuclide ao="2.0" name="O16" />
|
||||
</material>
|
||||
<material id="2" name="light water">
|
||||
<density units="g/cm3" value="1.0" />
|
||||
<nuclide ao="2.0" name="H1" />
|
||||
<nuclide ao="1.0" name="O16" />
|
||||
<sab name="c_H_in_H2O" />
|
||||
</material>
|
||||
</materials>
|
||||
<geometry>
|
||||
<cell id="1" material="1" region="-1" universe="1" />
|
||||
<cell id="2" material="2" region="1" universe="1" />
|
||||
<cell id="3" material="1" region="-2" universe="2" />
|
||||
<cell id="4" material="2" region="2" universe="2" />
|
||||
<cell fill="3" id="5" universe="4" />
|
||||
<cell fill="5" id="6" region="3 -4 5 -6" universe="6" />
|
||||
<lattice id="3">
|
||||
<pitch>1.2 1.2</pitch>
|
||||
<outer>1</outer>
|
||||
<dimension>2 2</dimension>
|
||||
<lower_left>-1.2 -1.2</lower_left>
|
||||
<universes>
|
||||
2 1
|
||||
1 1 </universes>
|
||||
</lattice>
|
||||
<lattice id="5">
|
||||
<pitch>2.4 2.4</pitch>
|
||||
<dimension>2 2</dimension>
|
||||
<lower_left>-2.4 -2.4</lower_left>
|
||||
<universes>
|
||||
4 4
|
||||
4 4 </universes>
|
||||
</lattice>
|
||||
<surface coeffs="0.0 0.0 0.4" id="1" type="z-cylinder" />
|
||||
<surface coeffs="0.0 0.0 0.5" id="2" type="z-cylinder" />
|
||||
<surface boundary="reflective" coeffs="-2.4" id="3" name="minimum x" type="x-plane" />
|
||||
<surface boundary="reflective" coeffs="2.4" id="4" name="maximum x" type="x-plane" />
|
||||
<surface boundary="reflective" coeffs="-2.4" id="5" name="minimum y" type="y-plane" />
|
||||
<surface boundary="reflective" coeffs="2.4" id="6" name="maximum y" type="y-plane" />
|
||||
</geometry>
|
||||
<settings>
|
||||
<run_mode>eigenvalue</run_mode>
|
||||
<particles>1000</particles>
|
||||
<batches>10</batches>
|
||||
<inactive>5</inactive>
|
||||
</settings>
|
||||
</model>
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<model>
|
||||
<materials>
|
||||
<material id="1">
|
||||
<density units="g/cm3" value="2.6989" />
|
||||
<nuclide ao="1.0" name="Al27" />
|
||||
</material>
|
||||
</materials>
|
||||
<geometry>
|
||||
<cell id="1" material="void" region="-1 2 -3" universe="1" />
|
||||
<cell id="2" material="1" region="-1 3 -4" universe="1" />
|
||||
<cell id="3" material="void" region="~(-1 2 -4)" universe="1" />
|
||||
<surface boundary="vacuum" coeffs="0.0 0.0 1.0" id="1" type="x-cylinder" />
|
||||
<surface boundary="vacuum" coeffs="-1.0" id="2" type="x-plane" />
|
||||
<surface coeffs="1.0" id="3" type="x-plane" />
|
||||
<surface boundary="vacuum" coeffs="1000000000.0" id="4" type="x-plane" />
|
||||
</geometry>
|
||||
<settings>
|
||||
<run_mode>fixed source</run_mode>
|
||||
<particles>10000</particles>
|
||||
<batches>1</batches>
|
||||
<source strength="1.0">
|
||||
<space type="point">
|
||||
<parameters>0 0 0</parameters>
|
||||
</space>
|
||||
<angle reference_uvw="1.0 0.0 0.0" type="monodirectional" />
|
||||
<energy type="discrete">
|
||||
<parameters>14000000.0 1.0</parameters>
|
||||
</energy>
|
||||
</source>
|
||||
<electron_treatment>ttb</electron_treatment>
|
||||
<photon_transport>true</photon_transport>
|
||||
<cutoff>
|
||||
<energy_photon>1000.0</energy_photon>
|
||||
</cutoff>
|
||||
</settings>
|
||||
<tallies>
|
||||
<filter id="1" type="surface">
|
||||
<bins>1</bins>
|
||||
</filter>
|
||||
<filter id="2" type="particle">
|
||||
<bins>neutron photon electron positron</bins>
|
||||
</filter>
|
||||
<tally id="1">
|
||||
<filters>1 2</filters>
|
||||
<scores>current</scores>
|
||||
</tally>
|
||||
<tally id="2">
|
||||
<filters>2</filters>
|
||||
<nuclides>Al27 total</nuclides>
|
||||
<scores>total (n,gamma)</scores>
|
||||
<estimator>tracklength</estimator>
|
||||
</tally>
|
||||
<tally id="3">
|
||||
<filters>2</filters>
|
||||
<nuclides>Al27 total</nuclides>
|
||||
<scores>total heating (n,gamma)</scores>
|
||||
<estimator>collision</estimator>
|
||||
</tally>
|
||||
<tally id="4">
|
||||
<filters>2</filters>
|
||||
<nuclides>Al27 total</nuclides>
|
||||
<scores>total heating (n,gamma)</scores>
|
||||
<estimator>analog</estimator>
|
||||
</tally>
|
||||
</tallies>
|
||||
</model>
|
||||
100
tests/regression_tests/model_xml/test.py
Normal file
100
tests/regression_tests/model_xml/test.py
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
from difflib import unified_diff
|
||||
import glob
|
||||
import filecmp
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
import openmc
|
||||
import pytest
|
||||
|
||||
from tests.testing_harness import PyAPITestHarness, colorize
|
||||
|
||||
# use a few models from other tests to make sure the same results are
|
||||
# produced when using a single model.xml file as input
|
||||
from ..adj_cell_rotation.test import model as adj_cell_rotation_model
|
||||
from ..lattice_multiple.test import model as lattice_multiple_model
|
||||
from ..energy_laws.test import model as energy_laws_model
|
||||
from ..photon_production.test import model as photon_production_model
|
||||
|
||||
|
||||
class ModelXMLTestHarness(PyAPITestHarness):
|
||||
"""Accept a results file to check against and assume inputs_true is the contents of a model.xml file.
|
||||
"""
|
||||
def __init__(self, model=None, inputs_true=None, results_true=None):
|
||||
statepoint_name = f'statepoint.{model.settings.batches}.h5'
|
||||
super().__init__(statepoint_name, model, inputs_true)
|
||||
|
||||
self.results_true = 'results_true.dat' if results_true is None else results_true
|
||||
|
||||
def _build_inputs(self):
|
||||
self._model.export_to_model_xml()
|
||||
|
||||
def _get_inputs(self):
|
||||
return open('model.xml').read()
|
||||
|
||||
def _compare_results(self):
|
||||
"""Make sure the current results agree with the reference."""
|
||||
compare = filecmp.cmp('results_test.dat', self.results_true)
|
||||
if not compare:
|
||||
expected = open(self.results_true).readlines()
|
||||
actual = open('results_test.dat').readlines()
|
||||
diff = unified_diff(expected, actual, self.results_true,
|
||||
'results_test.dat')
|
||||
print('Result differences:')
|
||||
print(''.join(colorize(diff)))
|
||||
os.rename('results_test.dat', 'results_error.dat')
|
||||
assert compare, 'Results do not agree'
|
||||
|
||||
def _cleanup(self):
|
||||
super()._cleanup()
|
||||
if os.path.exists('model.xml'):
|
||||
os.remove('model.xml')
|
||||
|
||||
|
||||
test_names = [
|
||||
'adj_cell_rotation',
|
||||
'lattice_multiple',
|
||||
'energy_laws',
|
||||
'photon_production'
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.parametrize("test_name", test_names, ids=lambda test: test)
|
||||
def test_model_xml(test_name, request):
|
||||
openmc.reset_auto_ids()
|
||||
|
||||
test_path = '../' + test_name
|
||||
results = test_path + "/results_true.dat"
|
||||
inputs = test_name + "_inputs_true.dat"
|
||||
model_name = test_name + "_model"
|
||||
harness = ModelXMLTestHarness(request.getfixturevalue(model_name), inputs, results)
|
||||
harness.main()
|
||||
|
||||
def test_input_arg(run_in_tmpdir):
|
||||
|
||||
pincell = openmc.examples.pwr_pin_cell()
|
||||
|
||||
pincell.settings.particles = 100
|
||||
|
||||
# export to separate XML files and run
|
||||
pincell.export_to_xml()
|
||||
openmc.run()
|
||||
|
||||
# make sure the executable isn't falling back on the separate XMLs
|
||||
for f in glob.glob('*.xml'):
|
||||
os.remove(f)
|
||||
# now export to a single XML file with a custom name
|
||||
pincell.export_to_model_xml('pincell.xml')
|
||||
assert Path('pincell.xml').exists()
|
||||
|
||||
# run by specifying that single file
|
||||
openmc.run(path_input='pincell.xml')
|
||||
|
||||
# check that this works for plotting too
|
||||
openmc.plot_geometry(path_input='pincell.xml')
|
||||
|
||||
# now ensure we get an error for an incorrect filename,
|
||||
# even in the presence of other, valid XML files
|
||||
pincell.export_to_model_xml()
|
||||
with pytest.raises(RuntimeError, match='ex-em-ell.xml'):
|
||||
openmc.run(path_input='ex-em-ell.xml')
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
from math import pi
|
||||
from pathlib import Path
|
||||
import os
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
|
|
@ -529,3 +530,40 @@ def test_calc_volumes(run_in_tmpdir, pin_model_attributes, mpi_intracomm):
|
|||
assert openmc.lib.materials[3].volume == mats[2].volume
|
||||
|
||||
test_model.finalize_lib()
|
||||
|
||||
def test_model_xml(run_in_tmpdir):
|
||||
|
||||
# load a model from examples
|
||||
pwr_model = openmc.examples.pwr_core()
|
||||
|
||||
# export to separate XMLs manually
|
||||
pwr_model.settings.export_to_xml('settings_ref.xml')
|
||||
pwr_model.materials.export_to_xml('materials_ref.xml')
|
||||
pwr_model.geometry.export_to_xml('geometry_ref.xml')
|
||||
|
||||
# now write and read a model.xml file
|
||||
pwr_model.export_to_model_xml()
|
||||
new_model = openmc.Model.from_model_xml()
|
||||
|
||||
# make sure we can also export this again to separate
|
||||
# XML files
|
||||
new_model.export_to_xml()
|
||||
|
||||
def test_single_xml_exec(run_in_tmpdir):
|
||||
|
||||
pincell_model = openmc.examples.pwr_pin_cell()
|
||||
|
||||
pincell_model.export_to_model_xml('pwr_pincell.xml')
|
||||
|
||||
openmc.run(path_input='pwr_pincell.xml')
|
||||
|
||||
with pytest.raises(RuntimeError, match='ex-em-ell.xml'):
|
||||
openmc.run(path_input='ex-em-ell.xml')
|
||||
|
||||
# test that a file in a different directory can be used
|
||||
os.mkdir('inputs')
|
||||
pincell_model.export_to_model_xml('./inputs/pincell.xml')
|
||||
openmc.run(path_input='./inputs/pincell.xml')
|
||||
|
||||
with pytest.raises(RuntimeError, match='input_dir'):
|
||||
openmc.run(path_input='input_dir/pincell.xml')
|
||||
Loading…
Add table
Add a link
Reference in a new issue