From f790d1c1ac6545bb463d1629dc5dd1420fd5653a Mon Sep 17 00:00:00 2001
From: Anna Mareike Kostelecky <anmako96@web.de>
Date: Thu, 1 Jun 2023 15:35:37 +0200
Subject: [PATCH] [PNM][util] Update python scripts and requirements to recent
 porespy/openpnm versions

---
 .../util/extract_pore_network_with_porespy.py |  23 ++--
 dumux/porenetwork/util/openpnm2dgf.py         | 125 +++++++++---------
 dumux/porenetwork/util/requirements.txt       |   6 +-
 3 files changed, 80 insertions(+), 74 deletions(-)

diff --git a/dumux/porenetwork/util/extract_pore_network_with_porespy.py b/dumux/porenetwork/util/extract_pore_network_with_porespy.py
index 97d8d1e3ac..872f3c1d83 100755
--- a/dumux/porenetwork/util/extract_pore_network_with_porespy.py
+++ b/dumux/porenetwork/util/extract_pore_network_with_porespy.py
@@ -70,7 +70,11 @@ def openPngFile(fileName):
 
 
 def openFile(fileName, numVoxels):
-    """Opens the image with fileName detecting the file type from extension"""
+    """Opens the image with fileName and number of voxels in each direction
+    detecting the file type from extension.
+
+    Returns the image as a numpy array.
+    """
 
     if fileName.endswith(".raw"):
         return openRawFile(fileName, numVoxels)
@@ -93,7 +97,9 @@ def extrude2D(image, layers, topAndBottomValues):
         raise IOError("Extrusion only works for 2D images")
     if not len(topAndBottomValues) == 2:
         raise IOError("topAndBottomValues can only have two entries")
-    image = np.tile(np.atleast_3d(image), reps=[1, 1, layers])
+    image = np.tile(
+        np.atleast_3d(image), reps=[1, 1, layers]
+    )  # reshape in 3D array and repeat layers times
     image[..., 0:1] = topAndBottomValues[0]
     image[..., -1:] = topAndBottomValues[1]
     print("Extruding image to 3D.")
