Repository: PMEAL/OpenPNM
Pull request #1326 (2019-09-12 14:11:16)

Ignore nans in neighbor lookups


Patch:
diff --git a/openpnm/models/misc/neighbor_lookups.py b/openpnm/models/misc/neighbor_lookups.py
index 59f3c08a0..59892761c 100644
--- a/openpnm/models/misc/neighbor_lookups.py
+++ b/openpnm/models/misc/neighbor_lookups.py
@@ -11,7 +11,7 @@ logger = logging.getLogger(__name__)
 
 
 def from_neighbor_throats(target, prop=None, throat_prop='pore.seed',
-                          mode='min'):
+                          mode='min', ignore_nans=True):
     r"""
     Adopt a value from the values found in neighboring throats
 
@@ -21,21 +21,20 @@ def from_neighbor_throats(target, prop=None, throat_prop='pore.seed',
         The object which this model is associated with. This controls the
         length of the calculated array, and also provides access to other
         necessary properties.
-
     prop : string
         The dictionary key of the array containing the throat property to be
         used in the calculation.  The default is 'throat.seed'.
-
     throat_prop : string
         Same as ``prop``, but will be deprecated.
-
     mode : string
         Controls how the pore property is calculated.  Options are 'min',
         'max' and 'mean'.
+    ignore_nans : boolean (default is ``True``)
+        If ``True`` the result will ignore ``nans`` in the neighbors
 
     Returns
     -------
-    value : NumPy ndarray
+    value : ND-array
         Array containing customized values based on those of adjacent throats.
 
     """
