Skip to content

Commit

Permalink
Consistency in treatment of paths for files specified within the Mode…
Browse files Browse the repository at this point in the history
…l class (#3153)
  • Loading branch information
paulromano authored Oct 10, 2024
1 parent fb3aaa4 commit 579777a
Show file tree
Hide file tree
Showing 15 changed files with 142 additions and 71 deletions.
25 changes: 17 additions & 8 deletions docs/source/usersguide/geometry.rst
Original file line number Diff line number Diff line change
Expand Up @@ -474,7 +474,7 @@ applied as universes in the OpenMC geometry file. A geometry represented
entirely by a DAGMC geometry will contain only the DAGMC universe. Using a
:class:`openmc.DAGMCUniverse` looks like the following::

dag_univ = openmc.DAGMCUniverse(filename='dagmc.h5m')
dag_univ = openmc.DAGMCUniverse('dagmc.h5m')
geometry = openmc.Geometry(dag_univ)
geometry.export_to_xml()

Expand All @@ -495,13 +495,22 @@ It is important in these cases to understand the DAGMC model's position
with respect to the CSG geometry. DAGMC geometries can be plotted with
OpenMC to verify that the model matches one's expectations.

**Note:** DAGMC geometries used in OpenMC are currently required to be clean,
meaning that all surfaces have been `imprinted and merged
<https://svalinn.github.io/DAGMC/usersguide/cubit_basics.html>`_ successfully
and that the model is `watertight
<https://svalinn.github.io/DAGMC/usersguide/tools.html#make-watertight>`_.
Future implementations of DAGMC geometry will support small volume overlaps and
un-merged surfaces.
By default, when you specify a .h5m file for a :class:`~openmc.DAGMCUniverse`
instance, it will store the absolute path to the .h5m file. If you prefer to
store the relative path, you can set the ``'resolve_paths'`` configuration
variable::

openmc.config['resolve_paths'] = False
dag_univ = openmc.DAGMCUniverse('dagmc.h5m')

.. note::
DAGMC geometries used in OpenMC are currently required to be clean,
meaning that all surfaces have been `imprinted and merged
<https://svalinn.github.io/DAGMC/usersguide/cubit_basics.html>`_ successfully
and that the model is `watertight
<https://svalinn.github.io/DAGMC/usersguide/tools.html#make-watertight>`_.
Future implementations of DAGMC geometry will support small volume overlaps and
un-merged surfaces.

Cell, Surface, and Material IDs
-------------------------------
Expand Down
27 changes: 24 additions & 3 deletions openmc/config.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from collections.abc import MutableMapping
from contextlib import contextmanager
import os
from pathlib import Path
import warnings
Expand All @@ -11,7 +12,7 @@

class _Config(MutableMapping):
def __init__(self, data=()):
self._mapping = {}
self._mapping = {'resolve_paths': True}
self.update(data)

def __getitem__(self, key):
Expand Down Expand Up @@ -42,10 +43,12 @@ def __setitem__(self, key, value):
# Reset photon source data since it relies on chain file
_DECAY_PHOTON_ENERGY.clear()
_DECAY_ENERGY.clear()
elif key == 'resolve_paths':
self._mapping[key] = value
else:
raise KeyError(f'Unrecognized config key: {key}. Acceptable keys '
'are "cross_sections", "mg_cross_sections" and '
'"chain_file"')
'are "cross_sections", "mg_cross_sections", '
'"chain_file", and "resolve_paths".')

def __iter__(self):
return iter(self._mapping)
Expand All @@ -61,6 +64,24 @@ def _set_path(self, key, value):
if not p.exists():
warnings.warn(f"'{value}' does not exist.")

@contextmanager
def patch(self, key, value):
"""Temporarily change a value in the configuration.
Parameters
----------
key : str
Key to change
value : object
New value
"""
previous_value = self.get(key)
self[key] = value
yield
if previous_value is None:
del self[key]
else:
self[key] = previous_value