@@ -194,22 +200,23 @@ def extractNetwork(
     writeSegmentationInfoFile(snowOutput, outputName + "_segmentation", resolution)
 
     # use PoreSpy to sanitize some parameters (might become obsolete as some point)
-    porenetwork, _ = op.io.PoreSpy.import_data(snowOutput.network)
+    porenetwork = op.io.network_from_porespy(snowOutput.network)  # porenetwork in OpenPNM format
 
-    # trimming pore network to avoid singularity
+    # trimming pore network to avoid singularity (remove isolated and disconnected pores)
     print("Number of pores before trimming: ", porenetwork.Np)
-    health = porenetwork.check_network_health()
-    op.topotools.trim(network=porenetwork, pores=health["trim_pores"])
+    health = op.utils.check_network_health(porenetwork)
+    op.topotools.trim(network=porenetwork, pores=health["disconnected_pores"])
+    op.topotools.trim(network=porenetwork, pores=health["isolated_pores"])
     print("Number of pores after trimming: ", porenetwork.Np)
 
     filename = outputName + ("_dual" if dualSnow else "")
 
     # export to vtk
-    op.io.VTK.save(network=porenetwork, filename=filename)
+    op.io.project_to_vtk(project=porenetwork.project, filename=filename)
     print("Writing", filename + ".vtp")
 
     # export network for OpenPNM
-    porenetwork.project.save_project(filename)
+    op.utils.Workspace().save_project(project=porenetwork.project, filename=filename)
     print("Writing", filename + ".pnm")
 
 
diff --git a/dumux/porenetwork/util/openpnm2dgf.py b/dumux/porenetwork/util/openpnm2dgf.py
index 809596cd38..e7dabeabfc 100755
--- a/dumux/porenetwork/util/openpnm2dgf.py
+++ b/dumux/porenetwork/util/openpnm2dgf.py
@@ -21,7 +21,7 @@ def load(filename):
     workspace.clear()
     project = workspace.load_project(filename=filename)
     print(project)
-    return project["net_01"], project["geo_01"]
+    return project["net"]
 
 
 def removeBoundaryThroats(net):
@@ -69,11 +69,11 @@ def getDualGridCoordinationNumber(net):
     coordNumVoid = np.zeros(numPoresTotal, dtype=np.int64)
     coordNumSolid = np.zeros(numPoresTotal, dtype=np.int64)
 
-    for voidThroat in net["throat.conns"][net["throat.void"]]:
+    for voidThroat in net["throat.conns"][net["throat.void_void"]]:
         coordNumVoid[voidThroat[0]] += 1
         coordNumVoid[voidThroat[1]] += 1
 
-    for solidThroat in net["throat.conns"][net["throat.solid"]]:
+    for solidThroat in net["throat.conns"][net["throat.solid_solid"]]:
         coordNumSolid[solidThroat[0]] += 1
         coordNumSolid[solidThroat[1]] += 1
 
@@ -84,27 +84,25 @@ def sanitizeDualNetwork(net, splitSinglePores):
     """DOC ME!"""
     # pylint: disable=too-many-locals,too-many-branches,too-many-statements
 
-    geo = net.project["geo_01"]
-
-    print("Num. void throats before dual grid sanitation", np.sum(net["throat.void"]))
+    print("Num. void throats before dual grid sanitation", np.sum(net["throat.void_void"]))
     print(net)
 
     # calculate coordination number for void-void and solid-solid connections
     coordNumVoid, coordNumSolid = getDualGridCoordinationNumber(net)
 
-    poreKeys = [key for key in geo if key.startswith("pore.")]
-    throatKeys = [key for key in geo if key.startswith("throat.")]
+    poreKeys = [key for key in net if key.startswith("pore.")]
+    throatKeys = [key for key in net if key.startswith("throat.")]
 
-    # HACK We want to add geometry information for the new fake pores
-    # and throats but it is not clear how to change the entries in geo.
-    # We therefore replace the geo object
+    # HACK We want to add information for the new fake pores
+    # and throats but it is not clear how to change the entries in net.
+    # We therefore replace the net object
     # by a dictionary which behaves similarly.
     # This should be improved!
     if splitSinglePores:
-        newGeo = {}
-        for key in geo:
-            newGeo[key] = geo[key]
-        geo = newGeo
+        newNet = {}
+        for key in net:
+            newNet[key] = net[key]
+        net = newNet
 
     singlePores = []
 
@@ -149,80 +147,80 @@ def sanitizeDualNetwork(net, splitSinglePores):
             net["pore." + domain][net["pore.fake_" + domain]] = True
             net["throat." + domain][net["throat.fake_" + domain]] = True
 
-            # add some articial geometry data
+            # add some articial data
             for throat in net.throats("fake_" + domain):
                 vIdx = net["throat.conns"][throat][0]
                 for key in poreKeys:
                     if "diameter" in key:
-                        geo[key] = np.append(
-                            geo[key], geo[key][vIdx]
+                        net[key] = np.append(
+                            net[key], net[key][vIdx]
                         )  # use same diameter for both pores
                         # TODO is this the right approach? # pylint: disable=fixme
                     if "volume" in key:
-                        geo[key] = np.append(
-                            geo[key], 0.5 * geo[key][vIdx]
+                        net[key] = np.append(
+                            net[key], 0.5 * net[key][vIdx]
                         )  # divide volume on both pores
-                        geo[key][vIdx] *= 0.5
+                        net[key][vIdx] *= 0.5
 
-                poreVolume = geo["pore.region_volume"][vIdx]
+                poreVolume = net["pore.region_volume"][vIdx]
                 eqRadius = (poreVolume * 3.0 / 4.0 * 1.0 / np.pi) ** (1.0 / 3.0)  # assume sphere
                 eqArea = np.pi * eqRadius**2
 
-                poreCenter0 = geo["pore.centroid"][vIdx]
+                poreCenter0 = net["pore.centroid"][vIdx]
                 poreCenter1 = poreCenter0 + np.array([1e-10, 1e-10, 1e-10])
                 throatCenter = 0.5 * (poreCenter0 + poreCenter1)
 
                 for key in throatKeys:
                     if "diameter" in key:
-                        geo[key] = np.append(geo[key], 2 * eqRadius)  # use equivalent diameter
+                        net[key] = np.append(net[key], 2 * eqRadius)  # use equivalent diameter
                     if "area" in key:
-                        geo[key] = np.append(geo[key], eqArea)  # use equivalent area
+                        net[key] = np.append(net[key], eqArea)  # use equivalent area
                     if "length" in key:
-                        geo[key] = np.append(geo[key], eqRadius)  # use equivalent radius
+                        net[key] = np.append(net[key], eqRadius)  # use equivalent radius
                     if "perimeter" in key:
-                        geo[key] = np.append(geo[key], eqRadius)  # use equivalent radius
+                        net[key] = np.append(net[key], eqRadius)  # use equivalent radius
                     if "centroid" in key:
-                        geo[key] = np.append(geo[key], throatCenter.reshape(-1, 3), axis=0)
+                        net[key] = np.append(net[key], throatCenter.reshape(-1, 3), axis=0)
 
             print(
                 "Num. void throats after dual grid sanitation",
-                np.sum(net["throat.void"]),
+                np.sum(net["throat.void_void"]),
             )
 
     if not splitSinglePores:
-        op.io.VTK.save(network=net, filename="before_delete")
+        op.io.project_to_vtk(project=net.project, filename="before_delete")
         singlePores = np.array(singlePores)
         throatsToDelete = []
 
         # for eIdx, throat in enumerate(net.throats('interconnect')):
         for eIdx, throat in enumerate(net["throat.conns"]):
-            if net["throat.interconnect"][eIdx] == 1:
+            if net["throat.solid_void"][eIdx] == 1 or net["throat.void_solid"][eIdx] == 1:
                 vIdx0 = throat[0]
                 vIdx1 = throat[1]
                 if vIdx0 in singlePores or vIdx1 in singlePores:
                     throatsToDelete.append(eIdx)
 
         trim(network=net, throats=throatsToDelete, pores=singlePores)
-        op.io.VTK.save(network=net, filename="after_delete")
+        op.io.project_to_vtk(project=net.project, filename="after_delete")
 
-    return net, geo
+    return net
 
 
-def _computeVertexElementData(net, geo, policy):
+def _computeVertexElementData(net, policy):
     """Compute element and vertex data for DGF output"""
     # pylint: disable=too-many-locals
 
     vertices = net["pore.coords"]
     elements = net["throat.conns"]
 
-    poreRadius = policy["pore.radius"](net, geo)
-    poreVolume = policy["pore.volume"](net, geo)
-    poreLabel = policy["pore.label"](net, geo)
-    throatRadius = policy["throat.radius"](net, geo)
-    throatLength = policy["throat.length"](net, geo)
-    throatArea = policy["throat.area"](net, geo)
-    throatShapeFactor = policy["throat.throatShapeFactor"](net, geo)
-    throatLabel = policy["throat.label"](net, geo)
+    poreRadius = policy["pore.radius"](net)
+    poreVolume = policy["pore.volume"](net)
+    poreLabel = policy["pore.label"](net)
+    throatRadius = policy["throat.radius"](net)
+    throatLength = policy["throat.length"](net)
+    throatArea = policy["throat.area"](net)
+    throatShapeFactor = policy["throat.throatShapeFactor"](net)
+    throatLabel = policy["throat.label"](net)
     throatCenter = net["throat.global_peak"]
 
     # create dgf data
@@ -231,8 +229,9 @@ def _computeVertexElementData(net, geo, policy):
         # 1D array (one entry for each throat)
         # containing 0 for void, 1 for solid and 2 for interconnect
         throatDomainType = np.zeros(len(elements), dtype=np.int64)
-        throatDomainType[net["throat.solid"]] = 1
-        throatDomainType[net["throat.interconnect"]] = 2
+        throatDomainType[net["throat.solid_solid"]] = 1
+        throatDomainType[net["throat.void_solid"]] = 2
+        throatDomainType[net["throat.solid_void"]] = 2
 
         # 1D array (one entry for each pore)
         # containing 0 for void, 1 for solid and 2
@@ -297,10 +296,10 @@ def _computeVertexElementData(net, geo, policy):
     return vertexData, elementData
 
 
-def writeDGF(filename, net, geo, policy):
+def writeDGF(filename, net, policy):
     """DOC ME!"""
 
-    vertexData, elementData = _computeVertexElementData(net, geo, policy)
+    vertexData, elementData = _computeVertexElementData(net, policy)
     isDualNetwork = "pore.solid" in net.keys()
 
     # write DGF file
@@ -349,8 +348,8 @@ if __name__ == "__main__":
     )
     args = vars(parser.parse_args())
 
-    # net contains topology and some labels, geometry contains pore sizes etc
-    network, geometry = load(args["file"])
+    # net contains topology and some labels, pore sizes etc
+    network = load(args["file"])
 
     # maybe remove boundary throats
     if args["deleteBoundaryThroats"]:
@@ -359,15 +358,15 @@ if __name__ == "__main__":
 
     # check if we have a dual network
     if "pore.solid" in network.keys():
-        network, geometry = sanitizeDualNetwork(network, args["splitSinglePores"])
+        network = sanitizeDualNetwork(network, args["splitSinglePores"])
 
     # begin policy //////////////////////////////////////////
     # TODO: How can we conveniently load / exchange this? # pylint: disable=fixme
-    def throatLengthFunc(net, geo):
+    def throatLengthFunc(net):
         """DOC ME!"""
         elements = net["throat.conns"]
-        length = copy.deepcopy(geo["throat.direct_length"])
-        poreRadius = geo["pore.extended_diameter"] / 2.0
+        length = copy.deepcopy(net["throat.direct_length"])
+        poreRadius = net["pore.extended_diameter"] / 2.0
 
         for eIdx, element in enumerate(elements):
             vIdx0, vIdx1 = element[0], element[1]
@@ -375,13 +374,13 @@ if __name__ == "__main__":
 
         return np.clip(length, 1e-9, np.max(length))
 
-    def throatShapeFactorFunc(net, geo):  # pylint: disable=unused-argument
+    def throatShapeFactorFunc(net):  # pylint: disable=unused-argument
         """DOC ME!"""
-        transmissibilityG = geo["throat.cross_sectional_area"] / geo["throat.perimeter"] ** 2
+        transmissibilityG = net["throat.cross_sectional_area"] / net["throat.perimeter"] ** 2
         maxG = 1.0 / 16.0  # for circle
         return np.clip(transmissibilityG, 0.0, maxG)
 
-    def poreLabelFunc(net, geo):  # pylint: disable=unused-argument
+    def poreLabelFunc(net):  # pylint: disable=unused-argument
         """DOC ME!"""
         label = np.full(len(net["pore.coords"]), -1, dtype=int)
         label[net["pore.xmin"]] = 1
@@ -400,11 +399,11 @@ if __name__ == "__main__":
 
         return label
 
-    def throatLabelFunc(net, geo, policy=None):
+    def throatLabelFunc(net, policy=None):
         """DOC ME!"""
         # TODO add policy # pylint: disable=fixme
         _ = policy
-        pLabels = poreLabelFunc(net, geo)
+        pLabels = poreLabelFunc(net)
         label = np.full(len(net["throat.conns"]), -1, dtype=int)
 
         for eIdx, element in enumerate(net["throat.conns"]):
@@ -419,12 +418,12 @@ if __name__ == "__main__":
         return label
 
     POLICY = {
-        "pore.radius": lambda n, g: g["pore.extended_diameter"] / 2.0,
-        "pore.volume": lambda n, g: g["pore.region_volume"],
+        "pore.radius": lambda n: n["pore.extended_diameter"] / 2.0,
+        "pore.volume": lambda n: n["pore.region_volume"],
         "pore.label": poreLabelFunc,
-        "throat.radius": lambda n, g: g["throat.inscribed_diameter"] / 2.0,
+        "throat.radius": lambda n: n["throat.inscribed_diameter"] / 2.0,
         "throat.length": throatLengthFunc,
-        "throat.area": lambda n, g: g["throat.cross_sectional_area"],
+        "throat.area": lambda n: n["throat.cross_sectional_area"],
         "throat.throatShapeFactor": throatShapeFactorFunc,
         "throat.label": throatLabelFunc,
     }
@@ -436,7 +435,7 @@ if __name__ == "__main__":
         outputFilename = os.path.splitext(args["outputname"])[0] + ".dgf"
 
     print("Writing", outputFilename)
-    writeDGF(outputFilename, network, geometry, POLICY)
+    writeDGF(outputFilename, network, POLICY)
     # export to vtk
-    op.io.VTK.save(network=network, filename=outputFilename)
+    op.io.project_to_vtk(project=network.project, filename=outputFilename)
     print("Writing", outputFilename + ".vtp")
diff --git a/dumux/porenetwork/util/requirements.txt b/dumux/porenetwork/util/requirements.txt
index d0612b1f38..65ed36f02c 100644
--- a/dumux/porenetwork/util/requirements.txt
+++ b/dumux/porenetwork/util/requirements.txt
@@ -1,6 +1,6 @@
 # SPDX-FileCopyrightInfo: Copyright © DuMux Project contributors, see AUTHORS.md in root folder
 # SPDX-License-Identifier: CC0-1.0
 
-porespy~=2.1.0
-openpnm~=2.8.2
-loguru~=0.6.0
+porespy~=2.2.2
+openpnm~=3.1.2
+loguru~=0.7.0
-- 
GitLab