From 2555b951a4c585ab31942d4a718c2c5354d082a8 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Thu, 3 Nov 2022 18:27:53 -0500 Subject: [PATCH 01/53] Factor out to_xml_element methods for XML classes --- openmc/geometry.py | 52 +++++++++++++-------- openmc/plots.py | 21 +++++---- openmc/settings.py | 109 ++++++++++++++++++++++++--------------------- openmc/tallies.py | 25 +++++++---- 4 files changed, 120 insertions(+), 87 deletions(-) diff --git a/openmc/geometry.py b/openmc/geometry.py index 3b6933362..889cb6b31 100644 --- a/openmc/geometry.py +++ b/openmc/geometry.py @@ -103,6 +103,38 @@ 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) + + return element + def export_to_xml(self, path='geometry.xml', remove_surfs=False): """Export geometry to an XML file. @@ -117,25 +149,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) diff --git a/openmc/plots.py b/openmc/plots.py index 2708683b1..52dcf3fc1 100644 --- a/openmc/plots.py +++ b/openmc/plots.py @@ -909,14 +909,8 @@ 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. - - Parameters - ---------- - path : str - Path to file to write. Defaults to 'plots.xml'. - + def to_xml_element(self): + """Create a 'plots' element to be written to an XML file. """ # Reset xml element tree self._plots_file.clear() @@ -926,6 +920,17 @@ class Plots(cv.CheckedList): # Clean the indentation in the file to be user-readable clean_indentation(self._plots_file) + 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(): diff --git a/openmc/settings.py b/openmc/settings.py index 7d327ce7c..29e19db2e 100644 --- a/openmc/settings.py +++ b/openmc/settings.py @@ -1539,6 +1539,63 @@ class Settings: if text is not None: self.max_tracks = int(text) + def to_xml_element(self): + """Create a 'settings' element to be written to an XML file. + """ + + # 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) + 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) + 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) + 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) + + return element + def export_to_xml(self, path: PathLike = 'settings.xml'): """Export simulation settings to an XML file. @@ -1548,57 +1605,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) diff --git a/openmc/tallies.py b/openmc/tallies.py index d0355f14e..dfb0c98bf 100644 --- a/openmc/tallies.py +++ b/openmc/tallies.py @@ -3155,6 +3155,21 @@ class Tallies(cv.CheckedList): for d in derivs: root_element.append(d.to_xml_element()) + def to_xml_element(self): + """Creates a 'tallies' element to be written to an XML file. + """ + element = ET.Element("tallies") + self._create_mesh_subelements(element) + 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) + + return element + + def export_to_xml(self, path='tallies.xml'): """Create a tallies.xml file that can be used for a simulation. @@ -3164,15 +3179,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) From ad746cb3e1d57c2fec163fbb4b8f87286bf72852 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Thu, 3 Nov 2022 19:09:15 -0500 Subject: [PATCH 02/53] Writing all main nodes to a single XML file --- openmc/material.py | 62 +++++++++++++++++++++++++------------------ openmc/model/model.py | 34 +++++++++++++++++------- openmc/settings.py | 2 +- 3 files changed, 62 insertions(+), 36 deletions(-) diff --git a/openmc/material.py b/openmc/material.py index 420a08521..af42e2e24 100644 --- a/openmc/material.py +++ b/openmc/material.py @@ -1449,6 +1449,41 @@ class Materials(cv.CheckedList): for material in self: material.make_isotropic_in_lab() + def _write_xml(self, file): + """Writes XML content of the materials to an open file handle. + + Parameters + ---------- + file : IOTextWrapper + Open file handle to write content into. + """ + # Write the header and the opening tag for the root element. + file.write("\n") + file.write('\n') + + # Write the 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(' ') + file.write(' ') + reorder_attributes(element) # TODO: Remove when support is Python 3.8+ + ET.ElementTree(element).write(file, encoding='unicode') + + # Write the 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(' ') + file.write(' ') + 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('\n') + + def export_to_xml(self, path: PathLike = 'materials.xml'): """Export material collection to an XML file. @@ -1468,32 +1503,7 @@ class Materials(cv.CheckedList): # one go. with open(str(p), 'w', encoding='utf-8', errors='xmlcharrefreplace') as fh: - - # Write the header and the opening tag for the root element. - fh.write("\n") - fh.write('\n') - - # Write the 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') - - # Write the 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') - - # Write the closing tag for the root element. - fh.write('\n') + self._write_xml(fh) @classmethod def from_xml(cls, path: PathLike = 'materials.xml'): diff --git a/openmc/model/model.py b/openmc/model/model.py index 98136b2d5..5a1b86a1f 100644 --- a/openmc/model/model.py +++ b/openmc/model/model.py @@ -5,11 +5,16 @@ import os from pathlib import Path from numbers import Integral from tempfile import NamedTemporaryFile +<<<<<<< HEAD import warnings +======= +from xml.etree import ElementTree as ET +>>>>>>> d42935a08 (Writing all main nodes to a single XML file) 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 @@ -404,7 +409,7 @@ class Model: Parameters ---------- directory : str - Directory to write XML files to. If it doesn't exist already, it + Directory to write the model.xml file to. If it doesn't exist already, it will be created. remove_surfs : bool Whether or not to remove redundant surfaces from the geometry when @@ -416,8 +421,8 @@ class Model: d = Path(directory) if not d.is_dir(): d.mkdir(parents=True) + d /= 'model.xml' - 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.") @@ -425,22 +430,33 @@ class Model: # 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) + settings_element = self.settings.to_xml_element() + geometry_element = self.geometry.to_xml_element() # 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: - self.materials.export_to_xml(d) + materials = self.materials else: materials = openmc.Materials(self.geometry.get_all_materials() .values()) - materials.export_to_xml(d) - if self.tallies: - self.tallies.export_to_xml(d) - if self.plots: - self.plots.export_to_xml(d) + with open(d, 'w', encoding='utf-8', + errors='xmlcharrefreplace') as fh: + # Write the materials collection to the open XML file first. + # This will write the XML header also + materials._write_xml(fh) + # 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() + ET.ElementTree(tallies_element).write(fh, encoding='unicode') + if self.plots: + plots_element = self.plots.to_xml_element() + ET.ElementTree(plots_element).write(fh, encoding='unicode') def import_properties(self, filename): """Import physical properties diff --git a/openmc/settings.py b/openmc/settings.py index 29e19db2e..dd0041351 100644 --- a/openmc/settings.py +++ b/openmc/settings.py @@ -1595,7 +1595,7 @@ class Settings: clean_indentation(element) return element - + def export_to_xml(self, path: PathLike = 'settings.xml'): """Export simulation settings to an XML file. From a09a61e77b10416ad52613bb578e03d60af72ebf Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Thu, 3 Nov 2022 19:12:18 -0500 Subject: [PATCH 03/53] Writing all output under a single root node --- openmc/material.py | 7 +++++-- openmc/model/model.py | 6 +++++- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/openmc/material.py b/openmc/material.py index af42e2e24..9e3951ccf 100644 --- a/openmc/material.py +++ b/openmc/material.py @@ -1449,16 +1449,19 @@ class Materials(cv.CheckedList): for material in self: material.make_isotropic_in_lab() - def _write_xml(self, file): + def _write_xml(self, file, header=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 """ # Write the header and the opening tag for the root element. - file.write("\n") + if header: + file.write("\n") file.write('\n') # Write the element. diff --git a/openmc/model/model.py b/openmc/model/model.py index 5a1b86a1f..1af3bdffc 100644 --- a/openmc/model/model.py +++ b/openmc/model/model.py @@ -444,9 +444,12 @@ class Model: with open(d, 'w', encoding='utf-8', errors='xmlcharrefreplace') as fh: + # write the XML header + fh.write("\n") + fh.write("\n") # Write the materials collection to the open XML file first. # This will write the XML header also - materials._write_xml(fh) + materials._write_xml(fh, False) # Write remaining elements as a tree ET.ElementTree(geometry_element).write(fh, encoding='unicode') ET.ElementTree(settings_element).write(fh, encoding='unicode') @@ -457,6 +460,7 @@ class Model: if self.plots: plots_element = self.plots.to_xml_element() ET.ElementTree(plots_element).write(fh, encoding='unicode') + fh.write("\n") def import_properties(self, filename): """Import physical properties From 7a9d8c95afa53fca2a8d99cf48a6be13f1786107 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Thu, 3 Nov 2022 19:18:39 -0500 Subject: [PATCH 04/53] Keeping old option to write separate XMLs so I can still use to tests to make sure I don't break stuff. --- openmc/model/model.py | 61 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 60 insertions(+), 1 deletion(-) diff --git a/openmc/model/model.py b/openmc/model/model.py index 1af3bdffc..e3c4c4ae1 100644 --- a/openmc/model/model.py +++ b/openmc/model/model.py @@ -403,7 +403,66 @@ class Model: depletion_operator.cleanup_when_done = True depletion_operator.finalize() - def export_to_xml(self, directory='.', remove_surfs=False): + def export_to_xml(self, directory='.', remove_surfs=False, separate_xmls=True): + """Export model to separate XML files. + + Parameters + ---------- + directory : str + Directory to write XML files to. If it doesn't exist already, it + will be created. + remove_surfs : bool + Whether or not to remove redundant surfaces from the geometry when + exporting. + + .. versionadded:: 0.13.1 + + separate_xmls : bool + Whether or not to write a single model.xml file or many XML files. + """ + if separate_xmls: + self.export_to_separate_xmls(directory, remove_surfs) + else: + self.export_to_single_xml(directory, remove_surfs) + + def export_to_separate_xmls(self, directory='.', remove_surfs=False): + """Export model to separate XML files. + + Parameters + ---------- + directory : str + Directory to write XML files to. If it doesn't exist already, it + will be created. + remove_surfs : bool + Whether or not to remove redundant surfaces from the geometry when + exporting. + + .. versionadded:: 0.13.1 + """ + # Create directory if required + d = Path(directory) + if not d.is_dir(): + d.mkdir(parents=True) + + self.settings.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 + # a collection. + if self.materials: + self.materials.export_to_xml(d) + else: + materials = openmc.Materials(self.geometry.get_all_materials() + .values()) + materials.export_to_xml(d) + + if self.tallies: + self.tallies.export_to_xml(d) + if self.plots: + self.plots.export_to_xml(d) + + def export_to_single_xml(self, directory='.', remove_surfs=False): """Export model to XML files. Parameters From 040965245dc5a7476aa005829e7cb42f75a9233c Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Thu, 3 Nov 2022 20:10:22 -0500 Subject: [PATCH 05/53] Adding signatures for reading information from an XML node where necessary. --- include/openmc/cross_sections.h | 5 +++ include/openmc/geometry_aux.h | 6 ++++ include/openmc/material.h | 4 +++ include/openmc/plot.h | 4 +++ include/openmc/settings.h | 5 ++- include/openmc/tallies/tally.h | 8 +++-- src/cross_sections.cpp | 8 +++-- src/geometry_aux.cpp | 8 +++-- src/initialize.cpp | 63 ++++++++++++++++++++++++++++++--- src/material.cpp | 9 +++-- src/plot.cpp | 7 ++-- src/settings.cpp | 14 +++++--- src/tallies/tally.cpp | 8 +++-- 13 files changed, 127 insertions(+), 22 deletions(-) diff --git a/include/openmc/cross_sections.h b/include/openmc/cross_sections.h index 2b0473bec..06140a6a8 100644 --- a/include/openmc/cross_sections.h +++ b/include/openmc/cross_sections.h @@ -62,6 +62,11 @@ extern vector 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] diff --git a/include/openmc/geometry_aux.h b/include/openmc/geometry_aux.h index b248d491a..a4c506c31 100644 --- a/include/openmc/geometry_aux.h +++ b/include/openmc/geometry_aux.h @@ -10,6 +10,7 @@ #include #include "openmc/vector.h" +#include "openmc/xml_interface.h" namespace openmc { @@ -19,8 +20,13 @@ extern std::unordered_map> extern std::unordered_map 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. //============================================================================== diff --git a/include/openmc/material.h b/include/openmc/material.h index b251a3ca8..81f4e1421 100644 --- a/include/openmc/material.h +++ b/include/openmc/material.h @@ -221,6 +221,10 @@ double density_effect(const vector& f, const vector& 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 diff --git a/include/openmc/plot.h b/include/openmc/plot.h index 650b7e16a..a415b1747 100644 --- a/include/openmc/plot.h +++ b/include/openmc/plot.h @@ -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(); diff --git a/include/openmc/settings.h b/include/openmc/settings.h index 1e061b235..cd0ab477c 100644 --- a/include/openmc/settings.h +++ b/include/openmc/settings.h @@ -127,9 +127,12 @@ extern double weight_survive; //!< Survival weight after Russian roulette //============================================================================== //! Read settings from XML file -//! \param[in] root XML node for void read_settings_xml(); +//! Read settings from XML node +//! \param[in] root XML node for +void read_settings_xml(pugi::xml_node root); + void free_memory_settings(); } // namespace openmc diff --git a/include/openmc/tallies/tally.h b/include/openmc/tallies/tally.h index 3cead91dc..56a51370a 100644 --- a/include/openmc/tallies/tally.h +++ b/include/openmc/tallies/tally.h @@ -48,10 +48,10 @@ public: void set_nuclides(const vector& nuclides); //! returns vector of indices corresponding to the tally this is called on - const vector& filters() const { return filters_; } + const vector& 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 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(); diff --git a/src/cross_sections.cpp b/src/cross_sections.cpp index afb0a1f36..2094fd551 100644 --- a/src/cross_sections.cpp +++ b/src/cross_sections.cpp @@ -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 diff --git a/src/geometry_aux.cpp b/src/geometry_aux.cpp index 261471984..83e8494de 100644 --- a/src/geometry_aux.cpp +++ b/src/geometry_aux.cpp @@ -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); diff --git a/src/initialize.cpp b/src/initialize.cpp index aa353aa9c..44f84034a 100644 --- a/src/initialize.cpp +++ b/src/initialize.cpp @@ -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" @@ -291,10 +292,53 @@ int parse_command_line(int argc, char* argv[]) void read_input_xml() { - read_settings_xml(); + + // search for a single model.xml file + // Check if settings.xml exists + std::string model_filename = settings::path_input + "model.xml"; + bool use_model_file = file_exists(model_filename); + pugi::xml_node model_root; + if (use_model_file) { + write_message("Found model.xml file."); + pugi::xml_document doc; + auto result = doc.load_file(model_filename.c_str()); + if (!result) { + fatal_error("Error processing model.xml file."); + } + auto model_root = doc.document_element(); + + 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("A '{}' file is also present. This file will be ignored.", input)); + } + } + } else { + write_message("No model.xml found. Reading separate XML inputs..."); + } + if (use_model_file) { + if (!check_for_node(model_root, "settings")) { + fatal_error("No node present in the model.xml file."); + } + read_settings_xml(model_root.child("settings")); + } else { + read_settings_xml(); + } + read_cross_sections_xml(); - read_materials_xml(); - read_geometry_xml(); + if (use_model_file) { + if (!check_for_node(model_root, "materials")) { + fatal_error("No node present in the model.xml file."); + } + read_materials_xml(model_root.child("materials")); + if (!check_for_node(model_root, "geometry")) { + fatal_error("No node present in the model.xml_file."); + } + read_geometry_xml(model_root.child("geometry")); + } else { + read_materials_xml(); + read_geometry_xml(); + } // Final geometry setup and assign temperatures finalize_geometry(); @@ -302,14 +346,23 @@ void read_input_xml() // Finalize cross sections having assigned temperatures finalize_cross_sections(); - read_tallies_xml(); + if (use_model_file && check_for_node(model_root, "tallies")) { + read_tallies_xml(model_root.child("tallies")); + } else { + read_tallies_xml(); + } // Initialize distribcell_filters prepare_distribcell(); // Read the plots.xml regardless of plot mode in case plots are requested // via the API - read_plots_xml(); + if (use_model_file) { + if (check_for_node(model_root, "plots")) + read_plots_xml(model_root.child("plots")); + } else { + read_plots_xml(); + } if (settings::run_mode == RunMode::PLOTTING) { // Read plots.xml if it exists if (mpi::master && settings::verbosity >= 5) diff --git a/src/material.cpp b/src/material.cpp index 30dfa5ed5..f2e23aede 100644 --- a/src/material.cpp +++ b/src/material.cpp @@ -1264,8 +1264,7 @@ double density_effect(const vector& f, const vector& 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_node)); } diff --git a/src/plot.cpp b/src/plot.cpp index b012ed131..e28543cdf 100644 --- a/src/plot.cpp +++ b/src/plot.cpp @@ -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,10 @@ void read_plots_xml() doc.load_file(filename.c_str()); pugi::xml_node root = doc.document_element(); +} + +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; diff --git a/src/settings.cpp b/src/settings.cpp index eb0d4936c..710fb2810 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -211,13 +211,11 @@ 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( @@ -245,6 +243,14 @@ void read_settings_xml() // Get root element xml_node root = doc.document_element(); + read_settings_xml(root); +} + +void read_settings_xml(pugi::xml_node root) +{ + using namespace settings; + using namespace pugi; + // Verbosity if (check_for_node(root, "verbosity")) { verbosity = std::stoi(get_node_value(root, "verbosity")); diff --git a/src/tallies/tally.cpp b/src/tallies/tally.cpp index 51194684a..1c1f274d7 100644 --- a/src/tallies/tally.cpp +++ b/src/tallies/tally.cpp @@ -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 setting if (check_for_node(root, "assume_separate")) { settings::assume_separate = get_node_value_bool(root, "assume_separate"); From cdd2a6b1cacb54387de050e96c36bca74c114777 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Thu, 3 Nov 2022 20:43:29 -0500 Subject: [PATCH 06/53] Correcting doc scope and some reads --- src/initialize.cpp | 10 ++++++---- src/settings.cpp | 2 ++ 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/src/initialize.cpp b/src/initialize.cpp index 44f84034a..cb35ebce9 100644 --- a/src/initialize.cpp +++ b/src/initialize.cpp @@ -298,14 +298,14 @@ void read_input_xml() std::string model_filename = settings::path_input + "model.xml"; bool use_model_file = file_exists(model_filename); pugi::xml_node model_root; + pugi::xml_document doc; if (use_model_file) { - write_message("Found model.xml file."); - pugi::xml_document doc; + write_message("Reading model.xml..."); auto result = doc.load_file(model_filename.c_str()); if (!result) { fatal_error("Error processing model.xml file."); } - auto model_root = doc.document_element(); + model_root = doc.document_element(); auto other_inputs = {"materials.xml", "geometry.xml", "settings.xml", "tallies.xml", "plots.xml"}; for (const auto& input : other_inputs) { @@ -320,12 +320,14 @@ void read_input_xml() if (!check_for_node(model_root, "settings")) { fatal_error("No node present in the model.xml file."); } - read_settings_xml(model_root.child("settings")); + auto settings_root = model_root.child("settings"); + read_settings_xml(); } else { read_settings_xml(); } read_cross_sections_xml(); + if (use_model_file) { if (!check_for_node(model_root, "materials")) { fatal_error("No node present in the model.xml file."); diff --git a/src/settings.cpp b/src/settings.cpp index 710fb2810..de24e200a 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -266,6 +266,7 @@ void read_settings_xml(pugi::xml_node root) // Find if a multi-group or continuous-energy simulation is desired if (check_for_node(root, "energy_mode")) { + std::cout << "Here" << std::endl; std::string temp_str = get_node_value(root, "energy_mode", true, true); if (temp_str == "mg" || temp_str == "multi-group") { run_CE = false; @@ -273,6 +274,7 @@ void read_settings_xml(pugi::xml_node root) run_CE = true; } } + std::cout << "Here" << std::endl; // Look for deprecated cross_sections.xml file in settings.xml if (check_for_node(root, "cross_sections")) { From bec9bf6f20805e12134e798aca98b90dd8e71d23 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Thu, 3 Nov 2022 21:18:04 -0500 Subject: [PATCH 07/53] Refactoring model read into it's own function. --- include/openmc/initialize.h | 1 + src/initialize.cpp | 148 +++++++++++++++++++++++------------- src/settings.cpp | 19 +++-- 3 files changed, 106 insertions(+), 62 deletions(-) diff --git a/include/openmc/initialize.h b/include/openmc/initialize.h index 869be4441..47474c986 100644 --- a/include/openmc/initialize.h +++ b/include/openmc/initialize.h @@ -11,6 +11,7 @@ int parse_command_line(int argc, char* argv[]); #ifdef OPENMC_MPI void initialize_mpi(MPI_Comm intracomm); #endif +bool read_model_xml(); void read_input_xml(); } // namespace openmc diff --git a/src/initialize.cpp b/src/initialize.cpp index cb35ebce9..254ad88da 100644 --- a/src/initialize.cpp +++ b/src/initialize.cpp @@ -290,81 +290,125 @@ int parse_command_line(int argc, char* argv[]) return 0; } -void read_input_xml() -{ - - // search for a single model.xml file - // Check if settings.xml exists +bool read_model_xml() { + // get verbosity from settings node std::string model_filename = settings::path_input + "model.xml"; bool use_model_file = file_exists(model_filename); - pugi::xml_node model_root; + if (!file_exists(model_filename)) return false; + + // attempt to open the document pugi::xml_document doc; - if (use_model_file) { - write_message("Reading model.xml..."); - auto result = doc.load_file(model_filename.c_str()); - if (!result) { - fatal_error("Error processing model.xml file."); - } - model_root = doc.document_element(); - - 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("A '{}' file is also present. This file will be ignored.", input)); - } - } - } else { - write_message("No model.xml found. Reading separate XML inputs..."); + auto result = doc.load_file(model_filename.c_str()); + if (!result) { + fatal_error("Error processing model.xml file."); } - if (use_model_file) { - if (!check_for_node(model_root, "settings")) { + pugi::xml_node root = doc.document_element(); + + // Read settings + if (!check_for_node(root, "settings")) { fatal_error("No node present in the model.xml file."); - } - auto settings_root = model_root.child("settings"); - read_settings_xml(); - } else { - read_settings_xml(); + } + auto settings_root = root.child("settings"); + + // Verbosity + if (check_for_node(settings_root, "verbosity")) { + settings::verbosity = std::stoi(get_node_value(settings_root, "verbosity")); } - read_cross_sections_xml(); - - if (use_model_file) { - if (!check_for_node(model_root, "materials")) { - fatal_error("No node present in the model.xml file."); - } - read_materials_xml(model_root.child("materials")); - if (!check_for_node(model_root, "geometry")) { - fatal_error("No node present in the model.xml_file."); - } - read_geometry_xml(model_root.child("geometry")); - } else { - read_materials_xml(); - read_geometry_xml(); + // 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("Reading model XML file...", 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(("Other XML file input(s) are present. These file will be ignored in favor of the model.xml file.")); + break; + } + } + + // Read materials and cross sections + if (!check_for_node(root, "materials")) { + fatal_error("No node present in the model.xml file."); + } + auto materials_node = root.child("materials"); + read_cross_sections_xml(materials_node); + read_materials_xml(root.child("materials")); + + // Read geometry + if (!check_for_node(root, "geometry")) { + fatal_error("No node present in the model.xml_file."); + } + read_geometry_xml(root.child("geometry")); + // Final geometry setup and assign temperatures finalize_geometry(); // Finalize cross sections having assigned temperatures finalize_cross_sections(); - if (use_model_file && check_for_node(model_root, "tallies")) { - read_tallies_xml(model_root.child("tallies")); + 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")); + + if (settings::run_mode == RunMode::PLOTTING) { + // Read plots.xml if it exists + if (mpi::master && settings::verbosity >= 5) + print_plot(); + } else { - read_tallies_xml(); + // Write summary information + if (mpi::master && settings::output_summary) + write_summary(); + + // Warn if overlap checking is on + if (mpi::master && settings::check_overlaps) { + warning("Cell overlap checking is ON."); + } } + return true; +} + +void read_input_xml() +{ + // attempt to reach the model.xml file if present + if (read_model_xml()) return; + + read_settings_xml(); + read_cross_sections_xml(); + read_materials_xml(); + read_geometry_xml(); + + // Final geometry setup and assign temperatures + finalize_geometry(); + + // Finalize cross sections having assigned temperatures + finalize_cross_sections(); + + read_tallies_xml(); + // Initialize distribcell_filters prepare_distribcell(); // Read the plots.xml regardless of plot mode in case plots are requested // via the API - if (use_model_file) { - if (check_for_node(model_root, "plots")) - read_plots_xml(model_root.child("plots")); - } else { - read_plots_xml(); - } + read_plots_xml(); + if (settings::run_mode == RunMode::PLOTTING) { // Read plots.xml if it exists if (mpi::master && settings::verbosity >= 5) diff --git a/src/settings.cpp b/src/settings.cpp index de24e200a..3048862e3 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -243,14 +243,6 @@ void read_settings_xml() { // Get root element xml_node root = doc.document_element(); - read_settings_xml(root); -} - -void read_settings_xml(pugi::xml_node root) -{ - using namespace settings; - using namespace pugi; - // Verbosity if (check_for_node(root, "verbosity")) { verbosity = std::stoi(get_node_value(root, "verbosity")); @@ -262,11 +254,19 @@ void read_settings_xml(pugi::xml_node root) 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::cout << "Here" << std::endl; std::string temp_str = get_node_value(root, "energy_mode", true, true); if (temp_str == "mg" || temp_str == "multi-group") { run_CE = false; @@ -274,7 +274,6 @@ void read_settings_xml(pugi::xml_node root) run_CE = true; } } - std::cout << "Here" << std::endl; // Look for deprecated cross_sections.xml file in settings.xml if (check_for_node(root, "cross_sections")) { From 81aa653d506bf3e9f9288679c8e92a6f21b6ad46 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Thu, 3 Nov 2022 21:22:41 -0500 Subject: [PATCH 08/53] Factoring out some common lines between reader functions --- include/openmc/initialize.h | 1 + src/initialize.cpp | 22 ++++++---------------- 2 files changed, 7 insertions(+), 16 deletions(-) diff --git a/include/openmc/initialize.h b/include/openmc/initialize.h index 47474c986..d3d12d45d 100644 --- a/include/openmc/initialize.h +++ b/include/openmc/initialize.h @@ -13,6 +13,7 @@ void initialize_mpi(MPI_Comm intracomm); #endif bool read_model_xml(); void read_input_xml(); +void initial_output(); } // namespace openmc diff --git a/src/initialize.cpp b/src/initialize.cpp index 254ad88da..aa3e15efa 100644 --- a/src/initialize.cpp +++ b/src/initialize.cpp @@ -108,6 +108,9 @@ int openmc_init(int argc, char* argv[], const void* intracomm) // Read XML input files read_input_xml(); + // Write some initial output under the header if needed + initial_output(); + // Check for particle restart run if (settings::particle_restart_run) settings::run_mode = RunMode::PARTICLE; @@ -365,22 +368,6 @@ bool read_model_xml() { if (check_for_node(root, "plots")) read_plots_xml(root.child("plots")); - if (settings::run_mode == RunMode::PLOTTING) { - // Read plots.xml if it exists - if (mpi::master && settings::verbosity >= 5) - print_plot(); - - } else { - // Write summary information - if (mpi::master && settings::output_summary) - write_summary(); - - // Warn if overlap checking is on - if (mpi::master && settings::check_overlaps) { - warning("Cell overlap checking is ON."); - } - } - return true; } @@ -408,7 +395,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() { + // handle some final output if (settings::run_mode == RunMode::PLOTTING) { // Read plots.xml if it exists if (mpi::master && settings::verbosity >= 5) From 9391f17cff9a798c048ec62b9eed3a9188baa534 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Thu, 3 Nov 2022 22:29:36 -0500 Subject: [PATCH 09/53] Adding import for single or multiple XMLs --- openmc/geometry.py | 46 +++++++++++----- openmc/material.py | 40 ++++++++++---- openmc/model/model.py | 37 ++++++++++++- openmc/plots.py | 28 ++++++++-- openmc/settings.py | 119 ++++++++++++++++++++++++------------------ openmc/tallies.py | 72 +++++++++++++++---------- 6 files changed, 232 insertions(+), 110 deletions(-) diff --git a/openmc/geometry.py b/openmc/geometry.py index 889cb6b31..b4f8046bd 100644 --- a/openmc/geometry.py +++ b/openmc/geometry.py @@ -162,13 +162,13 @@ class Geometry: tree.write(str(p), xml_declaration=True, encoding='utf-8') @classmethod - def from_xml(cls, path='geometry.xml', materials=None): - """Generate geometry from XML file + def from_xml_element(cls, elem, materials=None): + """Generate geometry from an XML element Parameters ---------- - path : str, optional - Path to geometry XML file + 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. @@ -187,13 +187,10 @@ class Geometry: 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'): + for surface in elem.findall('surface'): s = openmc.Surface.from_xml_element(surface) surfaces[s.id] = s @@ -207,15 +204,15 @@ class Geometry: surfaces[s1].periodic_surface = surfaces[s2] # Add any DAGMC universes - for elem in root.findall('dagmc_universe'): + for elem in elem.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) + # contain it (needed to determine which universe is the elem) child_of = defaultdict(list) - for elem in root.findall('lattice'): + for elem in elem.findall('lattice'): lat = openmc.RectLattice.from_xml_element(elem, get_universe) universes[lat.id] = lat if lat.outer is not None: @@ -223,7 +220,7 @@ class Geometry: for u in lat.universes.ravel(): child_of[u].append(lat) - for elem in root.findall('hex_lattice'): + for elem in elem.findall('hex_lattice'): lat = openmc.HexLattice.from_xml_element(elem, get_universe) universes[lat.id] = lat if lat.outer is not None: @@ -245,7 +242,7 @@ class Geometry: mats = {str(m.id): m for m in materials} mats['void'] = None - for elem in root.findall('cell'): + for elem in elem.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) @@ -258,6 +255,29 @@ class Geometry: else: raise ValueError('Error determining root universe.') + @classmethod + def from_xml(cls, path='geometry.xml', materials=None): + """Generate geometry from XML file + + Parameters + ---------- + path : str, optional + Path to geometry XML file + 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 + + """ + tree = ET.parse(path) + root = tree.getroot() + + return cls.from_xml_element(root, materials) + def find(self, point): """Find cells/universes/lattices which contain a given point diff --git a/openmc/material.py b/openmc/material.py index 9e3951ccf..c33596800 100644 --- a/openmc/material.py +++ b/openmc/material.py @@ -1508,6 +1508,34 @@ class Materials(cv.CheckedList): errors='xmlcharrefreplace') as fh: self._write_xml(fh) + @classmethod + def from_xml_element(cls, elem): + """Generate materials collection from XML file + + Parameters + ---------- + elem : xml.etree.ElementTree.Element + XML element + + Returns + ------- + openmc.Materials + Materials collection + + """ + # 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'): """Generate materials collection from XML file @@ -1526,14 +1554,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) \ No newline at end of file diff --git a/openmc/model/model.py b/openmc/model/model.py index e3c4c4ae1..2193084c3 100644 --- a/openmc/model/model.py +++ b/openmc/model/model.py @@ -208,7 +208,42 @@ class Model: self._plots.append(plot) @classmethod - def from_xml(cls, geometry='geometry.xml', materials='materials.xml', + def from_xml(cls, separate_xmls=True, **kwargs): + if separate_xmls: + return cls.from_separate_xmls(**kwargs) + else: + return cls.from_model_xml(**kwargs) + + @classmethod + def from_model_xml(cls, path='model.xml'): + """Create model from single XML file + + Parameters + ---------- + path : str or Pathlike + Path to model.xml file + """ + tree = ET.parse(path) + root = tree.getroot() + + model = cls() + + model.settings = openmc.Settings.from_xml_element(root.find('settings')) + model.materials = openmc.Materials.from_xml_element(root.find('materials')) + model.geometry = \ + openmc.Geometry.from_xml_element(root.find('geometry'), model.materials) + + if tally_node := root.find('tallies'): + print(tally_node) + model.tallies = openmc.Tallies.from_xml_element(tally_node) + + if plots_node := root.find('plots'): + model.plots = openmc.Plots.from_xml_element(plots_node) + + return model + + @classmethod + def from_separate_xmls(cls, geometry='geometry.xml', materials='materials.xml', settings='settings.xml', tallies='tallies.xml', plots='plots.xml'): """Create model from existing XML files diff --git a/openmc/plots.py b/openmc/plots.py index 52dcf3fc1..badea2e58 100644 --- a/openmc/plots.py +++ b/openmc/plots.py @@ -941,6 +941,27 @@ class Plots(cv.CheckedList): 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 elem in elem.findall('plot'): + plots.append(Plot.from_xml_element(elem)) + return plots + @classmethod def from_xml(cls, path='plots.xml'): """Generate plots collection from XML file @@ -958,9 +979,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 diff --git a/openmc/settings.py b/openmc/settings.py index dd0041351..c2fca98f7 100644 --- a/openmc/settings.py +++ b/openmc/settings.py @@ -1595,7 +1595,7 @@ class Settings: clean_indentation(element) return element - + def export_to_xml(self, path: PathLike = 'settings.xml'): """Export simulation settings to an XML file. @@ -1617,6 +1617,71 @@ class Settings: tree = ET.ElementTree(root_element) tree.write(str(p), xml_declaration=True, encoding='utf-8') + @classmethod + def from_xml_element(cls, elem): + """Generate settings from XML element + + Parameters + ---------- + elem : xml.etree.ElementTree.Element + XML element + + 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) + 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) + 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) + 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 @@ -1636,54 +1701,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) diff --git a/openmc/tallies.py b/openmc/tallies.py index dfb0c98bf..0355568ec 100644 --- a/openmc/tallies.py +++ b/openmc/tallies.py @@ -3191,6 +3191,49 @@ class Tallies(cv.CheckedList): tree = ET.ElementTree(root_element) tree.write(str(p), xml_declaration=True, encoding='utf-8') + @classmethod + def from_xml_element(cls, elem): + """Generate tallies from an XML element + + Parameters + ---------- + elem : xml.etree.ElementTree.Element + XML element + + Returns + ------- + openmc.Tallies + Tallies object + + """ + # Read mesh elements + meshes = {} + for elem in elem.findall('mesh'): + mesh = MeshBase.from_xml_element(elem) + meshes[mesh.id] = mesh + + # Read filter elements + filters = {} + for elem in elem.findall('filter'): + filter = openmc.Filter.from_xml_element(elem, meshes=meshes) + filters[filter.id] = filter + + # Read derivative elements + derivatives = {} + for elem in elem.findall('derivative'): + deriv = openmc.TallyDerivative.from_xml_element(elem) + derivatives[deriv.id] = deriv + + # Read tally elements + tallies = [] + for elem in elem.findall('tally'): + tally = openmc.Tally.from_xml_element( + elem, filters=filters, derivatives=derivatives + ) + tallies.append(tally) + + return cls(tallies) + @classmethod def from_xml(cls, path='tallies.xml'): """Generate tallies from XML file @@ -3208,31 +3251,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) From af6b0af1e3ab2fc84224a12e55928cc0030a27d4 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Thu, 3 Nov 2022 22:41:28 -0500 Subject: [PATCH 10/53] Fix loop variable overwrite --- openmc/tallies.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/openmc/tallies.py b/openmc/tallies.py index 0355568ec..2996f1b03 100644 --- a/openmc/tallies.py +++ b/openmc/tallies.py @@ -3208,27 +3208,27 @@ class Tallies(cv.CheckedList): """ # Read mesh elements meshes = {} - for elem in elem.findall('mesh'): - mesh = MeshBase.from_xml_element(elem) + for e in elem.findall('mesh'): + mesh = MeshBase.from_xml_element(e) meshes[mesh.id] = mesh # Read filter elements filters = {} - for elem in elem.findall('filter'): - filter = openmc.Filter.from_xml_element(elem, meshes=meshes) + for e in elem.findall('filter'): + filter = openmc.Filter.from_xml_element(e, meshes=meshes) filters[filter.id] = filter # Read derivative elements derivatives = {} - for elem in elem.findall('derivative'): - deriv = openmc.TallyDerivative.from_xml_element(elem) + for e in elem.findall('derivative'): + deriv = openmc.TallyDerivative.from_xml_element(e) derivatives[deriv.id] = deriv # Read tally elements tallies = [] - for elem in elem.findall('tally'): + for e in elem.findall('tally'): tally = openmc.Tally.from_xml_element( - elem, filters=filters, derivatives=derivatives + e, filters=filters, derivatives=derivatives ) tallies.append(tally) From 5ac6e87a68c0666ef41ef1c6d9b60aa19500b1a5 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Thu, 3 Nov 2022 23:15:14 -0500 Subject: [PATCH 11/53] Properly call read_plots_xml --- src/plot.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/plot.cpp b/src/plot.cpp index e28543cdf..1a62fe263 100644 --- a/src/plot.cpp +++ b/src/plot.cpp @@ -140,6 +140,8 @@ 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) From 2e716589a14d2465ab4a494e12ddf86172af72e3 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Thu, 3 Nov 2022 23:57:22 -0500 Subject: [PATCH 12/53] Other bug fixes --- openmc/geometry.py | 32 ++++++++++++++++---------------- openmc/model/model.py | 6 +++--- openmc/plots.py | 7 ++++--- 3 files changed, 23 insertions(+), 22 deletions(-) diff --git a/openmc/geometry.py b/openmc/geometry.py index b4f8046bd..d036df9c7 100644 --- a/openmc/geometry.py +++ b/openmc/geometry.py @@ -204,24 +204,24 @@ class Geometry: surfaces[s1].periodic_surface = surfaces[s2] # Add any DAGMC universes - for elem in elem.findall('dagmc_universe'): - dag_univ = openmc.DAGMCUniverse.from_xml_element(elem) + 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 elem in elem.findall('lattice'): - lat = openmc.RectLattice.from_xml_element(elem, get_universe) + 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 elem in elem.findall('hex_lattice'): - lat = openmc.HexLattice.from_xml_element(elem, get_universe) + 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) @@ -235,15 +235,8 @@ class Geometry: 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 elem.findall('cell'): - c = openmc.Cell.from_xml_element(elem, surfaces, mats, get_universe) + for e in elem.findall('cell'): + c = openmc.Cell.from_xml_element(e, surfaces, materials, get_universe) if c.fill_type in ('universe', 'lattice'): child_of[c.fill].append(c) @@ -276,7 +269,14 @@ class Geometry: tree = ET.parse(path) root = tree.getroot() - return cls.from_xml_element(root, materials) + # 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 + + return cls.from_xml_element(root, mats) def find(self, point): """Find cells/universes/lattices which contain a given point diff --git a/openmc/model/model.py b/openmc/model/model.py index 2193084c3..a839dc8f0 100644 --- a/openmc/model/model.py +++ b/openmc/model/model.py @@ -208,11 +208,11 @@ class Model: self._plots.append(plot) @classmethod - def from_xml(cls, separate_xmls=True, **kwargs): + def from_xml(cls, *args, separate_xmls=True, **kwargs): if separate_xmls: - return cls.from_separate_xmls(**kwargs) + return cls.from_separate_xmls(*args, **kwargs) else: - return cls.from_model_xml(**kwargs) + return cls.from_model_xml(*args, **kwargs) @classmethod def from_model_xml(cls, path='model.xml'): diff --git a/openmc/plots.py b/openmc/plots.py index badea2e58..b1f192787 100644 --- a/openmc/plots.py +++ b/openmc/plots.py @@ -919,6 +919,7 @@ 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 @@ -936,8 +937,8 @@ class Plots(cv.CheckedList): 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') @@ -958,8 +959,8 @@ class Plots(cv.CheckedList): """ # Generate each plot plots = cls() - for elem in elem.findall('plot'): - plots.append(Plot.from_xml_element(elem)) + for e in elem.findall('plot'): + plots.append(Plot.from_xml_element(e)) return plots @classmethod From b4dcc30f92c4b8ff29b77505cdf654fa7eff7181 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Fri, 4 Nov 2022 00:58:43 -0500 Subject: [PATCH 13/53] Removing walrus operator --- openmc/model/model.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/openmc/model/model.py b/openmc/model/model.py index a839dc8f0..bc8f0b531 100644 --- a/openmc/model/model.py +++ b/openmc/model/model.py @@ -233,12 +233,11 @@ class Model: model.geometry = \ openmc.Geometry.from_xml_element(root.find('geometry'), model.materials) - if tally_node := root.find('tallies'): - print(tally_node) - model.tallies = openmc.Tallies.from_xml_element(tally_node) + if root.find('tallies'): + model.tallies = openmc.Tallies.from_xml_element(root.find('tallies')) - if plots_node := root.find('plots'): - model.plots = openmc.Plots.from_xml_element(plots_node) + if root.find('plots'): + model.plots = openmc.Plots.from_xml_element(root.find('plots')) return model From 611475a835be98beee0082727e94660b3fcd3000 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Fri, 4 Nov 2022 22:43:25 -0500 Subject: [PATCH 14/53] Restructureing input function calls a little --- src/initialize.cpp | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/src/initialize.cpp b/src/initialize.cpp index aa3e15efa..3f6e69ecd 100644 --- a/src/initialize.cpp +++ b/src/initialize.cpp @@ -106,7 +106,7 @@ 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_input_xml(); // Write some initial output under the header if needed initial_output(); @@ -373,9 +373,6 @@ bool read_model_xml() { void read_input_xml() { - // attempt to reach the model.xml file if present - if (read_model_xml()) return; - read_settings_xml(); read_cross_sections_xml(); read_materials_xml(); From 021bb280c9b4cad713ecdd516c9ef8b9ee97bd2c Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Fri, 4 Nov 2022 22:45:37 -0500 Subject: [PATCH 15/53] Adding initial test for exporting model.xmls. Correcting material maps --- openmc/model/model.py | 3 ++- tests/unit_tests/test_model.py | 21 +++++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/openmc/model/model.py b/openmc/model/model.py index bc8f0b531..6950642e0 100644 --- a/openmc/model/model.py +++ b/openmc/model/model.py @@ -230,8 +230,9 @@ class Model: model.settings = openmc.Settings.from_xml_element(root.find('settings')) model.materials = openmc.Materials.from_xml_element(root.find('materials')) + materials = {str(m.id): m for m in model.materials} model.geometry = \ - openmc.Geometry.from_xml_element(root.find('geometry'), model.materials) + openmc.Geometry.from_xml_element(root.find('geometry'), materials) if root.find('tallies'): model.tallies = openmc.Tallies.from_xml_element(root.find('tallies')) diff --git a/tests/unit_tests/test_model.py b/tests/unit_tests/test_model.py index 9b05ed2a5..ce81b00ee 100644 --- a/tests/unit_tests/test_model.py +++ b/tests/unit_tests/test_model.py @@ -7,6 +7,8 @@ import pytest import openmc import openmc.lib +from .. import cdtemp + @pytest.fixture(scope='function') def pin_model_attributes(): @@ -529,3 +531,22 @@ 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(): + + with cdtemp(): + # 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_xml(separate_xmls=False) + new_model = openmc.Model.from_xml(separate_xmls=False) + + # make sure we can also export this again to separate + # XML files + new_model.export_to_xml(separate_xmls=True) \ No newline at end of file From abd3d515b0906363ce1975ffadd983c6c5a86f2b Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Fri, 4 Nov 2022 22:46:11 -0500 Subject: [PATCH 16/53] Correcting doc strings for material mapping sent to --- openmc/cell.py | 2 +- openmc/geometry.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/openmc/cell.py b/openmc/cell.py index 0cea73b32..d03052623 100644 --- a/openmc/cell.py +++ b/openmc/cell.py @@ -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 diff --git a/openmc/geometry.py b/openmc/geometry.py index d036df9c7..57aab1884 100644 --- a/openmc/geometry.py +++ b/openmc/geometry.py @@ -256,9 +256,9 @@ class Geometry: ---------- path : str, optional Path to geometry XML file - 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. + materials : dict + Dictionary mapping material ID strings to :class:`openmc.Material` + instances (defined in :math:`openmc.Geometry.from_xml`) Returns ------- From 662d83a7dd72240b4da8594319bab416977ec71b Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Wed, 9 Nov 2022 23:29:17 -0600 Subject: [PATCH 17/53] Running some existing regression tests using single XML file --- .../adj_cell_rotation/__init__.py | 2 + .../regression_tests/energy_laws/__init__.py | 1 + .../lattice_multiple/__init__.py | 1 + tests/regression_tests/model_xml/__init__.py | 0 .../model_xml/inputs_true.dat | 38 ++++++++++ tests/regression_tests/model_xml/test.py | 76 +++++++++++++++++++ .../photon_production/__init__.py | 1 + 7 files changed, 119 insertions(+) create mode 100644 tests/regression_tests/model_xml/__init__.py create mode 100644 tests/regression_tests/model_xml/inputs_true.dat create mode 100644 tests/regression_tests/model_xml/test.py diff --git a/tests/regression_tests/adj_cell_rotation/__init__.py b/tests/regression_tests/adj_cell_rotation/__init__.py index e69de29bb..7efe44865 100644 --- a/tests/regression_tests/adj_cell_rotation/__init__.py +++ b/tests/regression_tests/adj_cell_rotation/__init__.py @@ -0,0 +1,2 @@ + +from .test import model \ No newline at end of file diff --git a/tests/regression_tests/energy_laws/__init__.py b/tests/regression_tests/energy_laws/__init__.py index e69de29bb..29fbfcc0c 100644 --- a/tests/regression_tests/energy_laws/__init__.py +++ b/tests/regression_tests/energy_laws/__init__.py @@ -0,0 +1 @@ +from .test import model \ No newline at end of file diff --git a/tests/regression_tests/lattice_multiple/__init__.py b/tests/regression_tests/lattice_multiple/__init__.py index e69de29bb..29fbfcc0c 100644 --- a/tests/regression_tests/lattice_multiple/__init__.py +++ b/tests/regression_tests/lattice_multiple/__init__.py @@ -0,0 +1 @@ +from .test import model \ No newline at end of file diff --git a/tests/regression_tests/model_xml/__init__.py b/tests/regression_tests/model_xml/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/tests/regression_tests/model_xml/inputs_true.dat b/tests/regression_tests/model_xml/inputs_true.dat new file mode 100644 index 000000000..5aedbfa14 --- /dev/null +++ b/tests/regression_tests/model_xml/inputs_true.dat @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + eigenvalue + 10000 + 10 + 5 + + + -4.0 -4.0 -4.0 4.0 4.0 4.0 + + + + diff --git a/tests/regression_tests/model_xml/test.py b/tests/regression_tests/model_xml/test.py new file mode 100644 index 000000000..448079b3f --- /dev/null +++ b/tests/regression_tests/model_xml/test.py @@ -0,0 +1,76 @@ +from difflib import unified_diff +import filecmp +import os + +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 import model as adj_cell_rotation_model +from ..lattice_multiple import model as lattice_mult_model +from ..energy_laws import model as energy_laws_model +from ..photon_production import model as photon_prod_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_xml(separate_xmls=False) + + def _get_inputs(self): + return open('model.xml').read() + + def _compare_inputs(self): + """Skip input comparisons for now + """ + pass + + 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') + + +models = [ +'adj_cell_rotation_model', +'lattice_mult_model', +'energy_laws_model', +'photon_prod_model' +] +paths = [ +'../adj_cell_rotation', +'../lattice_multiple', +'../energy_laws', +'../photon_production' +] + + +@pytest.mark.parametrize("model, path", zip(models, paths)) +def test_model_xml(model, path, request): + inputs = path + "/inputs_true.dat" + results = path + "/results_true.dat" + harness = ModelXMLTestHarness(request.getfixturevalue(model), inputs, results) + harness.main() \ No newline at end of file diff --git a/tests/regression_tests/photon_production/__init__.py b/tests/regression_tests/photon_production/__init__.py index e69de29bb..29fbfcc0c 100644 --- a/tests/regression_tests/photon_production/__init__.py +++ b/tests/regression_tests/photon_production/__init__.py @@ -0,0 +1 @@ +from .test import model \ No newline at end of file From 1e7dbe99cf7d6c262a9900930836f5786f519f9a Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Thu, 10 Nov 2022 00:01:40 -0600 Subject: [PATCH 18/53] Making sure meshes are only written once to a model.xml file --- openmc/model/model.py | 18 ++++++++++++------ openmc/settings.py | 15 ++++++++++----- openmc/tallies.py | 24 ++++++++++++------------ 3 files changed, 34 insertions(+), 23 deletions(-) diff --git a/openmc/model/model.py b/openmc/model/model.py index 6950642e0..cb84f2c17 100644 --- a/openmc/model/model.py +++ b/openmc/model/model.py @@ -5,11 +5,8 @@ import os from pathlib import Path from numbers import Integral from tempfile import NamedTemporaryFile -<<<<<<< HEAD import warnings -======= from xml.etree import ElementTree as ET ->>>>>>> d42935a08 (Writing all main nodes to a single XML file) import h5py @@ -234,8 +231,16 @@ class Model: model.geometry = \ openmc.Geometry.from_xml_element(root.find('geometry'), materials) + # gather meshses from other classes before reading the tally node + meshes = {} + if model.settings.entropy_mesh is not None: + meshes[model.settings.entropy_mesh.id] = model.settings.entropy_mesh + + for ww in model.settings.weight_windows: + meshes[ww.mesh.id] = ww.mesh + if root.find('tallies'): - model.tallies = openmc.Tallies.from_xml_element(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')) @@ -524,7 +529,8 @@ class Model: # Can be used to modify tallies in case any surfaces are redundant redundant_surfaces = self.geometry.remove_redundant_surfaces() - settings_element = self.settings.to_xml_element() + memo = set() + settings_element = self.settings.to_xml_element(memo) geometry_element = self.geometry.to_xml_element() # If a materials collection was specified, export it. Otherwise, look @@ -549,7 +555,7 @@ class Model: ET.ElementTree(settings_element).write(fh, encoding='unicode') if self.tallies: - tallies_element = self.tallies.to_xml_element() + tallies_element = self.tallies.to_xml_element(memo) ET.ElementTree(tallies_element).write(fh, encoding='unicode') if self.plots: plots_element = self.plots.to_xml_element() diff --git a/openmc/settings.py b/openmc/settings.py index c2fca98f7..74ec0d54a 100644 --- a/openmc/settings.py +++ b/openmc/settings.py @@ -1073,7 +1073,7 @@ class Settings: subelement = ET.SubElement(element, key) subelement.text = str(value) - def _create_entropy_mesh_subelement(self, root): + def _create_entropy_mesh_subelement(self, root, memo=None): 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: @@ -1207,15 +1207,20 @@ 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, memo=None): for ww in self._weight_windows: # Add weight window information root.append(ww.to_xml_element()) + # check the memo for a mesh + if memo and ww.mesh.id in memo: + continue + # See if a 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 memo is not None: memo.add(ww.mesh.id) if self._weight_windows_on is not None: elem = ET.SubElement(root, "weight_windows_on") @@ -1539,7 +1544,7 @@ class Settings: if text is not None: self.max_tracks = int(text) - def to_xml_element(self): + def to_xml_element(self, memo=None): """Create a 'settings' element to be written to an XML file. """ @@ -1569,7 +1574,7 @@ class Settings: self._create_seed_subelement(element) self._create_survival_biasing_subelement(element) self._create_cutoff_subelement(element) - self._create_entropy_mesh_subelement(element) + self._create_entropy_mesh_subelement(element, memo) self._create_trigger_subelement(element) self._create_no_reduce_subelement(element) self._create_verbosity_subelement(element) @@ -1587,7 +1592,7 @@ class Settings: 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) + self._create_weight_windows_subelement(element, memo) self._create_max_splits_subelement(element) self._create_max_tracks_subelement(element) diff --git a/openmc/tallies.py b/openmc/tallies.py index 2996f1b03..e37f04e25 100644 --- a/openmc/tallies.py +++ b/openmc/tallies.py @@ -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,11 +3155,11 @@ class Tallies(cv.CheckedList): for d in derivs: root_element.append(d.to_xml_element()) - def to_xml_element(self): + 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) + self._create_mesh_subelements(element, memo) self._create_filter_subelements(element) self._create_tally_subelements(element) self._create_derivative_subelements(element) @@ -3192,7 +3192,7 @@ class Tallies(cv.CheckedList): tree.write(str(p), xml_declaration=True, encoding='utf-8') @classmethod - def from_xml_element(cls, elem): + def from_xml_element(cls, elem, meshes=None): """Generate tallies from an XML element Parameters @@ -3207,7 +3207,7 @@ class Tallies(cv.CheckedList): """ # Read mesh elements - meshes = {} + meshes = {} if meshes is None else meshes for e in elem.findall('mesh'): mesh = MeshBase.from_xml_element(e) meshes[mesh.id] = mesh From d034e1de9c8fe166f38624ee872b8bc46643c66a Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Wed, 23 Nov 2022 21:23:36 -0600 Subject: [PATCH 19/53] Apply suggestions from code review Incorporating suggestions from @paulromano Co-authored-by: Paul Romano --- openmc/geometry.py | 2 +- openmc/model/model.py | 6 ++---- tests/regression_tests/model_xml/test.py | 16 ++++++++-------- tests/unit_tests/test_model.py | 2 -- 4 files changed, 11 insertions(+), 15 deletions(-) diff --git a/openmc/geometry.py b/openmc/geometry.py index 57aab1884..630106628 100644 --- a/openmc/geometry.py +++ b/openmc/geometry.py @@ -256,7 +256,7 @@ class Geometry: ---------- path : str, optional Path to geometry XML file - materials : dict + materials : dict Dictionary mapping material ID strings to :class:`openmc.Material` instances (defined in :math:`openmc.Geometry.from_xml`) diff --git a/openmc/model/model.py b/openmc/model/model.py index cb84f2c17..559def570 100644 --- a/openmc/model/model.py +++ b/openmc/model/model.py @@ -228,8 +228,7 @@ class Model: model.settings = openmc.Settings.from_xml_element(root.find('settings')) model.materials = openmc.Materials.from_xml_element(root.find('materials')) materials = {str(m.id): m for m in model.materials} - model.geometry = \ - openmc.Geometry.from_xml_element(root.find('geometry'), materials) + model.geometry = .Geometry.from_xml_element(root.find('geometry'), materials) # gather meshses from other classes before reading the tally node meshes = {} @@ -542,8 +541,7 @@ class Model: materials = openmc.Materials(self.geometry.get_all_materials() .values()) - with open(d, 'w', encoding='utf-8', - errors='xmlcharrefreplace') as fh: + with open(d, 'w', encoding='utf-8', errors='xmlcharrefreplace') as fh: # write the XML header fh.write("\n") fh.write("\n") diff --git a/tests/regression_tests/model_xml/test.py b/tests/regression_tests/model_xml/test.py index 448079b3f..289bd4655 100644 --- a/tests/regression_tests/model_xml/test.py +++ b/tests/regression_tests/model_xml/test.py @@ -55,16 +55,16 @@ class ModelXMLTestHarness(PyAPITestHarness): models = [ -'adj_cell_rotation_model', -'lattice_mult_model', -'energy_laws_model', -'photon_prod_model' + 'adj_cell_rotation_model', + 'lattice_mult_model', + 'energy_laws_model', + 'photon_prod_model' ] paths = [ -'../adj_cell_rotation', -'../lattice_multiple', -'../energy_laws', -'../photon_production' + '../adj_cell_rotation', + '../lattice_multiple', + '../energy_laws', + '../photon_production' ] diff --git a/tests/unit_tests/test_model.py b/tests/unit_tests/test_model.py index ce81b00ee..928bf0715 100644 --- a/tests/unit_tests/test_model.py +++ b/tests/unit_tests/test_model.py @@ -7,8 +7,6 @@ import pytest import openmc import openmc.lib -from .. import cdtemp - @pytest.fixture(scope='function') def pin_model_attributes(): From b76825fa9f0598ca563d1c9d579968aa7bffa370 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Wed, 23 Nov 2022 22:16:34 -0600 Subject: [PATCH 20/53] Addressing some comments from PR --- include/openmc/initialize.h | 6 ++++- openmc/geometry.py | 6 ++--- openmc/model/model.py | 49 ++++++++++++++++++++++++---------- openmc/plots.py | 6 +++++ src/initialize.cpp | 4 +-- tests/unit_tests/test_model.py | 27 +++++++++---------- 6 files changed, 64 insertions(+), 34 deletions(-) diff --git a/include/openmc/initialize.h b/include/openmc/initialize.h index d3d12d45d..5caccceda 100644 --- a/include/openmc/initialize.h +++ b/include/openmc/initialize.h @@ -11,8 +11,12 @@ int parse_command_line(int argc, char* argv[]); #ifdef OPENMC_MPI void initialize_mpi(MPI_Comm intracomm); #endif + +//! Read material, geometry, settings, and tallies from a single XML file bool read_model_xml(); -void read_input_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 diff --git a/openmc/geometry.py b/openmc/geometry.py index 630106628..d036df9c7 100644 --- a/openmc/geometry.py +++ b/openmc/geometry.py @@ -256,9 +256,9 @@ class Geometry: ---------- path : str, optional Path to geometry XML file - materials : dict - Dictionary mapping material ID strings to :class:`openmc.Material` - instances (defined in :math:`openmc.Geometry.from_xml`) + 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 ------- diff --git a/openmc/model/model.py b/openmc/model/model.py index 559def570..2ed9bf668 100644 --- a/openmc/model/model.py +++ b/openmc/model/model.py @@ -205,8 +205,24 @@ class Model: self._plots.append(plot) @classmethod - def from_xml(cls, *args, separate_xmls=True, **kwargs): - if separate_xmls: + def from_xml(cls, *args, path='model.xml', separate_xmls=True, **kwargs): + """Generate geometry from XML file + + Parameters + ---------- + path : str, optional + Path to model XML file + separate_xmls : bool + Whether or not to read from a single or separate XML files + + Returns + ------- + openmc.Geometry + Geometry object + + """ + + if separate_xmls or not Path(path).exists(): return cls.from_separate_xmls(*args, **kwargs) else: return cls.from_model_xml(*args, **kwargs) @@ -228,7 +244,7 @@ class Model: model.settings = openmc.Settings.from_xml_element(root.find('settings')) model.materials = openmc.Materials.from_xml_element(root.find('materials')) materials = {str(m.id): m for m in model.materials} - model.geometry = .Geometry.from_xml_element(root.find('geometry'), materials) + model.geometry = openmc.Geometry.from_xml_element(root.find('geometry'), materials) # gather meshses from other classes before reading the tally node meshes = {} @@ -442,8 +458,8 @@ class Model: depletion_operator.cleanup_when_done = True depletion_operator.finalize() - def export_to_xml(self, directory='.', remove_surfs=False, separate_xmls=True): - """Export model to separate XML files. + def export_to_xml(self, directory='.', remove_surfs=False, separate_xmls=True, filename='model.xml'): + """Export model to an XML file(s). Parameters ---------- @@ -453,6 +469,8 @@ class Model: remove_surfs : bool Whether or not to remove redundant surfaces from the geometry when exporting. + filename : str + Name of the single XML file to create (only used :math:`separate_xmls` if False) .. versionadded:: 0.13.1 @@ -460,11 +478,15 @@ class Model: Whether or not to write a single model.xml file or many XML files. """ if separate_xmls: - self.export_to_separate_xmls(directory, remove_surfs) + if filename != 'model.xml': + warnings.warn('Export filename parameter {filename} is ignored ' + 'because the model is being written to separate XML files.') + self._export_to_separate_xmls(directory, remove_surfs) else: - self.export_to_single_xml(directory, remove_surfs) + filename = Path(directory) / Path(filename) + self._export_to_single_xml(filename, remove_surfs) - def export_to_separate_xmls(self, directory='.', remove_surfs=False): + def _export_to_separate_xmls(self, directory='.', remove_surfs=False): """Export model to separate XML files. Parameters @@ -501,7 +523,7 @@ class Model: if self.plots: self.plots.export_to_xml(d) - def export_to_single_xml(self, directory='.', remove_surfs=False): + def _export_to_single_xml(self, path='model.xml', remove_surfs=False): """Export model to XML files. Parameters @@ -516,10 +538,9 @@ class Model: .. versionadded:: 0.13.1 """ # Create directory if required - d = Path(directory) - if not d.is_dir(): - d.mkdir(parents=True) - d /= 'model.xml' + xml_path = Path(path) + if not xml_path.parent.exists: + raise RuntimeError(f'The directory "{xml_path}" does not exist.') if remove_surfs: warnings.warn("remove_surfs kwarg will be deprecated soon, please " @@ -541,7 +562,7 @@ class Model: materials = openmc.Materials(self.geometry.get_all_materials() .values()) - with open(d, 'w', encoding='utf-8', errors='xmlcharrefreplace') as fh: + with open(xml_path, 'w', encoding='utf-8', errors='xmlcharrefreplace') as fh: # write the XML header fh.write("\n") fh.write("\n") diff --git a/openmc/plots.py b/openmc/plots.py index b1f192787..0a0451625 100644 --- a/openmc/plots.py +++ b/openmc/plots.py @@ -911,6 +911,12 @@ class Plots(cv.CheckedList): def to_xml_element(self): """Create a 'plots' element to be written to an XML file. + + Returns + ------- + element : xml.etree.ElementTree.Element + XML element containing all plot elements + """ # Reset xml element tree self._plots_file.clear() diff --git a/src/initialize.cpp b/src/initialize.cpp index 3f6e69ecd..dc5c0c3c1 100644 --- a/src/initialize.cpp +++ b/src/initialize.cpp @@ -106,7 +106,7 @@ int openmc_init(int argc, char* argv[], const void* intracomm) openmc::openmc_set_seed(DEFAULT_SEED); // Read XML input files - if (!read_model_xml()) read_input_xml(); + if (!read_model_xml()) read_separate_xml_files(); // Write some initial output under the header if needed initial_output(); @@ -371,7 +371,7 @@ bool read_model_xml() { return true; } -void read_input_xml() +void read_separate_xml_files() { read_settings_xml(); read_cross_sections_xml(); diff --git a/tests/unit_tests/test_model.py b/tests/unit_tests/test_model.py index 928bf0715..87557ddc5 100644 --- a/tests/unit_tests/test_model.py +++ b/tests/unit_tests/test_model.py @@ -530,21 +530,20 @@ def test_calc_volumes(run_in_tmpdir, pin_model_attributes, mpi_intracomm): test_model.finalize_lib() -def test_model_xml(): +def test_model_xml(run_in_tmpdir): - with cdtemp(): - # load a model from examples - pwr_model = openmc.examples.pwr_core() + # 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') + # 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_xml(separate_xmls=False) - new_model = openmc.Model.from_xml(separate_xmls=False) + # now write and read a model.xml file + pwr_model.export_to_xml(separate_xmls=False) + new_model = openmc.Model.from_xml(separate_xmls=False) - # make sure we can also export this again to separate - # XML files - new_model.export_to_xml(separate_xmls=True) \ No newline at end of file + # make sure we can also export this again to separate + # XML files + new_model.export_to_xml(separate_xmls=True) \ No newline at end of file From d9847163e86a4131016a1a6a5f0cd7c0e12b34cc Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Wed, 23 Nov 2022 23:06:31 -0600 Subject: [PATCH 21/53] Adding ability to specify an input filename to the executable or to the python exec --- include/openmc/initialize.h | 4 +++ openmc/executor.py | 37 +++++++++++++++++++----- openmc/model/model.py | 1 - src/initialize.cpp | 24 +++++++++++---- tests/regression_tests/model_xml/test.py | 22 +++++++++++++- 5 files changed, 73 insertions(+), 15 deletions(-) diff --git a/include/openmc/initialize.h b/include/openmc/initialize.h index 5caccceda..c37208e5e 100644 --- a/include/openmc/initialize.h +++ b/include/openmc/initialize.h @@ -1,6 +1,8 @@ #ifndef OPENMC_INITIALIZE_H #define OPENMC_INITIALIZE_H +#include + #ifdef OPENMC_MPI #include "mpi.h" #endif @@ -19,6 +21,8 @@ void read_separate_xml_files(); //! Write some output that occurs right after initialization void initial_output(); + +std::string args_xml_filename {}; } // namespace openmc #endif // OPENMC_INITIALIZE_H diff --git a/openmc/executor.py b/openmc/executor.py index a73d896ff..b14313213 100644 --- a/openmc/executor.py +++ b/openmc/executor.py @@ -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, input_file=None): """Converts user-readable flags in to command-line arguments to be run with the OpenMC executable via subprocess. @@ -42,6 +42,8 @@ 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']. + input_file : str or Pathlike + Name of a single XML input file for the OpenMC executable to read. .. versionadded:: 0.13.0 @@ -82,6 +84,9 @@ def _process_CLI_arguments(volume=False, geometry_debug=False, particles=None, if mpi_args is not None: args = mpi_args + args + if input_file is not None: + args += ['-i', input_file] + return args @@ -118,7 +123,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='.', input_file=None): """Run OpenMC in plotting mode Parameters @@ -129,6 +134,8 @@ def plot_geometry(output=True, openmc_exec='openmc', cwd='.'): Path to OpenMC executable cwd : str, optional Path to working directory to run in + input_file : str + Name of a single XML input file for the OpenMC executable to read. Raises ------ @@ -136,10 +143,13 @@ def plot_geometry(output=True, openmc_exec='openmc', cwd='.'): If the `openmc` executable returns a non-zero status """ + args = [openmc_exec, '-p'] + if input_file is not None: + args += ['-i', input_file] _run([openmc_exec, '-p'], output, cwd) -def plot_inline(plots, openmc_exec='openmc', cwd='.'): +def plot_inline(plots, openmc_exec='openmc', cwd='.', input_file=None): """Display plots inline in a Jupyter notebook. .. versionchanged:: 0.13.0 @@ -155,6 +165,8 @@ def plot_inline(plots, openmc_exec='openmc', cwd='.'): Path to OpenMC executable cwd : str, optional Path to working directory to run in + input_file : str + Name of a single XML input file for the OpenMC executable to read. Raises ------ @@ -171,7 +183,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, input_file) if plots is not None: images = [_get_plot_image(p, cwd) for p in plots] @@ -179,7 +191,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, + input_file=None): """Run stochastic volume calculations in OpenMC. This function runs OpenMC in stochastic volume calculation mode. To specify @@ -210,6 +223,8 @@ def calculate_volumes(threads=None, output=True, cwd='.', cwd : str, optional Path to working directory to run in. Defaults to the current working directory. + input_file : str or Pathlike + Name of a single XML input file for the OpenMC executable to read. Raises ------ @@ -223,14 +238,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, + input_file=input_file) _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, + input_file=None): """Run an OpenMC simulation. Parameters @@ -265,6 +282,9 @@ def run(particles=None, threads=None, geometry_debug=False, .. versionadded:: 0.12 + input_file : str or Pathlike + Name of a single XML input file for the OpenMC executable to read. + Raises ------ RuntimeError @@ -275,6 +295,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, + input_file=input_file) _run(args, output, cwd) diff --git a/openmc/model/model.py b/openmc/model/model.py index 2ed9bf668..15effd3e4 100644 --- a/openmc/model/model.py +++ b/openmc/model/model.py @@ -221,7 +221,6 @@ class Model: Geometry object """ - if separate_xmls or not Path(path).exists(): return cls.from_separate_xmls(*args, **kwargs) else: diff --git a/src/initialize.cpp b/src/initialize.cpp index dc5c0c3c1..02a4f9ab7 100644 --- a/src/initialize.cpp +++ b/src/initialize.cpp @@ -181,10 +181,11 @@ int parse_command_line(int argc, char* argv[]) } else if (arg == "-e" || arg == "--event") { settings::event_based = true; - + } else if (arg == "-i" || arg == "--input") { + i += 1; + args_xml_filename = std::string(argv[i]); } 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; @@ -295,9 +296,19 @@ int parse_command_line(int argc, char* argv[]) bool read_model_xml() { // get verbosity from settings node - std::string model_filename = settings::path_input + "model.xml"; - bool use_model_file = file_exists(model_filename); - if (!file_exists(model_filename)) return false; + + std::string xml_filename = "model.xml"; + if (!args_xml_filename.empty()) xml_filename = args_xml_filename; + std::string model_filename = settings::path_input + xml_filename; + + // check that the model file exists. If it does not and a custom filename was + // supplied by the user report an error + if (!file_exists(model_filename)) { + if (!args_xml_filename.empty()) { + fatal_error(fmt::format("The input file '{}' specified on the command line does not exist", args_xml_filename)); + } + return false; + } // attempt to open the document pugi::xml_document doc; @@ -325,6 +336,9 @@ bool read_model_xml() { title(); } + if (!args_xml_filename.empty()) + write_message(fmt::format("Reaging user-specified input '{}'...", args_xml_filename), 5); + else write_message("Reading model XML file...", 5); read_settings_xml(settings_root); diff --git a/tests/regression_tests/model_xml/test.py b/tests/regression_tests/model_xml/test.py index 289bd4655..342d7dca9 100644 --- a/tests/regression_tests/model_xml/test.py +++ b/tests/regression_tests/model_xml/test.py @@ -73,4 +73,24 @@ def test_model_xml(model, path, request): inputs = path + "/inputs_true.dat" results = path + "/results_true.dat" harness = ModelXMLTestHarness(request.getfixturevalue(model), inputs, results) - harness.main() \ No newline at end of file + 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(separate_xmls=True) + openmc.run() + + # now export to a single XML file with a custom name + pincell.export_to_xml(filename='pincell.xml', separate_xmls=False) + + # run by specifying that single file + openmc.run(input_file='pincell.xml') + + # now ensure we get an error for an incorrect filename + with pytest.raises(RuntimeError): + openmc.run(input_file='ex-em-ell.xml') \ No newline at end of file From 366f9e905c4f049e8971f743b6cca5d210fff04e Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Wed, 23 Nov 2022 23:10:57 -0600 Subject: [PATCH 22/53] Test comment and updated error messages --- src/initialize.cpp | 8 +++++--- tests/regression_tests/model_xml/test.py | 3 ++- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/initialize.cpp b/src/initialize.cpp index 02a4f9ab7..68436f07e 100644 --- a/src/initialize.cpp +++ b/src/initialize.cpp @@ -348,14 +348,15 @@ bool read_model_xml() { 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(("Other XML file input(s) are present. These file will be ignored in favor of the model.xml file.")); + warning((fmt::format("Other XML file input(s) are present. These file will be ignored in favor of the {} file.", xml_filename)); break; } } // Read materials and cross sections if (!check_for_node(root, "materials")) { - fatal_error("No node present in the model.xml file."); + fatal_error( + fmt::format("No node present in the {} file.", xml_filename)); } auto materials_node = root.child("materials"); read_cross_sections_xml(materials_node); @@ -363,7 +364,8 @@ bool read_model_xml() { // Read geometry if (!check_for_node(root, "geometry")) { - fatal_error("No node present in the model.xml_file."); + fatal_error(fmt::format( + "No node present in the model.xml_file.", xml_filename)); } read_geometry_xml(root.child("geometry")); diff --git a/tests/regression_tests/model_xml/test.py b/tests/regression_tests/model_xml/test.py index 342d7dca9..5bfe912a4 100644 --- a/tests/regression_tests/model_xml/test.py +++ b/tests/regression_tests/model_xml/test.py @@ -91,6 +91,7 @@ def test_input_arg(run_in_tmpdir): # run by specifying that single file openmc.run(input_file='pincell.xml') - # now ensure we get an error for an incorrect filename + # now ensure we get an error for an incorrect filename, + # even in the presence of other, valid XML files with pytest.raises(RuntimeError): openmc.run(input_file='ex-em-ell.xml') \ No newline at end of file From 1e6cb68ea420558f6f120e830602b8938f2c1319 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Wed, 23 Nov 2022 23:14:30 -0600 Subject: [PATCH 23/53] Addressing a few more comments in C++ code --- src/initialize.cpp | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/initialize.cpp b/src/initialize.cpp index 68436f07e..87e839dfd 100644 --- a/src/initialize.cpp +++ b/src/initialize.cpp @@ -358,8 +358,12 @@ bool read_model_xml() { fatal_error( fmt::format("No node present in the {} file.", xml_filename)); } + + // find materials node this object cannot be used after being passed to the + // cross section reading function below auto materials_node = root.child("materials"); read_cross_sections_xml(materials_node); + read_materials_xml(root.child("materials")); // Read geometry @@ -411,7 +415,7 @@ void read_separate_xml_files() } void initial_output() { - // handle some final output + // write initial output if (settings::run_mode == RunMode::PLOTTING) { // Read plots.xml if it exists if (mpi::master && settings::verbosity >= 5) From 7a203fbf7266f365d3d209461687402edcb3b56b Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Wed, 23 Nov 2022 23:16:57 -0600 Subject: [PATCH 24/53] Correcting parenthesis --- src/initialize.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/initialize.cpp b/src/initialize.cpp index 87e839dfd..a6a6b5a77 100644 --- a/src/initialize.cpp +++ b/src/initialize.cpp @@ -348,7 +348,7 @@ bool read_model_xml() { 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 file will be ignored in favor of the {} file.", xml_filename)); + warning((fmt::format("Other XML file input(s) are present. These file will be ignored in favor of the {} file.", xml_filename))); break; } } From fe47d565fd7fddb464e3e67907936211bc76a857 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Wed, 23 Nov 2022 23:17:11 -0600 Subject: [PATCH 25/53] Spot check in error message from failed run --- tests/regression_tests/model_xml/test.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/regression_tests/model_xml/test.py b/tests/regression_tests/model_xml/test.py index 5bfe912a4..0dc2cc289 100644 --- a/tests/regression_tests/model_xml/test.py +++ b/tests/regression_tests/model_xml/test.py @@ -93,5 +93,6 @@ def test_input_arg(run_in_tmpdir): # now ensure we get an error for an incorrect filename, # even in the presence of other, valid XML files - with pytest.raises(RuntimeError): - openmc.run(input_file='ex-em-ell.xml') \ No newline at end of file + with pytest.raises(RuntimeError) as execinfo: + openmc.run(input_file='ex-em-ell.xml') + assert 'ex-em-ell.xml' in execinfo.value \ No newline at end of file From 6f7a6febd3fe01531979d2276f1c40fb6f8430f7 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Fri, 25 Nov 2022 08:22:57 -0600 Subject: [PATCH 26/53] Updating model XML file format w/ indentation. --- openmc/_xml.py | 28 ++++-- openmc/material.py | 27 +++-- openmc/model/model.py | 7 +- .../model_xml/inputs_true.dat | 99 ++++++++++++------- 4 files changed, 111 insertions(+), 50 deletions(-) diff --git a/openmc/_xml.py b/openmc/_xml.py index 32679fd89..b4e08c751 100644 --- a/openmc/_xml.py +++ b/openmc/_xml.py @@ -1,22 +1,36 @@ -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 pastee 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 include an indentation after closing the element + """ i = "\n" + level*spaces_per_level*" " + # ensure there's awlays 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: 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 diff --git a/openmc/material.py b/openmc/material.py index c33596800..332f8c965 100644 --- a/openmc/material.py +++ b/openmc/material.py @@ -1449,7 +1449,7 @@ class Materials(cv.CheckedList): for material in self: material.make_isotropic_in_lab() - def _write_xml(self, file, header=True): + 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 @@ -1458,33 +1458,46 @@ class Materials(cv.CheckedList): 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("\n") - file.write('\n') + file.write(indentation+'\n') # Write the element. if self.cross_sections is not None: element = ET.Element('cross_sections') element.text = str(self.cross_sections) - clean_indentation(element, level=1) + clean_indentation(element, level=level+1) element.tail = element.tail.strip(' ') - file.write(' ') + 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 elements. for material in sorted(self, key=lambda x: x.id): element = material.to_xml_element() - clean_indentation(element, level=1) + clean_indentation(element, level=level+1) element.tail = element.tail.strip(' ') - file.write(' ') + 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('\n') + file.write(indentation+'\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'): diff --git a/openmc/model/model.py b/openmc/model/model.py index 15effd3e4..9c38d27c3 100644 --- a/openmc/model/model.py +++ b/openmc/model/model.py @@ -552,6 +552,9 @@ class Model: settings_element = self.settings.to_xml_element(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. @@ -567,16 +570,18 @@ class Model: fh.write("\n") # Write the materials collection to the open XML file first. # This will write the XML header also - materials._write_xml(fh, False) + 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(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("\n") diff --git a/tests/regression_tests/model_xml/inputs_true.dat b/tests/regression_tests/model_xml/inputs_true.dat index 5aedbfa14..c40802631 100644 --- a/tests/regression_tests/model_xml/inputs_true.dat +++ b/tests/regression_tests/model_xml/inputs_true.dat @@ -1,38 +1,67 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - eigenvalue - 10000 - 10 - 5 - - - -4.0 -4.0 -4.0 4.0 4.0 4.0 - - - + + + + + + + + + + + + + + + + + fixed source + 10000 + 1 + + + 0 0 0 + + + + 14000000.0 1.0 + + + ttb + true + + 1000.0 + + + + + 16 + + + neutron photon electron positron + + + 1 2 + current + + + 2 + Al27 total + total (n,gamma) + tracklength + + + 2 + Al27 total + total heating (n,gamma) + collision + + + 2 + Al27 total + total heating (n,gamma) + analog + + From 5d1ead2e6a88affe5900067412e0464171cba8e4 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Sat, 26 Nov 2022 00:46:12 -0600 Subject: [PATCH 27/53] Addressing some more PR comments. A couple of typo fixes too. --- openmc/_xml.py | 2 +- openmc/model/model.py | 2 +- src/initialize.cpp | 6 +----- tests/regression_tests/adj_cell_rotation/__init__.py | 2 -- tests/regression_tests/energy_laws/__init__.py | 1 - tests/regression_tests/lattice_multiple/__init__.py | 1 - tests/regression_tests/model_xml/test.py | 8 ++++---- tests/regression_tests/photon_production/__init__.py | 1 - 8 files changed, 7 insertions(+), 16 deletions(-) diff --git a/openmc/_xml.py b/openmc/_xml.py index b4e08c751..450c8a99b 100644 --- a/openmc/_xml.py +++ b/openmc/_xml.py @@ -11,7 +11,7 @@ def clean_indentation(element, level=0, spaces_per_level=2, trailing_indent=True spaces_per_level : int Number of spaces per indentation level (default 2) trailing_indent : bool - Whether or not to include an indentation after closing the element + Whether or not to add indentation after closing the element """ i = "\n" + level*spaces_per_level*" " diff --git a/openmc/model/model.py b/openmc/model/model.py index 9c38d27c3..145a2151b 100644 --- a/openmc/model/model.py +++ b/openmc/model/model.py @@ -469,7 +469,7 @@ class Model: Whether or not to remove redundant surfaces from the geometry when exporting. filename : str - Name of the single XML file to create (only used :math:`separate_xmls` if False) + Name of the single XML file to create (only used :math:`separate_xmls` is False) .. versionadded:: 0.13.1 diff --git a/src/initialize.cpp b/src/initialize.cpp index a6a6b5a77..212b2f7bd 100644 --- a/src/initialize.cpp +++ b/src/initialize.cpp @@ -359,11 +359,7 @@ bool read_model_xml() { fmt::format("No node present in the {} file.", xml_filename)); } - // find materials node this object cannot be used after being passed to the - // cross section reading function below - auto materials_node = root.child("materials"); - read_cross_sections_xml(materials_node); - + read_cross_sections_xml(root.child("materials")); read_materials_xml(root.child("materials")); // Read geometry diff --git a/tests/regression_tests/adj_cell_rotation/__init__.py b/tests/regression_tests/adj_cell_rotation/__init__.py index 7efe44865..e69de29bb 100644 --- a/tests/regression_tests/adj_cell_rotation/__init__.py +++ b/tests/regression_tests/adj_cell_rotation/__init__.py @@ -1,2 +0,0 @@ - -from .test import model \ No newline at end of file diff --git a/tests/regression_tests/energy_laws/__init__.py b/tests/regression_tests/energy_laws/__init__.py index 29fbfcc0c..e69de29bb 100644 --- a/tests/regression_tests/energy_laws/__init__.py +++ b/tests/regression_tests/energy_laws/__init__.py @@ -1 +0,0 @@ -from .test import model \ No newline at end of file diff --git a/tests/regression_tests/lattice_multiple/__init__.py b/tests/regression_tests/lattice_multiple/__init__.py index 29fbfcc0c..e69de29bb 100644 --- a/tests/regression_tests/lattice_multiple/__init__.py +++ b/tests/regression_tests/lattice_multiple/__init__.py @@ -1 +0,0 @@ -from .test import model \ No newline at end of file diff --git a/tests/regression_tests/model_xml/test.py b/tests/regression_tests/model_xml/test.py index 0dc2cc289..52f67f76f 100644 --- a/tests/regression_tests/model_xml/test.py +++ b/tests/regression_tests/model_xml/test.py @@ -9,10 +9,10 @@ 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 import model as adj_cell_rotation_model -from ..lattice_multiple import model as lattice_mult_model -from ..energy_laws import model as energy_laws_model -from ..photon_production import model as photon_prod_model +from ..adj_cell_rotation.test import model as adj_cell_rotation_model +from ..lattice_multiple.test import model as lattice_mult_model +from ..energy_laws.test import model as energy_laws_model +from ..photon_production.test import model as photon_prod_model class ModelXMLTestHarness(PyAPITestHarness): diff --git a/tests/regression_tests/photon_production/__init__.py b/tests/regression_tests/photon_production/__init__.py index 29fbfcc0c..e69de29bb 100644 --- a/tests/regression_tests/photon_production/__init__.py +++ b/tests/regression_tests/photon_production/__init__.py @@ -1 +0,0 @@ -from .test import model \ No newline at end of file From e7f95b0c7a5fc0c1548183f38606ce158c2eb31c Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Mon, 28 Nov 2022 00:44:05 -0600 Subject: [PATCH 28/53] Docstring correction and a note about the recursion --- openmc/_xml.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/openmc/_xml.py b/openmc/_xml.py index 450c8a99b..6d298638d 100644 --- a/openmc/_xml.py +++ b/openmc/_xml.py @@ -1,6 +1,6 @@ def clean_indentation(element, level=0, spaces_per_level=2, trailing_indent=True): """Set indentation of XML element and its sub-elements. - Copied and pastee from https://effbot.org/zone/element-lib.htm#prettyprint. + 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. @@ -26,6 +26,8 @@ def clean_indentation(element, level=0, spaces_per_level=2, trailing_indent=True if trailing_indent and (not element.tail or not element.tail.strip()): element.tail = i for sub_element in element: + # `trailing_indent` intentionally not passed to the recursive call. + # it only applies to the element for the initial of this function. clean_indentation(sub_element, level+1, spaces_per_level) if not sub_element.tail or not sub_element.tail.strip(): sub_element.tail = i From b3225be24d384ba834fab6f003ff7ba39e9d99b1 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Mon, 28 Nov 2022 20:03:42 -0600 Subject: [PATCH 29/53] Adding executor tests --- tests/unit_tests/test_model.py | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/tests/unit_tests/test_model.py b/tests/unit_tests/test_model.py index 87557ddc5..5d4765d65 100644 --- a/tests/unit_tests/test_model.py +++ b/tests/unit_tests/test_model.py @@ -1,5 +1,6 @@ from math import pi from pathlib import Path +import os import numpy as np import pytest @@ -546,4 +547,23 @@ def test_model_xml(run_in_tmpdir): # make sure we can also export this again to separate # XML files - new_model.export_to_xml(separate_xmls=True) \ No newline at end of file + new_model.export_to_xml(separate_xmls=True) + +def test_model_exec(run_in_tmpdir): + + pincell_model = openmc.examples.pwr_pin_cell() + + pincell_model.export_to_xml(filename='pwr_pincell.xml', separate_xmls=False) + + openmc.run(input_file='pwr_pincell.xml') + + with pytest.raises(RuntimeError, match='ex-em-ell.xml'): + openmc.run(input_file='ex-em-ell.xml') + + # test that a file in a different directory can be used + os.mkdir('inputs') + pincell_model.export_to_xml(directory='./inputs', filename='pincell.xml', separate_xmls=False) + openmc.run(input_file='./inputs/pincell.xml') + + with pytest.raises(RuntimeError, match='input_dir'): + openmc.run(input_file='input_dir/pincell.xml') \ No newline at end of file From 72ff942226e8fb60801328a64530ad1cd94b1da0 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Mon, 28 Nov 2022 20:04:45 -0600 Subject: [PATCH 30/53] Checking RuntimeError message correctly --- tests/regression_tests/model_xml/test.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/tests/regression_tests/model_xml/test.py b/tests/regression_tests/model_xml/test.py index 52f67f76f..51c25a603 100644 --- a/tests/regression_tests/model_xml/test.py +++ b/tests/regression_tests/model_xml/test.py @@ -93,6 +93,5 @@ def test_input_arg(run_in_tmpdir): # now ensure we get an error for an incorrect filename, # even in the presence of other, valid XML files - with pytest.raises(RuntimeError) as execinfo: - openmc.run(input_file='ex-em-ell.xml') - assert 'ex-em-ell.xml' in execinfo.value \ No newline at end of file + with pytest.raises(RuntimeError, match='ex-em-ell.xml'): + openmc.run(input_file='ex-em-ell.xml') \ No newline at end of file From 7f5d2a583932206bc681a8555d658334d74ebc9d Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Mon, 28 Nov 2022 23:54:26 -0600 Subject: [PATCH 31/53] Refactoring model_xml regression tests a bit and adding input checking --- .../adj_cell_rotation_inputs_true.dat | 38 +++++++++++ .../model_xml/energy_laws_inputs_true.dat | 23 +++++++ .../lattice_multiple_inputs_true.dat | 53 +++++++++++++++ .../photon_production_inputs_true.dat | 67 +++++++++++++++++++ tests/regression_tests/model_xml/test.py | 42 ++++++------ 5 files changed, 201 insertions(+), 22 deletions(-) create mode 100644 tests/regression_tests/model_xml/adj_cell_rotation_inputs_true.dat create mode 100644 tests/regression_tests/model_xml/energy_laws_inputs_true.dat create mode 100644 tests/regression_tests/model_xml/lattice_multiple_inputs_true.dat create mode 100644 tests/regression_tests/model_xml/photon_production_inputs_true.dat diff --git a/tests/regression_tests/model_xml/adj_cell_rotation_inputs_true.dat b/tests/regression_tests/model_xml/adj_cell_rotation_inputs_true.dat new file mode 100644 index 000000000..3b14f304e --- /dev/null +++ b/tests/regression_tests/model_xml/adj_cell_rotation_inputs_true.dat @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + eigenvalue + 10000 + 10 + 5 + + + -4.0 -4.0 -4.0 4.0 4.0 4.0 + + + + diff --git a/tests/regression_tests/model_xml/energy_laws_inputs_true.dat b/tests/regression_tests/model_xml/energy_laws_inputs_true.dat new file mode 100644 index 000000000..62485ff72 --- /dev/null +++ b/tests/regression_tests/model_xml/energy_laws_inputs_true.dat @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + + + eigenvalue + 1000 + 10 + 5 + + diff --git a/tests/regression_tests/model_xml/lattice_multiple_inputs_true.dat b/tests/regression_tests/model_xml/lattice_multiple_inputs_true.dat new file mode 100644 index 000000000..d828b25f6 --- /dev/null +++ b/tests/regression_tests/model_xml/lattice_multiple_inputs_true.dat @@ -0,0 +1,53 @@ + + + + + + + + + + + + + + + + + + + + + + + + 1.2 1.2 + 1 + 2 2 + -1.2 -1.2 + +2 1 +1 1 + + + 2.4 2.4 + 2 2 + -2.4 -2.4 + +4 4 +4 4 + + + + + + + + + + eigenvalue + 1000 + 10 + 5 + + diff --git a/tests/regression_tests/model_xml/photon_production_inputs_true.dat b/tests/regression_tests/model_xml/photon_production_inputs_true.dat new file mode 100644 index 000000000..9e60033be --- /dev/null +++ b/tests/regression_tests/model_xml/photon_production_inputs_true.dat @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + fixed source + 10000 + 1 + + + 0 0 0 + + + + 14000000.0 1.0 + + + ttb + true + + 1000.0 + + + + + 1 + + + neutron photon electron positron + + + 1 2 + current + + + 2 + Al27 total + total (n,gamma) + tracklength + + + 2 + Al27 total + total heating (n,gamma) + collision + + + 2 + Al27 total + total heating (n,gamma) + analog + + + diff --git a/tests/regression_tests/model_xml/test.py b/tests/regression_tests/model_xml/test.py index 51c25a603..9da5438db 100644 --- a/tests/regression_tests/model_xml/test.py +++ b/tests/regression_tests/model_xml/test.py @@ -10,9 +10,9 @@ 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_mult_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_prod_model +from ..photon_production.test import model as photon_production_model class ModelXMLTestHarness(PyAPITestHarness): @@ -30,10 +30,10 @@ class ModelXMLTestHarness(PyAPITestHarness): def _get_inputs(self): return open('model.xml').read() - def _compare_inputs(self): - """Skip input comparisons for now - """ - pass + # def _compare_inputs(self): + # """Skip input comparisons for now + # """ + # pass def _compare_results(self): """Make sure the current results agree with the reference.""" @@ -54,25 +54,23 @@ class ModelXMLTestHarness(PyAPITestHarness): os.remove('model.xml') -models = [ - 'adj_cell_rotation_model', - 'lattice_mult_model', - 'energy_laws_model', - 'photon_prod_model' -] -paths = [ - '../adj_cell_rotation', - '../lattice_multiple', - '../energy_laws', - '../photon_production' +test_names = [ + 'adj_cell_rotation', + 'lattice_multiple', + 'energy_laws', + 'photon_production' ] -@pytest.mark.parametrize("model, path", zip(models, paths)) -def test_model_xml(model, path, request): - inputs = path + "/inputs_true.dat" - results = path + "/results_true.dat" - harness = ModelXMLTestHarness(request.getfixturevalue(model), inputs, results) +@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): From 3f4a9eb7a2056f3136b00b82a814e934f52803e3 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Tue, 29 Nov 2022 01:19:38 -0600 Subject: [PATCH 32/53] Reordering geometry XML element attributes --- openmc/geometry.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openmc/geometry.py b/openmc/geometry.py index d036df9c7..afcae1d55 100644 --- a/openmc/geometry.py +++ b/openmc/geometry.py @@ -132,6 +132,7 @@ class Geometry: # 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 @@ -157,7 +158,6 @@ 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') From 83cf6848d0f0fbe0a59798b333542daf3255c7e5 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Tue, 29 Nov 2022 11:11:06 -0600 Subject: [PATCH 33/53] Updating expected inputs with reordering --- .../adj_cell_rotation_inputs_true.dat | 20 +++++++++---------- .../model_xml/energy_laws_inputs_true.dat | 2 +- .../lattice_multiple_inputs_true.dat | 16 +++++++-------- .../photon_production_inputs_true.dat | 8 ++++---- 4 files changed, 23 insertions(+), 23 deletions(-) diff --git a/tests/regression_tests/model_xml/adj_cell_rotation_inputs_true.dat b/tests/regression_tests/model_xml/adj_cell_rotation_inputs_true.dat index 3b14f304e..1c4516f42 100644 --- a/tests/regression_tests/model_xml/adj_cell_rotation_inputs_true.dat +++ b/tests/regression_tests/model_xml/adj_cell_rotation_inputs_true.dat @@ -13,16 +13,16 @@ - - - - - - - - - - + + + + + + + + + + eigenvalue diff --git a/tests/regression_tests/model_xml/energy_laws_inputs_true.dat b/tests/regression_tests/model_xml/energy_laws_inputs_true.dat index 62485ff72..c89ccc97e 100644 --- a/tests/regression_tests/model_xml/energy_laws_inputs_true.dat +++ b/tests/regression_tests/model_xml/energy_laws_inputs_true.dat @@ -12,7 +12,7 @@ - + eigenvalue diff --git a/tests/regression_tests/model_xml/lattice_multiple_inputs_true.dat b/tests/regression_tests/model_xml/lattice_multiple_inputs_true.dat index d828b25f6..0eed3c201 100644 --- a/tests/regression_tests/model_xml/lattice_multiple_inputs_true.dat +++ b/tests/regression_tests/model_xml/lattice_multiple_inputs_true.dat @@ -18,8 +18,8 @@ - - + + 1.2 1.2 1 @@ -37,12 +37,12 @@ 4 4 4 4 - - - - - - + + + + + + eigenvalue diff --git a/tests/regression_tests/model_xml/photon_production_inputs_true.dat b/tests/regression_tests/model_xml/photon_production_inputs_true.dat index 9e60033be..e4fe9a585 100644 --- a/tests/regression_tests/model_xml/photon_production_inputs_true.dat +++ b/tests/regression_tests/model_xml/photon_production_inputs_true.dat @@ -10,10 +10,10 @@ - - - - + + + + fixed source From 2c27d1065a1e583a3778aa493f70a36724b46306 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Tue, 29 Nov 2022 13:45:10 -0600 Subject: [PATCH 34/53] Moving other relevant XML reorder calls and updating expected input again. --- openmc/settings.py | 2 +- openmc/tallies.py | 2 +- .../model_xml/photon_production_inputs_true.dat | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/openmc/settings.py b/openmc/settings.py index 74ec0d54a..3c1c8c7f5 100644 --- a/openmc/settings.py +++ b/openmc/settings.py @@ -1598,6 +1598,7 @@ class Settings: # 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 @@ -1618,7 +1619,6 @@ 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') diff --git a/openmc/tallies.py b/openmc/tallies.py index e37f04e25..e17e7c99d 100644 --- a/openmc/tallies.py +++ b/openmc/tallies.py @@ -3166,6 +3166,7 @@ class Tallies(cv.CheckedList): # 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 @@ -3187,7 +3188,6 @@ 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') diff --git a/tests/regression_tests/model_xml/photon_production_inputs_true.dat b/tests/regression_tests/model_xml/photon_production_inputs_true.dat index e4fe9a585..0b2b43468 100644 --- a/tests/regression_tests/model_xml/photon_production_inputs_true.dat +++ b/tests/regression_tests/model_xml/photon_production_inputs_true.dat @@ -23,7 +23,7 @@ 0 0 0 - + 14000000.0 1.0 From cd7c3bf6ba62cd019c378a3a22880e24e847f8ef Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Tue, 6 Dec 2022 12:58:32 -0600 Subject: [PATCH 35/53] Updates to import/export methods suggested by @paulromano --- openmc/model/model.py | 43 ++++++++++++++++++++++++++----------------- 1 file changed, 26 insertions(+), 17 deletions(-) diff --git a/openmc/model/model.py b/openmc/model/model.py index 145a2151b..6ca6f72b6 100644 --- a/openmc/model/model.py +++ b/openmc/model/model.py @@ -205,15 +205,19 @@ class Model: self._plots.append(plot) @classmethod - def from_xml(cls, *args, path='model.xml', separate_xmls=True, **kwargs): + def from_xml(cls, *args, separate_xmls=True, **kwargs): """Generate geometry from XML file Parameters ---------- - path : str, optional - Path to model XML file + args : list + Positional arguments to be forwarded to + :func:`Model.from_separate_xmls` or :func:`Model.from_model_xml`. separate_xmls : bool - Whether or not to read from a single or separate XML files + Whether or not to read from a single or separate XML files. + kwargs : dict, optional + Keyword arguments to be forwarded to + :func:`Model.from_separate_xmls` or :func:`Model.from_model_xml`. Returns ------- @@ -221,7 +225,7 @@ class Model: Geometry object """ - if separate_xmls or not Path(path).exists(): + if separate_xmls: return cls.from_separate_xmls(*args, **kwargs) else: return cls.from_model_xml(*args, **kwargs) @@ -457,19 +461,19 @@ class Model: depletion_operator.cleanup_when_done = True depletion_operator.finalize() - def export_to_xml(self, directory='.', remove_surfs=False, separate_xmls=True, filename='model.xml'): + def export_to_xml(self, directory='.', remove_surfs=False, separate_xmls=True, path='model.xml'): """Export model to an XML file(s). Parameters ---------- directory : str Directory to write XML files to. If it doesn't exist already, it - will be created. + will be created. Only used if :math:`separate_xmls` is True. remove_surfs : bool Whether or not to remove redundant surfaces from the geometry when exporting. - filename : str - Name of the single XML file to create (only used :math:`separate_xmls` is False) + path : str + Path to an output filename or directory. Only used if :math:`separate_xmls` is False. .. versionadded:: 0.13.1 @@ -477,13 +481,9 @@ class Model: Whether or not to write a single model.xml file or many XML files. """ if separate_xmls: - if filename != 'model.xml': - warnings.warn('Export filename parameter {filename} is ignored ' - 'because the model is being written to separate XML files.') self._export_to_separate_xmls(directory, remove_surfs) else: - filename = Path(directory) / Path(filename) - self._export_to_single_xml(filename, remove_surfs) + self._export_to_single_xml(path, remove_surfs) def _export_to_separate_xmls(self, directory='.', remove_surfs=False): """Export model to separate XML files. @@ -530,16 +530,25 @@ class Model: directory : str Directory to write the model.xml file to. If it doesn't exist already, it will be created. + path : str or Pathlike + Location of the XML file to write. Can be a directory or file path. remove_surfs : bool Whether or not to remove redundant surfaces from the geometry when exporting. .. versionadded:: 0.13.1 """ - # Create directory if required xml_path = Path(path) - if not xml_path.parent.exists: - raise RuntimeError(f'The directory "{xml_path}" does not exist.') + # 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 " From ef2f690e3d4c7bb1f700f189bdaad88689fa5b16 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Tue, 6 Dec 2022 12:59:08 -0600 Subject: [PATCH 36/53] Updating handling of single XML input to leverage existing settings::path_input --- src/initialize.cpp | 42 +++++++++++++++++++----------------------- src/settings.cpp | 3 ++- 2 files changed, 21 insertions(+), 24 deletions(-) diff --git a/src/initialize.cpp b/src/initialize.cpp index 212b2f7bd..c0333e01c 100644 --- a/src/initialize.cpp +++ b/src/initialize.cpp @@ -181,9 +181,6 @@ int parse_command_line(int argc, char* argv[]) } else if (arg == "-e" || arg == "--event") { settings::event_based = true; - } else if (arg == "-i" || arg == "--input") { - i += 1; - args_xml_filename = std::string(argv[i]); } else if (arg == "-r" || arg == "--restart") { i += 1; // Check what type of file this is @@ -295,27 +292,24 @@ int parse_command_line(int argc, char* argv[]) } bool read_model_xml() { - // get verbosity from settings node + std::string model_filename = settings::path_input; - std::string xml_filename = "model.xml"; - if (!args_xml_filename.empty()) xml_filename = args_xml_filename; - std::string model_filename = settings::path_input + xml_filename; - - // check that the model file exists. If it does not and a custom filename was - // supplied by the user report an error - if (!file_exists(model_filename)) { - if (!args_xml_filename.empty()) { - fatal_error(fmt::format("The input file '{}' specified on the command line does not exist", args_xml_filename)); - } - return false; + // if the path input isn't an existing file, assume its a directory + // and append the default model.xml filename + if (!file_exists(settings::path_input)) { + model_filename += "/model.xml"; + if (!file_exists(model_filename)) + return false; } - // attempt to open the document + // try to process the path input as an XML file pugi::xml_document doc; - auto result = doc.load_file(model_filename.c_str()); - if (!result) { - fatal_error("Error processing model.xml file."); + // if the input is a file, try to read it + 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 @@ -348,15 +342,17 @@ bool read_model_xml() { 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 file will be ignored in favor of the {} file.", xml_filename))); + warning((fmt::format("Other XML file input(s) are present. These file " + "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 node present in the {} file.", xml_filename)); + fatal_error(fmt::format( + "No node present in the {} file.", model_filename)); } read_cross_sections_xml(root.child("materials")); @@ -365,7 +361,7 @@ bool read_model_xml() { // Read geometry if (!check_for_node(root, "geometry")) { fatal_error(fmt::format( - "No node present in the model.xml_file.", xml_filename)); + "No node present in the model.xml_file.", model_filename)); } read_geometry_xml(root.child("geometry")); diff --git a/src/settings.cpp b/src/settings.cpp index 3048862e3..74d45543d 100644 --- a/src/settings.cpp +++ b/src/settings.cpp @@ -222,7 +222,8 @@ void read_settings_xml() { 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.", From 574845b18b1de2d2fa9fa8415d7770e18d411bd6 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Tue, 6 Dec 2022 13:09:04 -0600 Subject: [PATCH 37/53] Moving Model.from_separate_xmls back to Model.from_xml. --- openmc/model/model.py | 78 +++++++++++++++---------------------------- 1 file changed, 26 insertions(+), 52 deletions(-) diff --git a/openmc/model/model.py b/openmc/model/model.py index 6ca6f72b6..34cc085b8 100644 --- a/openmc/model/model.py +++ b/openmc/model/model.py @@ -205,30 +205,40 @@ class Model: self._plots.append(plot) @classmethod - def from_xml(cls, *args, separate_xmls=True, **kwargs): - """Generate geometry from XML file + def from_xml(cls, geometry='geometry.xml', materials='materials.xml', + settings='settings.xml', tallies='tallies.xml', + plots='plots.xml'): + """Create model from existing XML files Parameters ---------- - args : list - Positional arguments to be forwarded to - :func:`Model.from_separate_xmls` or :func:`Model.from_model_xml`. - separate_xmls : bool - Whether or not to read from a single or separate XML files. - kwargs : dict, optional - Keyword arguments to be forwarded to - :func:`Model.from_separate_xmls` or :func:`Model.from_model_xml`. + geometry : str + Path to geometry.xml file + materials : str + Path to materials.xml file + settings : str + Path to settings.xml file + tallies : str + Path to tallies.xml file + + .. versionadded:: 0.13.0 + plots : str + Path to plots.xml file + + .. versionadded:: 0.13.0 Returns ------- - openmc.Geometry - Geometry object + openmc.model.Model + Model created from XML files """ - if separate_xmls: - return cls.from_separate_xmls(*args, **kwargs) - else: - return cls.from_model_xml(*args, **kwargs) + materials = openmc.Materials.from_xml(materials) + geometry = openmc.Geometry.from_xml(geometry, materials) + settings = openmc.Settings.from_xml(settings) + tallies = openmc.Tallies.from_xml(tallies) if Path(tallies).exists() else None + 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'): @@ -265,42 +275,6 @@ class Model: return model - @classmethod - def from_separate_xmls(cls, geometry='geometry.xml', materials='materials.xml', - settings='settings.xml', tallies='tallies.xml', - plots='plots.xml'): - """Create model from existing XML files - - Parameters - ---------- - geometry : str - Path to geometry.xml file - materials : str - Path to materials.xml file - settings : str - Path to settings.xml file - tallies : str - Path to tallies.xml file - - .. versionadded:: 0.13.0 - plots : str - Path to plots.xml file - - .. versionadded:: 0.13.0 - - Returns - ------- - openmc.model.Model - Model created from XML files - - """ - materials = openmc.Materials.from_xml(materials) - geometry = openmc.Geometry.from_xml(geometry, materials) - settings = openmc.Settings.from_xml(settings) - tallies = openmc.Tallies.from_xml(tallies) if Path(tallies).exists() else None - plots = openmc.Plots.from_xml(plots) if Path(plots).exists() else None - return cls(geometry, materials, settings, tallies, plots) - 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 From 270331aff956b95b1193e121c545345e64d3716c Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Tue, 6 Dec 2022 14:39:44 -0600 Subject: [PATCH 38/53] Adding function to check if a path is a directory --- include/openmc/file_utils.h | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/include/openmc/file_utils.h b/include/openmc/file_utils.h index f9c23468d..ae3a26e94 100644 --- a/include/openmc/file_utils.h +++ b/include/openmc/file_utils.h @@ -3,15 +3,31 @@ #include // for ifstream #include +#include namespace openmc { +// TODO: replace with std::filesysem 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 is_dir(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 directory path + if (is_dir(filename)) return false; + std::ifstream s {filename}; + s.seekg(0, std::ios::beg); return s.good(); } From bc1791d367b307656ce31bdfdf7c4b794def44ad Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Tue, 6 Dec 2022 14:40:02 -0600 Subject: [PATCH 39/53] Updates to simplify model filename checking --- include/openmc/initialize.h | 2 -- src/initialize.cpp | 30 ++++++++++++++++++------------ 2 files changed, 18 insertions(+), 14 deletions(-) diff --git a/include/openmc/initialize.h b/include/openmc/initialize.h index c37208e5e..a9b8b336f 100644 --- a/include/openmc/initialize.h +++ b/include/openmc/initialize.h @@ -21,8 +21,6 @@ void read_separate_xml_files(); //! Write some output that occurs right after initialization void initial_output(); - -std::string args_xml_filename {}; } // namespace openmc #endif // OPENMC_INITIALIZE_H diff --git a/src/initialize.cpp b/src/initialize.cpp index c0333e01c..b75f410a1 100644 --- a/src/initialize.cpp +++ b/src/initialize.cpp @@ -282,7 +282,12 @@ 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 (!is_dir(settings::path_input) && !file_exists(settings::path_input)) { + fatal_error(fmt::format("The specified path '{}' 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 += "/"; } @@ -294,13 +299,17 @@ int parse_command_line(int argc, char* argv[]) bool read_model_xml() { std::string model_filename = settings::path_input; - // if the path input isn't an existing file, assume its a directory - // and append the default model.xml filename - if (!file_exists(settings::path_input)) { - model_filename += "/model.xml"; - if (!file_exists(model_filename)) - return false; - } + // some string cleanup + // a trailing "/" is applied to path_input if it's present, + // remove it for the first attempt at reading the input file + if (ends_with(model_filename, "/")) model_filename.pop_back(); + if (model_filename.length() == 0) model_filename = "."; + + // if the current filename is a directory, append the default model filename + if (is_dir(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; @@ -330,10 +339,7 @@ bool read_model_xml() { title(); } - if (!args_xml_filename.empty()) - write_message(fmt::format("Reaging user-specified input '{}'...", args_xml_filename), 5); - else - write_message("Reading model XML file...", 5); + write_message(fmt::format("Reading model XML file '{}' ...", model_filename), 5); read_settings_xml(settings_root); From 5634d8318ad110ee20f79d69ab277f7134a0d277 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Tue, 6 Dec 2022 14:40:59 -0600 Subject: [PATCH 40/53] Changing name of input argument for Python executor --- openmc/executor.py | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/openmc/executor.py b/openmc/executor.py index b14313213..9267366e8 100644 --- a/openmc/executor.py +++ b/openmc/executor.py @@ -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, input_file=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,7 +42,7 @@ 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']. - input_file : str or Pathlike + path_input : str or Pathlike Name of a single XML input file for the OpenMC executable to read. .. versionadded:: 0.13.0 @@ -84,8 +84,8 @@ def _process_CLI_arguments(volume=False, geometry_debug=False, particles=None, if mpi_args is not None: args = mpi_args + args - if input_file is not None: - args += ['-i', input_file] + if path_input is not None: + args += [path_input] return args @@ -95,6 +95,7 @@ def _run(args, output, cwd): p = subprocess.Popen(args, cwd=cwd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True) + print(args) # Capture and re-print OpenMC output in real-time lines = [] while True: @@ -123,7 +124,7 @@ def _run(args, output, cwd): raise RuntimeError(error_msg) -def plot_geometry(output=True, openmc_exec='openmc', cwd='.', input_file=None): +def plot_geometry(output=True, openmc_exec='openmc', cwd='.', path_input=None): """Run OpenMC in plotting mode Parameters @@ -134,7 +135,7 @@ def plot_geometry(output=True, openmc_exec='openmc', cwd='.', input_file=None): Path to OpenMC executable cwd : str, optional Path to working directory to run in - input_file : str + path_input : str Name of a single XML input file for the OpenMC executable to read. Raises @@ -144,12 +145,12 @@ def plot_geometry(output=True, openmc_exec='openmc', cwd='.', input_file=None): """ args = [openmc_exec, '-p'] - if input_file is not None: - args += ['-i', input_file] + if path_input is not None: + args += ['-i', path_input] _run([openmc_exec, '-p'], output, cwd) -def plot_inline(plots, openmc_exec='openmc', cwd='.', input_file=None): +def plot_inline(plots, openmc_exec='openmc', cwd='.', path_input=None): """Display plots inline in a Jupyter notebook. .. versionchanged:: 0.13.0 @@ -165,7 +166,7 @@ def plot_inline(plots, openmc_exec='openmc', cwd='.', input_file=None): Path to OpenMC executable cwd : str, optional Path to working directory to run in - input_file : str + path_input : str Name of a single XML input file for the OpenMC executable to read. Raises @@ -183,7 +184,7 @@ def plot_inline(plots, openmc_exec='openmc', cwd='.', input_file=None): openmc.Plots(plots).export_to_xml(cwd) # Run OpenMC in geometry plotting mode - plot_geometry(False, openmc_exec, cwd, input_file) + plot_geometry(False, openmc_exec, cwd, path_input) if plots is not None: images = [_get_plot_image(p, cwd) for p in plots] @@ -192,7 +193,7 @@ def plot_inline(plots, openmc_exec='openmc', cwd='.', input_file=None): def calculate_volumes(threads=None, output=True, cwd='.', openmc_exec='openmc', mpi_args=None, - input_file=None): + path_input=None): """Run stochastic volume calculations in OpenMC. This function runs OpenMC in stochastic volume calculation mode. To specify @@ -223,7 +224,7 @@ def calculate_volumes(threads=None, output=True, cwd='.', cwd : str, optional Path to working directory to run in. Defaults to the current working directory. - input_file : str or Pathlike + path_input : str or Pathlike Name of a single XML input file for the OpenMC executable to read. Raises @@ -239,7 +240,7 @@ def calculate_volumes(threads=None, output=True, cwd='.', args = _process_CLI_arguments(volume=True, threads=threads, openmc_exec=openmc_exec, mpi_args=mpi_args, - input_file=input_file) + path_input=path_input) _run(args, output, cwd) @@ -247,7 +248,7 @@ def calculate_volumes(threads=None, output=True, 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, - input_file=None): + path_input=None): """Run an OpenMC simulation. Parameters @@ -282,7 +283,7 @@ def run(particles=None, threads=None, geometry_debug=False, .. versionadded:: 0.12 - input_file : str or Pathlike + path_input : str or Pathlike Name of a single XML input file for the OpenMC executable to read. Raises @@ -296,6 +297,6 @@ def run(particles=None, threads=None, geometry_debug=False, 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, - input_file=input_file) + path_input=path_input) _run(args, output, cwd) From d639d1dc1b2796e9fae599d74283b9f125e3801a Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Tue, 6 Dec 2022 14:41:11 -0600 Subject: [PATCH 41/53] Docstring correction --- openmc/model/model.py | 1 - 1 file changed, 1 deletion(-) diff --git a/openmc/model/model.py b/openmc/model/model.py index 34cc085b8..a7a6f2d7d 100644 --- a/openmc/model/model.py +++ b/openmc/model/model.py @@ -450,7 +450,6 @@ class Model: Path to an output filename or directory. Only used if :math:`separate_xmls` is False. .. versionadded:: 0.13.1 - separate_xmls : bool Whether or not to write a single model.xml file or many XML files. """ From 939adbe70e2c73e2d503260bc5a912812d1b0087 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Tue, 6 Dec 2022 14:41:26 -0600 Subject: [PATCH 42/53] Updating tests to reflect changes to API --- tests/regression_tests/model_xml/test.py | 12 +++++++++--- tests/unit_tests/test_model.py | 14 +++++++------- 2 files changed, 16 insertions(+), 10 deletions(-) diff --git a/tests/regression_tests/model_xml/test.py b/tests/regression_tests/model_xml/test.py index 9da5438db..8aa9e5c2e 100644 --- a/tests/regression_tests/model_xml/test.py +++ b/tests/regression_tests/model_xml/test.py @@ -1,6 +1,8 @@ from difflib import unified_diff +import glob import filecmp import os +from pathlib import Path import openmc import pytest @@ -83,13 +85,17 @@ def test_input_arg(run_in_tmpdir): pincell.export_to_xml(separate_xmls=True) openmc.run() + # make sure the executable isn't falling back on the separate XMLs + [os.remove(f) for f in glob.glob('*.xml')] # now export to a single XML file with a custom name - pincell.export_to_xml(filename='pincell.xml', separate_xmls=False) + pincell.export_to_xml(path='pincell.xml', separate_xmls=False) + assert Path('pincell.xml').exists() # run by specifying that single file - openmc.run(input_file='pincell.xml') + openmc.run(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_xml(separate_xmls=True) with pytest.raises(RuntimeError, match='ex-em-ell.xml'): - openmc.run(input_file='ex-em-ell.xml') \ No newline at end of file + openmc.run(path_input='ex-em-ell.xml') \ No newline at end of file diff --git a/tests/unit_tests/test_model.py b/tests/unit_tests/test_model.py index 5d4765d65..11a88d76c 100644 --- a/tests/unit_tests/test_model.py +++ b/tests/unit_tests/test_model.py @@ -543,7 +543,7 @@ def test_model_xml(run_in_tmpdir): # now write and read a model.xml file pwr_model.export_to_xml(separate_xmls=False) - new_model = openmc.Model.from_xml(separate_xmls=False) + new_model = openmc.Model.from_model_xml() # make sure we can also export this again to separate # XML files @@ -553,17 +553,17 @@ def test_model_exec(run_in_tmpdir): pincell_model = openmc.examples.pwr_pin_cell() - pincell_model.export_to_xml(filename='pwr_pincell.xml', separate_xmls=False) + pincell_model.export_to_xml(path='pwr_pincell.xml', separate_xmls=False) - openmc.run(input_file='pwr_pincell.xml') + openmc.run(path_input='pwr_pincell.xml') with pytest.raises(RuntimeError, match='ex-em-ell.xml'): - openmc.run(input_file='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_xml(directory='./inputs', filename='pincell.xml', separate_xmls=False) - openmc.run(input_file='./inputs/pincell.xml') + pincell_model.export_to_xml(path='./inputs/pincell.xml', separate_xmls=False) + openmc.run(path_input='./inputs/pincell.xml') with pytest.raises(RuntimeError, match='input_dir'): - openmc.run(input_file='input_dir/pincell.xml') \ No newline at end of file + openmc.run(path_input='input_dir/pincell.xml') \ No newline at end of file From 7fdfed22f37e2c50913ba96adce55da98daf4c40 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Tue, 6 Dec 2022 21:49:46 -0600 Subject: [PATCH 43/53] Some cleanup of file utils --- include/openmc/file_utils.h | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/include/openmc/file_utils.h b/include/openmc/file_utils.h index ae3a26e94..439af6ee0 100644 --- a/include/openmc/file_utils.h +++ b/include/openmc/file_utils.h @@ -7,7 +7,7 @@ namespace openmc { -// TODO: replace with std::filesysem when switch to C++17 is made +// 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 @@ -27,7 +27,6 @@ inline bool file_exists(const std::string& filename) if (is_dir(filename)) return false; std::ifstream s {filename}; - s.seekg(0, std::ios::beg); return s.good(); } From 55d77cf06c86726d9ebaf7abcb8de232b01af505 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Tue, 6 Dec 2022 21:50:08 -0600 Subject: [PATCH 44/53] Small improvements to read_model_xml --- src/initialize.cpp | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/initialize.cpp b/src/initialize.cpp index b75f410a1..041712d5a 100644 --- a/src/initialize.cpp +++ b/src/initialize.cpp @@ -284,7 +284,9 @@ int parse_command_line(int argc, char* argv[]) // check that the path is either a valid directory or file if (!is_dir(settings::path_input) && !file_exists(settings::path_input)) { - fatal_error(fmt::format("The specified path '{}' does not exist.", 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 @@ -297,13 +299,14 @@ int parse_command_line(int argc, char* argv[]) } bool read_model_xml() { - std::string model_filename = settings::path_input; + std::string model_filename = + settings::path_input.empty() ? "." : settings::path_input; // some string cleanup - // a trailing "/" is applied to path_input if it's present, + // 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 (model_filename.length() == 0) model_filename = "."; + if (ends_with(model_filename, "/")) + model_filename.pop_back(); // if the current filename is a directory, append the default model filename if (is_dir(model_filename)) model_filename += "/model.xml"; @@ -313,7 +316,6 @@ bool read_model_xml() { // try to process the path input as an XML file pugi::xml_document doc; - // if the input is a file, try to read it if (!doc.load_file(model_filename.c_str())) { fatal_error(fmt::format( "Error reading from single XML input file '{}'", model_filename)); From 7873b4d6c1bfb983a7ec4c87ad29b492d4bbb088 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Tue, 6 Dec 2022 21:50:49 -0600 Subject: [PATCH 45/53] Update tests/regression_tests/model_xml/test.py Co-authored-by: Paul Romano --- tests/regression_tests/model_xml/test.py | 5 ----- 1 file changed, 5 deletions(-) diff --git a/tests/regression_tests/model_xml/test.py b/tests/regression_tests/model_xml/test.py index 8aa9e5c2e..d040f4649 100644 --- a/tests/regression_tests/model_xml/test.py +++ b/tests/regression_tests/model_xml/test.py @@ -32,11 +32,6 @@ class ModelXMLTestHarness(PyAPITestHarness): def _get_inputs(self): return open('model.xml').read() - # def _compare_inputs(self): - # """Skip input comparisons for now - # """ - # pass - def _compare_results(self): """Make sure the current results agree with the reference.""" compare = filecmp.cmp('results_test.dat', self.results_true) From ef13643f297dfe030c644d7a776fd85be54afdaa Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Wed, 7 Dec 2022 08:28:13 -0600 Subject: [PATCH 46/53] Some housekeeping in the Python classes --- openmc/model/model.py | 8 ++------ openmc/settings.py | 38 ++++++++++++++++++++++++++------------ 2 files changed, 28 insertions(+), 18 deletions(-) diff --git a/openmc/model/model.py b/openmc/model/model.py index a7a6f2d7d..1fdd12730 100644 --- a/openmc/model/model.py +++ b/openmc/model/model.py @@ -496,13 +496,10 @@ class Model: self.plots.export_to_xml(d) def _export_to_single_xml(self, path='model.xml', remove_surfs=False): - """Export model to XML files. + """Export model to a single XML file. Parameters ---------- - directory : str - Directory to write the model.xml file to. If it doesn't exist already, it - will be created. path : str or Pathlike Location of the XML file to write. Can be a directory or file path. remove_surfs : bool @@ -530,8 +527,7 @@ class Model: # Can be used to modify tallies in case any surfaces are redundant redundant_surfaces = self.geometry.remove_redundant_surfaces() - memo = set() - settings_element = self.settings.to_xml_element(memo) + settings_element = self.settings.to_xml_element() geometry_element = self.geometry.to_xml_element() xml.clean_indentation(geometry_element, level=1) diff --git a/openmc/settings.py b/openmc/settings.py index 3c1c8c7f5..3f56f729f 100644 --- a/openmc/settings.py +++ b/openmc/settings.py @@ -1073,7 +1073,7 @@ class Settings: subelement = ET.SubElement(element, key) subelement.text = str(value) - def _create_entropy_mesh_subelement(self, root, memo=None): + def _create_entropy_mesh_subelement(self, root, mesh_memo=None): 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: @@ -1085,13 +1085,20 @@ class Settings: d = len(self.entropy_mesh.lower_left) self.entropy_mesh.dimension = (n,)*d + # 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 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()) - - subelement = ET.SubElement(root, "entropy_mesh") - subelement.text = str(self.entropy_mesh.id) + 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: @@ -1207,20 +1214,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, memo=None): + 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()) - # check the memo for a mesh - if memo and ww.mesh.id in memo: + # 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 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 memo is not None: memo.add(ww.mesh.id) + 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") @@ -1544,10 +1552,16 @@ class Settings: if text is not None: self.max_tracks = int(text) - def to_xml_element(self, memo=None): + 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. + """ + # create a memo object if one isn't passed in already + mesh_memo = mesh_memo if mesh_memo else set() # Reset xml element tree element = ET.Element("settings") @@ -1574,7 +1588,7 @@ class Settings: self._create_seed_subelement(element) self._create_survival_biasing_subelement(element) self._create_cutoff_subelement(element) - self._create_entropy_mesh_subelement(element, memo) + self._create_entropy_mesh_subelement(element, mesh_memo) self._create_trigger_subelement(element) self._create_no_reduce_subelement(element) self._create_verbosity_subelement(element) @@ -1592,7 +1606,7 @@ class Settings: 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, memo) + self._create_weight_windows_subelement(element, mesh_memo) self._create_max_splits_subelement(element) self._create_max_tracks_subelement(element) From 9c942db45eaca790dc0f7f142b801381d2574daa Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Wed, 7 Dec 2022 10:10:22 -0600 Subject: [PATCH 47/53] Correct use of mesh memo in single XML export --- openmc/model/model.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/openmc/model/model.py b/openmc/model/model.py index 1fdd12730..a54cd6076 100644 --- a/openmc/model/model.py +++ b/openmc/model/model.py @@ -527,7 +527,9 @@ class Model: # Can be used to modify tallies in case any surfaces are redundant redundant_surfaces = self.geometry.remove_redundant_surfaces() - settings_element = self.settings.to_xml_element() + # 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) @@ -554,7 +556,7 @@ class Model: ET.ElementTree(settings_element).write(fh, encoding='unicode') if self.tallies: - tallies_element = self.tallies.to_xml_element(memo) + 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: From 916863f82ae304ff6f936701cbb058c5e7c2583a Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Wed, 7 Dec 2022 11:02:10 -0600 Subject: [PATCH 48/53] Renaming and docstring/comment updates --- include/openmc/file_utils.h | 8 +++++--- include/openmc/geometry_aux.h | 2 +- openmc/settings.py | 2 -- src/initialize.cpp | 6 ++++-- 4 files changed, 10 insertions(+), 8 deletions(-) diff --git a/include/openmc/file_utils.h b/include/openmc/file_utils.h index 439af6ee0..cb52b33c8 100644 --- a/include/openmc/file_utils.h +++ b/include/openmc/file_utils.h @@ -11,7 +11,8 @@ namespace openmc { //! Determine if a path is a directory //! \param[in] path Path to check //! \return Whether the path is a directory -inline bool is_dir(const std::string& path) { +inline bool dir_exists(const std::string& path) +{ struct stat s; if (stat(path.c_str(), &s) != 0) return false; @@ -23,8 +24,9 @@ inline bool is_dir(const std::string& path) { //! \return Whether file exists inline bool file_exists(const std::string& filename) { - // rule out file being a directory path - if (is_dir(filename)) return false; + // rule out file being a path to a directory + if (dir_exists(filename)) + return false; std::ifstream s {filename}; return s.good(); diff --git a/include/openmc/geometry_aux.h b/include/openmc/geometry_aux.h index a4c506c31..cf62debc0 100644 --- a/include/openmc/geometry_aux.h +++ b/include/openmc/geometry_aux.h @@ -24,7 +24,7 @@ extern std::unordered_map universe_level_counts; void read_geometry_xml(); //! Read geometry from XML node -//! \param[in] root node of geometry XML element +//! \param[in] root node of geometry XML element void read_geometry_xml(pugi::xml_node root); //============================================================================== diff --git a/openmc/settings.py b/openmc/settings.py index 3f56f729f..294aea7a3 100644 --- a/openmc/settings.py +++ b/openmc/settings.py @@ -1560,8 +1560,6 @@ class Settings: mesh_memo : set of ints A set of mesh IDs to keep track of whether a mesh has already been written. """ - # create a memo object if one isn't passed in already - mesh_memo = mesh_memo if mesh_memo else set() # Reset xml element tree element = ET.Element("settings") diff --git a/src/initialize.cpp b/src/initialize.cpp index 041712d5a..3b332c3b2 100644 --- a/src/initialize.cpp +++ b/src/initialize.cpp @@ -283,7 +283,8 @@ int parse_command_line(int argc, char* argv[]) settings::path_input = std::string(argv[last_flag + 1]); // check that the path is either a valid directory or file - if (!is_dir(settings::path_input) && !file_exists(settings::path_input)) { + 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)); @@ -309,7 +310,8 @@ bool read_model_xml() { model_filename.pop_back(); // if the current filename is a directory, append the default model filename - if (is_dir(model_filename)) model_filename += "/model.xml"; + if (dir_exists(model_filename)) + model_filename += "/model.xml"; // if this file doesn't exist, stop here if (!file_exists(model_filename)) return false; From 145594ebb8697ff2c711b6a8f57abbfedfebf8c7 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Mon, 12 Dec 2022 20:23:49 -0600 Subject: [PATCH 49/53] Apply @paulromano suggestions from code review Co-authored-by: Paul Romano --- openmc/_xml.py | 2 +- openmc/executor.py | 3 +-- openmc/material.py | 1 - openmc/model/model.py | 2 +- src/initialize.cpp | 4 ++-- tests/regression_tests/model_xml/test.py | 3 ++- 6 files changed, 7 insertions(+), 8 deletions(-) diff --git a/openmc/_xml.py b/openmc/_xml.py index 6d298638d..aeaeb45b5 100644 --- a/openmc/_xml.py +++ b/openmc/_xml.py @@ -16,7 +16,7 @@ def clean_indentation(element, level=0, spaces_per_level=2, trailing_indent=True """ i = "\n" + level*spaces_per_level*" " - # ensure there's awlays some tail for the element passed in + # ensure there's always some tail for the element passed in if not element.tail: element.tail = "" diff --git a/openmc/executor.py b/openmc/executor.py index 9267366e8..38421ecc2 100644 --- a/openmc/executor.py +++ b/openmc/executor.py @@ -95,7 +95,6 @@ def _run(args, output, cwd): p = subprocess.Popen(args, cwd=cwd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True) - print(args) # Capture and re-print OpenMC output in real-time lines = [] while True: @@ -146,7 +145,7 @@ def plot_geometry(output=True, openmc_exec='openmc', cwd='.', path_input=None): """ args = [openmc_exec, '-p'] if path_input is not None: - args += ['-i', path_input] + args += [path_input] _run([openmc_exec, '-p'], output, cwd) diff --git a/openmc/material.py b/openmc/material.py index 332f8c965..aa778cab6 100644 --- a/openmc/material.py +++ b/openmc/material.py @@ -1548,7 +1548,6 @@ class Materials(cv.CheckedList): return materials - @classmethod def from_xml(cls, path: PathLike = 'materials.xml'): """Generate materials collection from XML file diff --git a/openmc/model/model.py b/openmc/model/model.py index a54cd6076..9983d5c60 100644 --- a/openmc/model/model.py +++ b/openmc/model/model.py @@ -259,7 +259,7 @@ class Model: materials = {str(m.id): m for m in model.materials} model.geometry = openmc.Geometry.from_xml_element(root.find('geometry'), materials) - # gather meshses from other classes before reading the tally node + # gather meshes from other classes before reading the tally node meshes = {} if model.settings.entropy_mesh is not None: meshes[model.settings.entropy_mesh.id] = model.settings.entropy_mesh diff --git a/src/initialize.cpp b/src/initialize.cpp index 3b332c3b2..0c137795b 100644 --- a/src/initialize.cpp +++ b/src/initialize.cpp @@ -352,7 +352,7 @@ bool read_model_xml() { 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 file " + warning((fmt::format("Other XML file input(s) are present. These files " "will be ignored in favor of the {} file.", model_filename))); break; @@ -371,7 +371,7 @@ bool read_model_xml() { // Read geometry if (!check_for_node(root, "geometry")) { fatal_error(fmt::format( - "No node present in the model.xml_file.", model_filename)); + "No node present in the {} file.", model_filename)); } read_geometry_xml(root.child("geometry")); diff --git a/tests/regression_tests/model_xml/test.py b/tests/regression_tests/model_xml/test.py index d040f4649..fbff02357 100644 --- a/tests/regression_tests/model_xml/test.py +++ b/tests/regression_tests/model_xml/test.py @@ -81,7 +81,8 @@ def test_input_arg(run_in_tmpdir): openmc.run() # make sure the executable isn't falling back on the separate XMLs - [os.remove(f) for f in glob.glob('*.xml')] + for f in glob.glob('*.xml'): + os.remove(f) # now export to a single XML file with a custom name pincell.export_to_xml(path='pincell.xml', separate_xmls=False) assert Path('pincell.xml').exists() From f3bcad37b72629cfc64e857aebdbe7c38461cabb Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Mon, 12 Dec 2022 21:18:48 -0600 Subject: [PATCH 50/53] Addressing comments from @paulromano --- openmc/executor.py | 32 ++++++----- openmc/geometry.py | 19 ++++--- openmc/model/model.py | 33 ++---------- openmc/settings.py | 68 +++++++++++++----------- tests/regression_tests/model_xml/test.py | 11 ++-- tests/unit_tests/test_model.py | 10 ++-- 6 files changed, 85 insertions(+), 88 deletions(-) diff --git a/openmc/executor.py b/openmc/executor.py index 38421ecc2..22862a7cf 100644 --- a/openmc/executor.py +++ b/openmc/executor.py @@ -43,7 +43,8 @@ def _process_CLI_arguments(volume=False, geometry_debug=False, particles=None, MPI execute command and any additional MPI arguments to pass, e.g. ['mpiexec', '-n', '8']. path_input : str or Pathlike - Name of a single XML input file for the OpenMC executable to read. + Path to a single XML file or a directory containing XML files for the + OpenMC executable to read. .. versionadded:: 0.13.0 @@ -135,7 +136,8 @@ def plot_geometry(output=True, openmc_exec='openmc', cwd='.', path_input=None): cwd : str, optional Path to working directory to run in path_input : str - Name of a single XML input file for the OpenMC executable to read. + Path to a single XML file or a directory containing XML files for the + OpenMC executable to read. Raises ------ @@ -146,7 +148,7 @@ def plot_geometry(output=True, openmc_exec='openmc', cwd='.', path_input=None): args = [openmc_exec, '-p'] if path_input is not None: args += [path_input] - _run([openmc_exec, '-p'], output, cwd) + _run(args, output, cwd) def plot_inline(plots, openmc_exec='openmc', cwd='.', path_input=None): @@ -166,7 +168,8 @@ def plot_inline(plots, openmc_exec='openmc', cwd='.', path_input=None): cwd : str, optional Path to working directory to run in path_input : str - Name of a single XML input file for the OpenMC executable to read. + Path to a single XML file or a directory containing XML files for the + OpenMC executable to read. Raises ------ @@ -224,7 +227,9 @@ def calculate_volumes(threads=None, output=True, cwd='.', Path to working directory to run in. Defaults to the current working directory. path_input : str or Pathlike - Name of a single XML input file for the OpenMC executable to read. + Path to a single XML file or a directory containing XML files for the + OpenMC executable to read. + Raises ------ @@ -256,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 @@ -275,15 +280,16 @@ 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 - Name of a single XML input file for the OpenMC executable to read. + Path to a single XML file or a directory containing XML files for the + OpenMC executable to read. Raises ------ diff --git a/openmc/geometry.py b/openmc/geometry.py index afcae1d55..a43899daa 100644 --- a/openmc/geometry.py +++ b/openmc/geometry.py @@ -179,6 +179,11 @@ class 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): @@ -236,7 +241,7 @@ class Geometry: child_of[u].append(lat) for e in elem.findall('cell'): - c = openmc.Cell.from_xml_element(e, surfaces, materials, get_universe) + c = openmc.Cell.from_xml_element(e, surfaces, mats, get_universe) if c.fill_type in ('universe', 'lattice'): child_of[c.fill].append(c) @@ -266,17 +271,15 @@ class Geometry: Geometry object """ - tree = ET.parse(path) - root = tree.getroot() - - # Create dictionary to easily look up materials + # 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 - return cls.from_xml_element(root, mats) + tree = ET.parse(path) + root = tree.getroot() + + return cls.from_xml_element(root, materials) def find(self, point): """Find cells/universes/lattices which contain a given point diff --git a/openmc/model/model.py b/openmc/model/model.py index 9983d5c60..a8dc1ce48 100644 --- a/openmc/model/model.py +++ b/openmc/model/model.py @@ -256,8 +256,7 @@ class Model: model.settings = openmc.Settings.from_xml_element(root.find('settings')) model.materials = openmc.Materials.from_xml_element(root.find('materials')) - materials = {str(m.id): m for m in model.materials} - model.geometry = openmc.Geometry.from_xml_element(root.find('geometry'), materials) + model.geometry = openmc.Geometry.from_xml_element(root.find('geometry'), model.materials) # gather meshes from other classes before reading the tally node meshes = {} @@ -435,30 +434,7 @@ class Model: depletion_operator.cleanup_when_done = True depletion_operator.finalize() - def export_to_xml(self, directory='.', remove_surfs=False, separate_xmls=True, path='model.xml'): - """Export model to an XML file(s). - - Parameters - ---------- - directory : str - Directory to write XML files to. If it doesn't exist already, it - will be created. Only used if :math:`separate_xmls` is True. - remove_surfs : bool - Whether or not to remove redundant surfaces from the geometry when - exporting. - path : str - Path to an output filename or directory. Only used if :math:`separate_xmls` is False. - - .. versionadded:: 0.13.1 - separate_xmls : bool - Whether or not to write a single model.xml file or many XML files. - """ - if separate_xmls: - self._export_to_separate_xmls(directory, remove_surfs) - else: - self._export_to_single_xml(path, remove_surfs) - - def _export_to_separate_xmls(self, directory='.', remove_surfs=False): + def export_to_xml(self, directory='.', remove_surfs=False): """Export model to separate XML files. Parameters @@ -495,13 +471,14 @@ class Model: if self.plots: self.plots.export_to_xml(d) - def _export_to_single_xml(self, path='model.xml', remove_surfs=False): + def export_to_model_xml(self, path='model.xml', remove_surfs=False): """Export model to a single XML file. Parameters ---------- path : str or Pathlike - Location of the XML file to write. Can be a directory or file path. + 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. diff --git a/openmc/settings.py b/openmc/settings.py index 294aea7a3..8f6d3ec07 100644 --- a/openmc/settings.py +++ b/openmc/settings.py @@ -1074,31 +1074,33 @@ class Settings: subelement.text = str(value) def _create_entropy_mesh_subelement(self, root, mesh_memo=None): - 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 + if self.entropy_mesh is None: + return - # add mesh ID to this element - subelement = ET.SubElement(root, "entropy_mesh") - subelement.text = str(self.entropy_mesh.id) + # 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 - # 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 + # add mesh ID to this element + subelement = ET.SubElement(root, "entropy_mesh") + subelement.text = str(self.entropy_mesh.id) - # See if a 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) + # 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 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: @@ -1149,15 +1151,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 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 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 diff --git a/tests/regression_tests/model_xml/test.py b/tests/regression_tests/model_xml/test.py index fbff02357..c67a72ed3 100644 --- a/tests/regression_tests/model_xml/test.py +++ b/tests/regression_tests/model_xml/test.py @@ -27,7 +27,7 @@ class ModelXMLTestHarness(PyAPITestHarness): self.results_true = 'results_true.dat' if results_true is None else results_true def _build_inputs(self): - self._model.export_to_xml(separate_xmls=False) + self._model.export_to_model_xml() def _get_inputs(self): return open('model.xml').read() @@ -77,21 +77,24 @@ def test_input_arg(run_in_tmpdir): pincell.settings.particles = 100 # export to separate XML files and run - pincell.export_to_xml(separate_xmls=True) + 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_xml(path='pincell.xml', separate_xmls=False) + 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_xml(separate_xmls=True) + pincell.export_to_model_xml() with pytest.raises(RuntimeError, match='ex-em-ell.xml'): openmc.run(path_input='ex-em-ell.xml') \ No newline at end of file diff --git a/tests/unit_tests/test_model.py b/tests/unit_tests/test_model.py index 11a88d76c..6c483aeb9 100644 --- a/tests/unit_tests/test_model.py +++ b/tests/unit_tests/test_model.py @@ -542,18 +542,18 @@ def test_model_xml(run_in_tmpdir): pwr_model.geometry.export_to_xml('geometry_ref.xml') # now write and read a model.xml file - pwr_model.export_to_xml(separate_xmls=False) + 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(separate_xmls=True) + new_model.export_to_xml() -def test_model_exec(run_in_tmpdir): +def test_single_xml_exec(run_in_tmpdir): pincell_model = openmc.examples.pwr_pin_cell() - pincell_model.export_to_xml(path='pwr_pincell.xml', separate_xmls=False) + pincell_model.export_to_model_xml('pwr_pincell.xml') openmc.run(path_input='pwr_pincell.xml') @@ -562,7 +562,7 @@ def test_model_exec(run_in_tmpdir): # test that a file in a different directory can be used os.mkdir('inputs') - pincell_model.export_to_xml(path='./inputs/pincell.xml', separate_xmls=False) + pincell_model.export_to_model_xml('./inputs/pincell.xml') openmc.run(path_input='./inputs/pincell.xml') with pytest.raises(RuntimeError, match='input_dir'): From 43687e5194f854d6d2a9fdf6287ec0c9b45bf3a1 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Mon, 12 Dec 2022 21:40:48 -0600 Subject: [PATCH 51/53] Improve comment on recursion arguments --- openmc/_xml.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/openmc/_xml.py b/openmc/_xml.py index aeaeb45b5..2a33d4b8d 100644 --- a/openmc/_xml.py +++ b/openmc/_xml.py @@ -26,8 +26,10 @@ def clean_indentation(element, level=0, spaces_per_level=2, trailing_indent=True if trailing_indent and (not element.tail or not element.tail.strip()): element.tail = i for sub_element in element: - # `trailing_indent` intentionally not passed to the recursive call. - # it only applies to the element for the initial of this function. + # `trailing_indent` is intentionally not forwarded to the recursive + # call. Any child element of the topmost clement 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 From 2eca57a299ce91b9ddb68b8d6b40313ad6ddd708 Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Tue, 13 Dec 2022 23:16:57 -0600 Subject: [PATCH 52/53] Adding mesh memo to Settings.from_xml_element --- openmc/model/model.py | 4 ++-- openmc/settings.py | 25 ++++++++++++++++++------- openmc/tallies.py | 4 ++++ 3 files changed, 24 insertions(+), 9 deletions(-) diff --git a/openmc/model/model.py b/openmc/model/model.py index a8dc1ce48..7c6d9b828 100644 --- a/openmc/model/model.py +++ b/openmc/model/model.py @@ -254,12 +254,12 @@ class Model: model = cls() - model.settings = openmc.Settings.from_xml_element(root.find('settings')) + 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) # gather meshes from other classes before reading the tally node - meshes = {} if model.settings.entropy_mesh is not None: meshes[model.settings.entropy_mesh.id] = model.settings.entropy_mesh diff --git a/openmc/settings.py b/openmc/settings.py index 8f6d3ec07..65ca71957 100644 --- a/openmc/settings.py +++ b/openmc/settings.py @@ -1417,13 +1417,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') @@ -1483,13 +1485,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') @@ -1541,7 +1545,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) @@ -1550,6 +1554,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: @@ -1643,13 +1650,17 @@ class Settings: tree.write(str(p), xml_declaration=True, encoding='utf-8') @classmethod - def from_xml_element(cls, elem): + 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 ------- @@ -1683,7 +1694,7 @@ class Settings: 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) + 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) @@ -1691,7 +1702,7 @@ class Settings: 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) + 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) @@ -1700,7 +1711,7 @@ class Settings: 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) + settings._weight_windows_from_xml_element(elem, meshes) settings._max_splits_from_xml_element(elem) settings._max_tracks_from_xml_element(elem) diff --git a/openmc/tallies.py b/openmc/tallies.py index e17e7c99d..b22cc4cf0 100644 --- a/openmc/tallies.py +++ b/openmc/tallies.py @@ -3199,6 +3199,10 @@ class Tallies(cv.CheckedList): ---------- 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 ------- From 25f2bd50f9005cf8fa05ea055ebce0384d488106 Mon Sep 17 00:00:00 2001 From: Paul Romano Date: Thu, 22 Dec 2022 18:27:42 -0600 Subject: [PATCH 53/53] Several small fixes --- openmc/_xml.py | 2 +- openmc/model/model.py | 12 ++++-------- openmc/settings.py | 5 +++-- 3 files changed, 8 insertions(+), 11 deletions(-) diff --git a/openmc/_xml.py b/openmc/_xml.py index 2a33d4b8d..6799a4e2d 100644 --- a/openmc/_xml.py +++ b/openmc/_xml.py @@ -27,7 +27,7 @@ def clean_indentation(element, level=0, spaces_per_level=2, trailing_indent=True element.tail = i for sub_element in element: # `trailing_indent` is intentionally not forwarded to the recursive - # call. Any child element of the topmost clement should add + # 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) diff --git a/openmc/model/model.py b/openmc/model/model.py index 7c6d9b828..2f4489084 100644 --- a/openmc/model/model.py +++ b/openmc/model/model.py @@ -244,6 +244,8 @@ class Model: def from_model_xml(cls, path='model.xml'): """Create model from single XML file + .. vesionadded:: 0.13.3 + Parameters ---------- path : str or Pathlike @@ -259,13 +261,6 @@ class Model: model.materials = openmc.Materials.from_xml_element(root.find('materials')) model.geometry = openmc.Geometry.from_xml_element(root.find('geometry'), model.materials) - # gather meshes from other classes before reading the tally node - if model.settings.entropy_mesh is not None: - meshes[model.settings.entropy_mesh.id] = model.settings.entropy_mesh - - for ww in model.settings.weight_windows: - meshes[ww.mesh.id] = ww.mesh - if root.find('tallies'): model.tallies = openmc.Tallies.from_xml_element(root.find('tallies'), meshes) @@ -474,6 +469,8 @@ class Model: 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 @@ -483,7 +480,6 @@ class Model: Whether or not to remove redundant surfaces from the geometry when exporting. - .. versionadded:: 0.13.1 """ xml_path = Path(path) # if the provided path doesn't end with the XML extension, assume the diff --git a/openmc/settings.py b/openmc/settings.py index 65ca71957..4e6f144fe 100644 --- a/openmc/settings.py +++ b/openmc/settings.py @@ -1100,7 +1100,8 @@ class Settings: 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) + 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: @@ -1609,7 +1610,7 @@ class Settings: self._create_temperature_subelements(element) self._create_trace_subelement(element) self._create_track_subelement(element) - self._create_ufs_mesh_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)