def _default_config():
"""Return default configuration"""
Expand Down
3 changes: 2 additions & 1 deletion openmc/material.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
import openmc.checkvalue as cv
from ._xml import clean_indentation, reorder_attributes
from .mixin import IDManagerMixin
from .utility_funcs import input_path
from openmc.checkvalue import PathLike
from openmc.stats import Univariate, Discrete, Mixture
from openmc.data.data import _get_element_symbol
Expand Down Expand Up @@ -1643,7 +1644,7 @@ def cross_sections(self) -> Path | None:
@cross_sections.setter
def cross_sections(self, cross_sections):
if cross_sections is not None:
self._cross_sections = Path(cross_sections)
self._cross_sections = input_path(cross_sections)

def append(self, material):
"""Append material to collection
Expand Down
9 changes: 4 additions & 5 deletions openmc/mesh.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@
from functools import wraps
from math import pi, sqrt, atan2
from numbers import Integral, Real
from pathlib import Path
import tempfile

import h5py
import lxml.etree as ET
Expand All @@ -19,6 +17,7 @@
from ._xml import get_text
from .mixin import IDManagerMixin
from .surface import _BOUNDARY_TYPES
from .utility_funcs import input_path


class MeshBase(IDManagerMixin, ABC):
Expand Down Expand Up @@ -2072,7 +2071,7 @@ class UnstructuredMesh(MeshBase):
Parameters
----------
filename : str or pathlib.Path
filename : path-like
Location of the unstructured mesh file
library : {'moab', 'libmesh'}
Mesh library used for the unstructured mesh tally
Expand Down Expand Up @@ -2158,8 +2157,8 @@ def filename(self):

@filename.setter
def filename(self, filename):
cv.check_type('Unstructured Mesh filename', filename, (str, Path))
self._filename = filename
cv.check_type('Unstructured Mesh filename', filename, PathLike)
self._filename = input_path(filename)

@property
def library(self):
Expand Down
36 changes: 22 additions & 14 deletions openmc/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@
import lxml.etree as ET

import openmc.checkvalue as cv
from openmc.checkvalue import PathLike
from openmc.stats.multivariate import MeshSpatial
from . import (RegularMesh, SourceBase, MeshSource, IndependentSource,
VolumeCalculation, WeightWindows, WeightWindowGenerator)
from ._xml import clean_indentation, get_text, reorder_attributes
from openmc.checkvalue import PathLike
from .mesh import _read_meshes
from .mesh import _read_meshes, RegularMesh
from .source import SourceBase, MeshSource, IndependentSource
from .utility_funcs import input_path
from .volume import VolumeCalculation
from .weight_windows import WeightWindows, WeightWindowGenerator


class RunMode(Enum):
Expand Down Expand Up @@ -699,14 +701,18 @@ def surf_source_read(self) -> dict:
return self._surf_source_read

@surf_source_read.setter
def surf_source_read(self, surf_source_read: dict):
cv.check_type('surface source reading options', surf_source_read, Mapping)
for key, value in surf_source_read.items():
def surf_source_read(self, ssr: dict):
cv.check_type('surface source reading options', ssr, Mapping)
for key, value in ssr.items():
cv.check_value('surface source reading key', key,
('path'))
if key == 'path':
cv.check_type('path to surface source file', value, str)
self._surf_source_read = surf_source_read
cv.check_type('path to surface source file', value, PathLike)
self._surf_source_read = dict(ssr)

# Resolve path to surface source file
if 'path' in ssr:
self._surf_source_read['path'] = input_path(ssr['path'])

@property
def surf_source_write(self) -> dict:
Expand Down Expand Up @@ -1066,8 +1072,8 @@ def weight_windows_file(self) -> PathLike | None:

@weight_windows_file.setter
def weight_windows_file(self, value: PathLike):
cv.check_type('weight windows file', value, (str, Path))
self._weight_windows_file = value
cv.check_type('weight windows file', value, PathLike)
self._weight_windows_file = input_path(value)

@property
def weight_window_generators(self) -> list[WeightWindowGenerator]:
Expand Down Expand Up @@ -1241,7 +1247,7 @@ def _create_surf_source_read_subelement(self, root):
element = ET.SubElement(root, "surf_source_read")
if 'path' in self._surf_source_read:
subelement = ET.SubElement(element, "path")
subelement.text = self._surf_source_read['path']
subelement.text = str(self._surf_source_read['path'])

def _create_surf_source_write_subelement(self, root):
if self._surf_source_write:
Expand Down Expand Up @@ -1501,7 +1507,7 @@ def _create_weight_window_generators_subelement(self, root, mesh_memo=None):
def _create_weight_windows_file_element(self, root):
if self.weight_windows_file is not None:
element = ET.Element("weight_windows_file")
element.text = self.weight_windows_file
element.text = str(self.weight_windows_file)
root.append(element)

def _create_weight_window_checkpoints_subelement(self, root):
Expand Down Expand Up @@ -1645,9 +1651,11 @@ def _sourcepoint_from_xml_element(self, root):
def _surf_source_read_from_xml_element(self, root):
elem = root.find('surf_source_read')
if elem is not None:
ssr = {}
value = get_text(elem, 'path')
if value is not None:
self.surf_source_read['path'] = value
ssr['path'] = value
self.surf_source_read = ssr

def _surf_source_write_from_xml_element(self, root):
elem = root.find('surf_source_write')
Expand Down
38 changes: 17 additions & 21 deletions openmc/source.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from collections.abc import Iterable, Sequence
from enum import IntEnum
from numbers import Real
from pathlib import Path
import warnings
from typing import Any
from pathlib import Path
Expand All @@ -19,6 +20,7 @@
from openmc.stats.univariate import Univariate
from ._xml import get_text
from .mesh import MeshBase, StructuredMesh, UnstructuredMesh
from .utility_funcs import input_path


class SourceBase(ABC):
Expand Down Expand Up @@ -664,7 +666,7 @@ class CompiledSource(SourceBase):
Parameters
----------
library : str or None
library : path-like
Path to a compiled shared library
parameters : str
Parameters to be provided to the compiled shared library function
Expand All @@ -686,7 +688,7 @@ class CompiledSource(SourceBase):
Attributes
----------
library : str or None
library : pathlib.Path
Path to a compiled shared library
parameters : str
Parameters to be provided to the compiled shared library function
Expand All @@ -702,17 +704,13 @@ class CompiledSource(SourceBase):
"""
def __init__(
self,
library: str | None = None,
library: PathLike,
parameters: str | None = None,
strength: float = 1.0,
constraints: dict[str, Any] | None = None
) -> None:
super().__init__(strength=strength, constraints=constraints)

