Repository: openmc-dev/openmc

Issue:
Allow LibMesh unstructured mesh to apply a scaling factor
For Cardinal, we often couple OpenMC to a MOOSE application in units of meters. It would be convenient if OpenMC's `LibMesh` unstructured mesh constructor could apply a constant scaling factor to a provided mesh file to internally convert it to the units of centimeters required by OpenMC. 

For unstructured mesh tallies in Cardinal, we copy from the bins in the mesh file to the mesh in the `[Mesh]` MOOSE block. For proper transfers to other MOOSE apps in SI units, the `[Mesh]` is typically in units of meters and provided with a file. Having a scaling factor capability in OpenMC would save us the extra step of making essentially the same mesh, once in units of meters (for the MOOSE side of things) and once in centimeters (for the OpenMC side of things).


Discussion:

Resolving patch:
diff --git a/openmc/mesh.py b/openmc/mesh.py
--- a/openmc/mesh.py
+++ b/openmc/mesh.py
@@ -615,8 +615,8 @@ class UnstructuredMesh(MeshBase):
         Unique identifier for the mesh
     name : str
         Name of the mesh
-    size : int
-        Number of elements in the unstructured mesh
+    length_multiplier: float
+        Constant multiplier to apply to mesh coordinates
 
     Attributes
     ----------
@@ -626,11 +626,16 @@ class UnstructuredMesh(MeshBase):
         Name of the mesh
     filename : str
         Name of the file containing the unstructured mesh
+    length_multiplier: float
+        Multiplicative factor to apply to mesh coordinates
     library : str
         Mesh library used for the unstructured mesh tally
     output : bool
         Indicates whether or not automatic tally output should
         be generated for this mesh
+    specified_length_multiplier: bool
+        Indicates whether a non-unity length multiplier has been
+        applied to this mesh
     volumes : Iterable of float
         Volumes of the unstructured mesh elements
     total_volume : float
@@ -639,13 +644,16 @@ class UnstructuredMesh(MeshBase):
         An iterable of element centroid coordinates, e.g. [(0.0, 0.0, 0.0),
         (1.0, 1.0, 1.0), ...]
     """
-    def __init__(self, filename, library, mesh_id=None, name=''):
+    def __init__(self, filename, library, mesh_id=None, name='',
+                        length_multiplier=1.0):
         super().__init__(mesh_id, name)
         self.filename = filename
         self._volumes = None
         self._centroids = None
         self.library = library
         self._output = True
+        self._specified_length_multiplier = False
+        self.length_multiplier = length_multiplier
 
     @property
     def filename(self):
@@ -713,6 +721,20 @@ def centroids(self, centroids):
                       Iterable, Real)
         self._centroids = centroids
 
+    @property
+    def length_multiplier(self):
+        return self._length_multiplier
+
+    @length_multiplier.setter
+    def length_multiplier(self, length_multiplier):
+        cv.check_type("Unstructured mesh length multiplier", 
+                                length_multiplier,
+                                Real)
+        self._length_multiplier = length_multiplier
+
+        if (self._length_multiplier != 1.0):
+          self._specified_length_multiplier = True
+
     def __repr__(self):
         string = super().__repr__()
         string += '{: <16}=\t{}\n'.format('\tFilename', self.filename)
@@ -763,7 +785,7 @@ def write_data_to_vtk(self, filename, datasets, volume_normalization=True):
 
         for centroid in self.centroids:
             # create a point for each centroid
-            point_id = points.InsertNextPoint(centroid)
+            point_id = points.InsertNextPoint(centroid * self.length_multiplier)
             # create a cell of type "Vertex" for each point
             cell_id = vertices.InsertNextCell(cell_dim, (point_id,))
 
@@ -817,6 +839,9 @@ def from_hdf5(cls, group):
         mesh.centroids = np.reshape(centroids, (vol_data.shape[0], 3))
         mesh.size = mesh.volumes.size
 
+        if 'length_multiplier' in group:
+            mesh.length_multiplier = group['length_multiplier'][()]
+
         return mesh
 
     def to_xml_element(self):
@@ -836,6 +861,9 @@ def to_xml_element(self):
         subelement = ET.SubElement(element, "filename")
         subelement.text = self.filename
 
+        if (self._specified_length_multiplier):
+          element.set("length_multiplier", str(self.length_multiplier))
+
         return element
 
     @classmethod
@@ -855,5 +883,6 @@ def from_xml_element(cls, elem):
         mesh_id = int(get_text(elem, 'id'))
         filename = get_text(elem, 'filename')
         library = get_text(elem, 'library')
+        length_multiplier = float(get_text(elem, 'length_multiplier', 1.0))
 
-        return cls(filename, library, mesh_id)
+        return cls(filename, library, mesh_id, '', length_multiplier)


Test patch: