Repository: PMEAL/OpenPNM
Pull request #979 (2018-07-11 06:59:52)

Changing the API for the mapping functions
The mapping functions used to take IDs as arguments, now they take pore numbers and the associated object.  It hides ids from the users.


Patch:
diff --git a/example_script.py b/example_script.py
index b29879f2e..beb1ae060 100644
--- a/example_script.py
+++ b/example_script.py
@@ -41,4 +41,5 @@ rxn.set_value_BC(pores=pn.pores('top'), values=1)
 rxn.run()
 air.update(rxn.results())
 
-proj.export_data(network=pn, phases=[hg, air, water], filename='output.vtp')
+# Output network and phases to a VTP file for visualization in Paraview
+# proj.export_data(network=pn, phases=[hg, air, water], filename='output.vtp')
diff --git a/openpnm/core/Base.py b/openpnm/core/Base.py
index 305c54f16..254aba32d 100644
--- a/openpnm/core/Base.py
+++ b/openpnm/core/Base.py
@@ -757,37 +757,62 @@ class Base(dict):
             t = namedtuple('index_map', ('indices', 'mask'))
             return t(ind, mask)
 
-    def map_pores(self, ids, filtered=True):
+    def map_pores(self, pores, origin, filtered=True):
         r"""
-        Translates pore ids to indices on the calling object
+        Given a list of pore on a target object, finds indices of
+        those pores on the calling object
 
         Parameters
         ----------
-        ids : array_like
-            The ids of the pores whose indices are sought
+        pores : array_like
+            The indices of the pores on the target object
+
+        origin : OpenPNM Base object
+            The object corresponding to the indices given in ``pores``
 
         filtered : boolean (default is ``True``)
             If ``True`` then a ND-array of indices is returned with missing
             indices removed, otherwise a named-tuple containing both the
             ``indices`` and a boolean ``mask`` with ``False`` indicating
-            which ``ids`` were not found.
+            which locations were not found.
+
+        Returns
+        -------
+        Pore indices on the calling object corresponding to the same pores
+        on the target object.  Can be an array or a tuple containing an array
+        and a mask, depending on the value of ``filtered``.
 
         """
+        ids = origin['pore._id'][pores]
         return self._map(element='pore', ids=ids, filtered=filtered)
 
-    def map_throats(self, ids, filtered=True):
+    def map_throats(self, throats, origin, filtered=True):
         r"""
-        Translates throat ids to indices on the calling object
+        Given a list of throats on a target object, finds indices of
+        those throats on the calling object
 
         Parameters
         ----------
-        ids : array_like
-             The ids of the throats whose indices are sought
+        throats : array_like
+            The indices of the throats on the target object
+
+        origin : OpenPNM Base object
+            The object corresponding to the indices given in ``throats``
 
         filtered : boolean (default is ``True``)
-            If ``True`` then a ND-array of indices is returned, otherwise
-            a named-tuple containing the ``indices`` and the ???
+            If ``True`` then a ND-array of indices is returned with missing
+            indices removed, otherwise a named-tuple containing both the
+            ``indices`` and a boolean ``mask`` with ``False`` indicating
+            which locations were not found.
+
+        Returns
+        -------
+        Throat indices on the calling object corresponding to the same throats
+        on the target object.  Can be an array or a tuple containing an array
+        and a mask, depending on the value of ``filtered``.
+
         """
+        ids = origin['throat._id'][throats]
         return self._map(element='throat', ids=ids, filtered=filtered)
 
     def _tomask(self, indices, element):
diff --git a/openpnm/models/geometry/throat_equivalent_area.py b/openpnm/models/geometry/throat_equivalent_area.py
index 008b64323..ea473a960 100644
--- a/openpnm/models/geometry/throat_equivalent_area.py
+++ b/openpnm/models/geometry/throat_equivalent_area.py
@@ -2,7 +2,8 @@ from numpy import pi as _pi
 import numpy as _np
 
 