@@ -46,6 +45,8 @@ def from_neighbor_throats(target, prop=None, throat_prop='pore.seed',
     if prop is not None:
         throat_prop = prop
     data = lookup[throat_prop]
+    if ignore_nans:
+        data = np.ma.MaskedArray(data=data, mask=np.isnan(data))
     neighborTs = network.find_neighbor_throats(pores=Ps,
                                                flatten=False,
                                                mode='or')
@@ -59,10 +60,11 @@ def from_neighbor_throats(target, prop=None, throat_prop='pore.seed',
     if mode == 'mean':
         for pore in range(len(Ps)):
             values[pore] = np.mean(data[neighborTs[pore]])
-    return values
+    return np.array(values)
 
 
-def from_neighbor_pores(target, prop=None, pore_prop='pore.seed', mode='min'):
+def from_neighbor_pores(target, prop=None, pore_prop='pore.seed', mode='min',
+                        ignore_nans=True):
     r"""
     Adopt a value based on the values in neighboring pores
 
@@ -72,21 +74,20 @@ def from_neighbor_pores(target, prop=None, pore_prop='pore.seed', mode='min'):
         The object which this model is associated with. This controls the
         length of the calculated array, and also provides access to other
         necessary properties.
-
     prop : string
         The dictionary key to the array containing the pore property to be
         used in the calculation.  Default is 'pore.seed'.
-
     pore_prop : string
         Same as ``prop`` but will be deprecated.
-
     mode : string
         Controls how the throat property is calculated.  Options are 'min',
         'max' and 'mean'.
+    ignore_nans : boolean (default is ``True``)
+        If ``True`` the result will ignore ``nans`` in the neighbors
 
     Returns
     -------
-    value : NumPy ndarray
+    value : ND-array
         Array containing customized values based on those of adjacent pores.
 
     """
@@ -98,10 +99,12 @@ def from_neighbor_pores(target, prop=None, pore_prop='pore.seed', mode='min'):
     if prop is not None:
         pore_prop = prop
     pvalues = lookup[pore_prop][P12]
+    if ignore_nans:
+        pvalues = np.ma.MaskedArray(data=pvalues, mask=np.isnan(pvalues))
     if mode == 'min':
         value = np.amin(pvalues, axis=1)
     if mode == 'max':
         value = np.amax(pvalues, axis=1)
     if mode == 'mean':
         value = np.mean(pvalues, axis=1)
-    return value
+    return np.array(value)


Test patch:
diff --git a/tests/unit/core/ModelsTest.py b/tests/unit/core/ModelsTest.py
index ba2317edd..922e62fbb 100644
--- a/tests/unit/core/ModelsTest.py
+++ b/tests/unit/core/ModelsTest.py
@@ -22,7 +22,7 @@ class ModelsTest:
         geo = op.geometry.StickAndBall(network=net, pores=net.Ps,
                                        throats=net.Ts)
         s = geo.models.__str__().split('\n')
-        assert len(s) == 68
+        assert len(s) == 69
         assert s.count('―'*78) == 15
 
     def test_regenerate_models(self):
diff --git a/tests/unit/models/misc/MiscTest.py b/tests/unit/models/misc/MiscTest.py
index 1d17e15ab..79e33ba67 100644
--- a/tests/unit/models/misc/MiscTest.py
+++ b/tests/unit/models/misc/MiscTest.py
@@ -145,6 +145,54 @@ class MiscTest:
         assert sp.all(self.geo['pore.seed'] > tmin)
         assert sp.all(self.geo['pore.seed'] < tmax)
 
+    def test_neighbor_pores_with_nans(self):
+        net = op.network.Cubic(shape=[2, 2, 2])
+        net['pore.values'] = 1.0
+        net['pore.values'][0] = sp.nan
+        f = mods.from_neighbor_pores
+        with_nans = f(target=net, pore_prop='pore.values',
+                      ignore_nans=False, mode='min')
+        assert sp.any(sp.isnan(with_nans))
+        no_nans = f(target=net, pore_prop='pore.values',
+                    ignore_nans=True, mode='min')
+        assert sp.all(~sp.isnan(no_nans))
+        with_nans = f(target=net, pore_prop='pore.values',
+                      ignore_nans=False, mode='max')
+        assert sp.any(sp.isnan(with_nans))
+        no_nans = f(target=net, pore_prop='pore.values',
+                    ignore_nans=True, mode='max')
+        assert sp.all(~sp.isnan(no_nans))
+        with_nans = f(target=net, pore_prop='pore.values',
+                      ignore_nans=False, mode='mean')
+        assert sp.any(sp.isnan(with_nans))
+        no_nans = f(target=net, pore_prop='pore.values',
+                    ignore_nans=True, mode='mean')
+        assert sp.all(~sp.isnan(no_nans))
+
+    def test_neighbor_throats_with_nans(self):
+        net = op.network.Cubic(shape=[2, 2, 2])
+        net['throat.values'] = 1.0
+        net['throat.values'][0] = sp.nan
+        f = mods.from_neighbor_throats
+        with_nans = f(target=net, throat_prop='throat.values',
+                      ignore_nans=False, mode='min')
+        assert sp.any(sp.isnan(with_nans))
+        no_nans = f(target=net, throat_prop='throat.values',
+                    ignore_nans=True, mode='min')
+        assert sp.all(~sp.isnan(no_nans))
+        with_nans = f(target=net, throat_prop='throat.values',
+                      ignore_nans=False, mode='max')
+        assert sp.any(sp.isnan(with_nans))
+        no_nans = f(target=net, throat_prop='throat.values',
+                    ignore_nans=True, mode='max')
+        assert sp.all(~sp.isnan(no_nans))
+        with_nans = f(target=net, throat_prop='throat.values',
+                      ignore_nans=False, mode='mean')
+        assert sp.any(sp.isnan(with_nans))
+        no_nans = f(target=net, throat_prop='throat.values',
+                    ignore_nans=True, mode='mean')
+        assert sp.all(~sp.isnan(no_nans))
+
     def test_from_neighbor_pores_min(self):
         self.geo.remove_model('throat.seed')
         self.geo['pore.seed'] = sp.rand(self.net.Np,)