From f3bcad37b72629cfc64e857aebdbe7c38461cabb Mon Sep 17 00:00:00 2001 From: Patrick Shriwise Date: Mon, 12 Dec 2022 21:18:48 -0600 Subject: [PATCH] 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'):