self._library = None
if library is not None:
self.library = library

self.library = library
self._parameters = None
if parameters is not None:
self.parameters = parameters
Expand All @@ -722,13 +720,13 @@ def type(self) -> str:
return "compiled"

@property
def library(self) -> str:
def library(self) -> Path:
return self._library

@library.setter
def library(self, library_name):
cv.check_type('library', library_name, str)
self._library = library_name
def library(self, library_name: PathLike):
cv.check_type('library', library_name, PathLike)
self._library = input_path(library_name)

@property
def parameters(self) -> str:
Expand All @@ -748,7 +746,7 @@ def populate_xml_element(self, element):
XML element containing source data
"""
element.set("library", self.library)
element.set("library", str(self.library))

if self.parameters is not None:
element.set("parameters", self.parameters)
Expand Down Expand Up @@ -794,7 +792,7 @@ class FileSource(SourceBase):
Parameters
----------
path : str or pathlib.Path
path : path-like
Path to the source file from which sites should be sampled
strength : float
Strength of the source (default is 1.0)
Expand Down Expand Up @@ -829,14 +827,12 @@ class FileSource(SourceBase):

def __init__(
self,
path: PathLike | None = None,
path: PathLike,
strength: float = 1.0,
constraints: dict[str, Any] | None = None
):
super().__init__(strength=strength, constraints=constraints)
self._path = None
if path is not None:
self.path = path
self.path = path

@property
def type(self) -> str:
Expand All @@ -848,8 +844,8 @@ def path(self) -> PathLike:

@path.setter
def path(self, p: PathLike):
cv.check_type('source file', p, str)
self._path = p
cv.check_type('source file', p, PathLike)
self._path = input_path(p)

def populate_xml_element(self, element):
"""Add necessary file source information to an XML element
Expand All @@ -861,7 +857,7 @@ def populate_xml_element(self, element):
"""
if self.path is not None:
element.set("file", self.path)
element.set("file", str(self.path))

@classmethod
def from_xml_element(cls, elem: ET.Element) -> openmc.FileSource:
Expand Down
Loading

0 comments on commit 579777a

Please sign in to comment.