-def spherical_pores(target, throat_area='throat.area', pore_diameter='pore.diameter',
+def spherical_pores(target, throat_area='throat.area',
+                    pore_diameter='pore.diameter',
                     throat_conduit_lengths='throat.conduit_lengths'):
     r"""
     Calculate equivalent cross-sectional area for a conduit consisting of two
@@ -24,7 +25,7 @@ def spherical_pores(target, throat_area='throat.area', pore_diameter='pore.diame
 
     """
     network = target.project.network
-    throats = network.map_throats(target['throat._id'])
+    throats = network.map_throats(throats=target.Ts, origin=target)
     cn = network['throat.conns'][throats]
     d1 = network[pore_diameter][cn[:, 0]]
     d2 = network[pore_diameter][cn[:, 1]]
@@ -35,11 +36,13 @@ def spherical_pores(target, throat_area='throat.area', pore_diameter='pore.diame
             'pore2': d2*L2*_pi / (2*_np.arctanh(2*L2/d2))}
 
 
-def truncated_pyramid(target, throat_area='throat.area', pore_diameter='pore.diameter'):
+def truncated_pyramid(target, throat_area='throat.area',
+                      pore_diameter='pore.diameter'):
     r"""
     Calculate equivalent cross-sectional area for a conduit consisting of two
-    truncated pyramid pores and a constant cross-section throat. This area can be later
-    used to calculate hydraulic or diffusive conductance of the conduit.
+    truncated pyramid pores and a constant cross-section throat. This area can
+    be later used to calculate hydraulic or diffusive conductance of the
+    conduit.
 
     Parameters
     ----------
@@ -56,7 +59,7 @@ def truncated_pyramid(target, throat_area='throat.area', pore_diameter='pore.dia
 
     """
     network = target.project.network
-    throats = network.map_throats(target['throat._id'])
+    throats = network.map_throats(throats=target.Ts, origin=target)
     cn = network['throat.conns'][throats]
     d1 = network[pore_diameter][cn[:, 0]]
     d2 = network[pore_diameter][cn[:, 1]]
@@ -66,7 +69,8 @@ def truncated_pyramid(target, throat_area='throat.area', pore_diameter='pore.dia
             'pore2': d2*td}
 
 
-def circular_pores(target, throat_area='throat.area', pore_diameter='pore.diameter',
+def circular_pores(target, throat_area='throat.area',
+                   pore_diameter='pore.diameter',
                    throat_conduit_lengths='throat.conduit_lengths'):
     r"""
     Calculate equivalent cross-sectional area for a conduit consisting of two
@@ -88,7 +92,7 @@ def circular_pores(target, throat_area='throat.area', pore_diameter='pore.diamet
 
     """
     network = target.project.network
-    throats = network.map_throats(target['throat._id'])
+    throats = network.map_throats(throats=target.Ts, origin=target)
     cn = network['throat.conns'][throats]
     d1 = network[pore_diameter][cn[:, 0]]
     d2 = network[pore_diameter][cn[:, 1]]
diff --git a/openpnm/models/geometry/throat_length.py b/openpnm/models/geometry/throat_length.py
index 36846ef57..d1695a155 100644
--- a/openpnm/models/geometry/throat_length.py
+++ b/openpnm/models/geometry/throat_length.py
@@ -20,7 +20,7 @@ def ctc(target, pore_diameter='pore.diameter'):
 
     """
     network = target.project.network
-    throats = network.map_throats(target['throat._id'])
+    throats = network.map_throats(throats=target.Ts, origin=target)
     cn = network['throat.conns'][throats]
     C1 = network['pore.coords'][cn[:, 0]]
     C2 = network['pore.coords'][cn[:, 1]]
@@ -48,7 +48,7 @@ def straight(target, pore_diameter='pore.diameter', L_negative=1e-12):
         ``None``.
     """
     network = target.project.network
-    throats = network.map_throats(target['throat._id'])
+    throats = network.map_throats(throats=target.Ts, origin=target)
     cn = network['throat.conns'][throats]
     E = ctc(target, pore_diameter=pore_diameter)
     D1 = network[pore_diameter][cn[:, 0]]
@@ -83,7 +83,7 @@ def spherical_pores(target, pore_diameter='pore.diameter',
 
     """
     network = target.project.network
-    throats = network.map_throats(target['throat._id'])
+    throats = network.map_throats(throats=target.Ts, origin=target)
     cn = network['throat.conns'][throats]
     d1 = network[pore_diameter][cn[:, 0]]
     d2 = network[pore_diameter][cn[:, 1]]
@@ -106,7 +106,7 @@ def spherical_pores(target, pore_diameter='pore.diameter',
     L2[mask_overlap] = L2temp[mask_overlap]
     Lt[mask_overlap] = L_negative
     # Removing negative lengths
-    if _sp.any([L1<0, L2<0, Lt<0]) and L_negative is not None:
+    if _sp.any([L1 < 0, L2 < 0, Lt < 0]) and L_negative is not None:
         _logger.warn('Negative pore/throat lengths are calculated. Arbitrary' +
                      ' positive length assigned: ' + str(L_negative))
         L1[L1 < 0] = L_negative
@@ -140,7 +140,7 @@ def truncated_pyramid(target, pore_diameter='pore.diameter',
 
 
 def circular_pores(target, pore_diameter='pore.diameter',
-                    throat_diameter='throat.diameter'):
+                   throat_diameter='throat.diameter'):
     r"""
     Calculate conduit lengths, i.e. pore 1 length, throat length,
     and pore 2 length, assuming that pores are circles.
diff --git a/openpnm/models/physics/hydraulic_conductance.py b/openpnm/models/physics/hydraulic_conductance.py
index adf6da582..e2a8dfb62 100644
--- a/openpnm/models/physics/hydraulic_conductance.py
+++ b/openpnm/models/physics/hydraulic_conductance.py
@@ -42,7 +42,7 @@ def hagen_poiseuille(target,
 
     """
     network = target.project.network
-    throats = network.map_throats(target['throat._id'])
+    throats = network.map_throats(throats=target.Ts, origin=target)
     phase = target.project.find_phase(target)
     geom = target.project.find_geometry(target)
     cn = network['throat.conns'][throats]
diff --git a/openpnm/models/physics/misc.py b/openpnm/models/physics/misc.py
index 1c994a81f..4bf8eaf0d 100644
--- a/openpnm/models/physics/misc.py
+++ b/openpnm/models/physics/misc.py
@@ -37,7 +37,7 @@ def poisson_conductance(target, pore_diffusivity, throat_diffusivity,
 
     """
     network = target.project.network
-    throats = network.map_throats(target['throat._id'])
+    throats = network.map_throats(throats=target.Ts, origin=target)
     phase = target.project.find_phase(target)
     geom = target.project.find_geometry(target)
     cn = network['throat.conns'][throats]
diff --git a/openpnm/models/physics/multiphase.py b/openpnm/models/physics/multiphase.py
index b354b7307..813b8e35c 100644
--- a/openpnm/models/physics/multiphase.py
+++ b/openpnm/models/physics/multiphase.py
@@ -62,7 +62,7 @@ def conduit_conductance(target, throat_conductance,
     value = phase[throat_conductance].copy()
     value[mask] = value[mask]*factor
     # Now map throats onto target object
-    Ts = phase.map_throats(ids=target['throat._id'])
+    Ts = network.map_throats(throats=target.Ts, origin=target)
     return value[Ts]
 
 
diff --git a/openpnm/topotools/topotools.py b/openpnm/topotools/topotools.py
index 060437d01..bab53041a 100644
--- a/openpnm/topotools/topotools.py
+++ b/openpnm/topotools/topotools.py
@@ -669,8 +669,6 @@ def trim(network, pores=[], throats=[]):
     Pkeep_inds = sp.where(Pkeep)[0]
     Tkeep_inds = sp.where(Tkeep)[0]
     Pmap = sp.ones((network.Np,), dtype=int)*-1
-    Pid = network['pore._id']
-    Tid = network['throat._id']
     tpore1 = network['throat.conns'][:, 0]
     tpore2 = network['throat.conns'][:, 1]
 
@@ -680,8 +678,8 @@ def trim(network, pores=[], throats=[]):
             Ps = Pkeep_inds
             Ts = Tkeep_inds
         else:
-            Ps = obj.map_pores(ids=Pid[Pkeep])
-            Ts = obj.map_throats(ids=Tid[Tkeep])
+            Ps = obj.map_pores(pores=Pkeep, origin=network)
+            Ts = obj.map_throats(throats=Tkeep, origin=network)
         for key in list(obj.keys()):
             temp = obj.pop(key)
             if key.split('.')[0] == 'throat':
diff --git a/openpnm/utils/Project.py b/openpnm/utils/Project.py
index 9db39f877..c64126996 100644
--- a/openpnm/utils/Project.py
+++ b/openpnm/utils/Project.py
@@ -341,9 +341,9 @@ class Project(list):
                 physics = self.find_physics(phase=phase)
                 temp = None
                 for phys in physics:
-                    Ps = phase.map_pores(phys['pore._id'])
+                    Ps = phase.map_pores(pores=phys.Ps, origin=phys)
                     physPs = phase.tomask(pores=Ps)
-                    Ts = phase.map_throats(phys['throat._id'])
+                    Ts = phase.map_throats(throats=phys.Ts, origin=phys)
                     physTs = phase.tomask(throats=Ts)
                     if np.all(geoPs == physPs) and np.all(geoTs == physTs):
                         temp = phys
diff --git a/openpnm/utils/vertexops.py b/openpnm/utils/vertexops.py
index 9917a9d5e..9235984c8 100644
--- a/openpnm/utils/vertexops.py
+++ b/openpnm/utils/vertexops.py
@@ -282,9 +282,8 @@ def pore2centroid(network):
     for geom_name in network.geometries():
         geometry = network.geometries(geom_name)[0]
         if 'pore.centroid' in geometry.props():
-            net_pores, geom_pores = geometry.map_pores(network,
-                                                       geometry.pores(),
-                                                       True).values()
+            net_pores = network.pores(geometry.name)
+            geom_pores = network.map_pors(poress=geometry.Ts, target=geometry)
             for i in range(len(geom_pores)):
                 network['pore.coords'][net_pores[i]] = \
                     geometry['pore.centroid'][geom_pores[i]]
@@ -408,11 +407,11 @@ def plot_pore(geometry, pores, fig=None, axis_bounds=None,
     from mpl_toolkits.mplot3d.art3d import Poly3DCollection
     if len(pores) > 0:
         net = geometry.network
-        net_pores = net.map_pores(geometry['pore._id'][pores])
+        net_pores = net.map_pores(pores=pores, origin=geometry)
         centroids = geometry['pore.centroid'][pores]
         coords = net['pore.coords'][net_pores]
         net_throats = net.find_neighbor_throats(pores=net_pores)
-        throats = geometry.map_throats(net['throat._id'][net_throats])
+        throats = geometry.map_throats(throats=net_throats, origin=net)
         tcentroids = geometry["throat.centroid"][throats]
         # Can't create volume from one throat
         if 1 <= len(throats):


Test patch:
diff --git a/tests/unit/core/BaseTest.py b/tests/unit/core/BaseTest.py
index 6bfbd0d8e..f4d75066b 100644
--- a/tests/unit/core/BaseTest.py
+++ b/tests/unit/core/BaseTest.py
@@ -572,26 +572,47 @@ class BaseTest:
         assert self.net['pore.test_dict.test1'].shape == (self.net.Np, )
         assert 'pore.test_dict.test2' in self.net.keys()
 
-    def test_pore_mapping(self):
+    def test_map_pores(self):
         a = self.geo21['pore._id']
         b = self.geo22['pore._id']
         assert a.size == self.geo21.Np
         assert b.size == self.geo22.Np
         assert ~sp.any(sp.in1d(a, b))
-        assert sp.all(self.net2.map_pores(a) == self.net2.pores(self.geo21.name))
-        assert sp.all(self.net2.map_pores(b) == self.net2.pores(self.geo22.name))
+        Pgeo21 = self.net2.map_pores(pores=self.geo21.Ps, origin=self.geo21)
+        assert sp.all(Pgeo21 == self.net2.pores(self.geo21.name))
+        Pgeo22 = self.net2.map_pores(pores=self.geo22.Ps, origin=self.geo22)
+        assert sp.all(Pgeo22 == self.net2.pores(self.geo22.name))
 
-    def test_throat_mapping(self):
+    def test_map_throats(self):
         a = self.geo21['throat._id']
         assert a.size == self.geo21.Nt
-        assert sp.all(self.net2.map_throats(a) == self.net2.throats(self.geo21.name))
+        Tgeo21 = self.net2.map_throats(throats=self.geo21.Ts, origin=self.geo21)
+        assert sp.all(Tgeo21 == self.net2.throats(self.geo21.name))
 
     def test_map_pores_unfiltered(self):
-        a = self.geo['pore._id']
-        b = self.net.map_pores(a, filtered=False)
+        b = self.net.map_pores(pores=self.geo.Ps, origin=self.geo, filtered=False)
         assert sp.all(b.indices == self.net.pores(self.geo.name))
         assert b.mask.size == self.geo.Np
 
+    def test_map_pores_unfiltered_missing(self):
+        Ps = self.net2.Ps[15:20]
+        b = self.geo22.map_pores(pores=Ps, origin=self.net2, filtered=False)
+        assert sum(b.mask) == 2
+        assert len(b.mask) == 5
+
+    def test_map_pores_reverse(self):
+        Ps = self.net2.Ps[:5]
+        b = self.geo21.map_pores(pores=Ps, origin=self.net2)
+        assert sp.all(b == [0, 1, 2, 3, 4])
+        Ps = self.net2.Ps[-5:]
+        b = self.geo22.map_pores(pores=Ps, origin=self.net2)
+        assert sp.all(b == [4, 5, 6, 7, 8])
+
+    def test_map_pores_missing(self):
+        Ps = self.net2.Ps[:5]
+        b = self.geo22.map_pores(pores=Ps, origin=self.net2)
+        assert len(b) == 0
+
     def test_interleave_data_bool(self):
         net = op.network.Cubic(shape=[2, 2, 2])
         Ps = net.pores('top')