Repository: PMEAL/OpenPNM
Pull request #998 (2018-07-18 02:11:46)

Fix 990 adding deep
Adjusting props and regen_models to optionally return/regenerate all the properties on the associated objects


Patch:
diff --git a/openpnm/core/Base.py b/openpnm/core/Base.py
index 14d980f21..bc21aa8e3 100644
--- a/openpnm/core/Base.py
+++ b/openpnm/core/Base.py
@@ -468,6 +468,14 @@ class Base(dict):
         vals = set(vals).difference(temp)
         # Convert to nice list for printing
         vals = PrintableList(list(vals))
+        # Repeat for associated objects if deep is True
+        if deep:
+            if self._isa('phase'):
+                for item in self.project.find_physics(phase=self):
+                    vals += item.props(element=element, mode=mode, deep=False)
+            if self._isa('network'):
+                for item in self.project.geometries().values():
+                    vals += item.props(element=element, mode=mode, deep=False)
         return vals
 
     def _get_labels(self, element, locations, mode):
diff --git a/openpnm/core/ModelsMixin.py b/openpnm/core/ModelsMixin.py
index d339f6123..4539b6474 100644
--- a/openpnm/core/ModelsMixin.py
+++ b/openpnm/core/ModelsMixin.py
@@ -151,7 +151,7 @@ class ModelsMixin():
     >>> print(temp.num_pores())
     3
 
-    But also has those needed for working with models.  For instance, a sipmle
+    But also has those needed for working with models.  For instance, a simple
     model can be added as follows:
 
     >>> temp.add_model(propname='pore.test',
@@ -218,7 +218,7 @@ class ModelsMixin():
         if regen_mode not in ['deferred']:
             self._regen(propname)
 
-    def regenerate_models(self, propnames=None, exclude=[]):
+    def regenerate_models(self, propnames=None, exclude=[], deep=False):
         r"""
         Re-runs the specified model or models.
 
@@ -234,41 +234,57 @@ class ModelsMixin():
             to exclude specific models.  It may be more convenient to supply
             as list of 2 models to exclude than to specify 8 models to include.
 
+        deep : boolean
+            Specifies whether or not to regenerate models on all associated
+            objects.  For instance, if ``True``, then all Physics models will
+            be regenerated when method is called on the corresponding Phase.
+            The default is ``False``.  The method does not work in reverse,
+            so regenerating models on a Physics will not update a Phase.
+
         """
+        # If empty list of propnames was given, do nothing and return
+        if type(propnames) is list and len(propnames) == 0:
+            return
         if type(propnames) is str:  # Convert string to list if necessary
             propnames = [propnames]
-
         if propnames is None:  # If no props given, then regenerate them all
             propnames = self.models.dependency_list()
             # If some props are to be excluded, remove them from list
             if len(exclude) > 0:
                 propnames = [i for i in propnames if i not in exclude]
-
-        # Check if any propnames are not on self, deal with separately
-        self_models = self.models.dependency_list()
-        foreign_models = set(propnames).difference(set(self_models))
-        if len(foreign_models):
-            # If foreign model is found on another object, regenerate it
-            for item in foreign_models:
-                if self._isa('phase'):
-                    for phys in self.project.find_physics(phase=self):
-                        phys.regenerate_models(propnames)
-                if self._isa('network'):
-                    for geom in self.geometries().values():
-                        geom.regenerate_models(propnames)
-            # Remove any foreign models from given list, and proceed
-            propnames = set(propnames).difference(foreign_models)
-
         # Re-order given propnames according to dependency tree
+        self_models = self.models.dependency_list()
         propnames = [i for i in self_models if i in propnames]
 
-        # Scan through list of propnames and regenerate each one
-        for item in propnames:
-            self._regen(item)
+        if deep:
+            other_models = None  # Will trigger regen of ALL models
+        else:
+            # Make list of given propnames that are not in self
+            other_models = list(set(propnames).difference(set(self_models)))
+        # The following has some redundant lines, but is easier to understand
+        if self._isa('phase'):
+            # Start be regenerating models on self
+            for item in propnames:
+                self._regen(item)
+            # Then regen models on associated objects, if any in other_models
+            for phys in self.project.find_physics(phase=self):
+                phys.regenerate_models(propnames=other_models, deep=False)
+        elif self._isa('network'):  # Repeat for other object types
+            for item in propnames:
+                self._regen(item)
+            for geom in self.project.geometries().values():
+                geom.regenerate_models(propnames=other_models, deep=False)
+        else:
+            for item in propnames:
+                self._regen(item)
 
     def _regen(self, prop):
         # Create a temporary dict of all model arguments
-        kwargs = self.models[prop].copy()
+        try:
+            kwargs = self.models[prop].copy()
+        except KeyError:
+            logger.info(prop+' not found, will retry if deep is True')
+            return
         # Pop model and regen_mode from temporary dict
         model = kwargs.pop('model')
         regen_mode = kwargs.pop('regen_mode', None)
@@ -284,6 +300,7 @@ class ModelsMixin():
             try:
                 self[prop] = model(target=self, **kwargs)
             except KeyError:
+                # Find names of missing dependencies and print nice warning
                 missing_deps = []
                 for key in kwargs.values():
                     if type(key) == str and key.split('.')[0] in ['pore', 'throat']:


Test patch:
diff --git a/tests/unit/core/ModelsTest.py b/tests/unit/core/ModelsTest.py
index 077e39f1f..8005356b4 100644
--- a/tests/unit/core/ModelsTest.py
+++ b/tests/unit/core/ModelsTest.py
@@ -112,6 +112,47 @@ class ModelsTest:
                    "following properties are missing: ['pore.max_size', " +
                    "'pore.seed']"))
 
+    def test_regenerate_models_on_phase_with_deep(self):
+        pn = op.network.Cubic(shape=[5, 5, 5])
+        geo = op.geometry.StickAndBall(network=pn, pores=pn.Ps, throats=pn.Ts)
+        phase = op.phases.Water(network=pn)
+        phys = op.physics.Standard(network=pn, phase=phase, geometry=geo)
+        phase.clear(mode='model_data')
+        phys.clear()
+        assert len(phys) == 2  # Only pore and throat.all remain
+        phase.regenerate_models(propnames=None, deep=False)
+        assert len(phys) == 2  # Still only pore and throat.all
+        phase.regenerate_models(propnames=None, deep=True)
+        assert len(phys) > 2  # Phys models are regenerated by phase regen
+
+    def test_regenerate_models_on_physics_with_deep(self):
+        pn = op.network.Cubic(shape=[5, 5, 5])
+        geo = op.geometry.StickAndBall(network=pn, pores=pn.Ps, throats=pn.Ts)
+        phase = op.phases.Water(network=pn)
+        phys = op.physics.Standard(network=pn, phase=phase, geometry=geo)
+        phase.clear(mode='model_data')
+        phys.clear()
+        assert len(phase) == 12  # Only pore and throat.all remain
+        phys.regenerate_models(propnames=None, deep=False)
+        assert len(phase) == 12  # Still only pore and throat.all
+        assert len(phys) == 3  # Only one property was regenerated
+        phys.regenerate_models(propnames=None, deep=True)
+        assert len(phase) == 12  # Phase models are not regenerated
+
+    def test_regenerate_models_on_network_with_deep(self):
+        pn = op.network.Cubic(shape=[5, 5, 5])
+        geo = op.geometry.StickAndBall(network=pn, pores=pn.Ps, throats=pn.Ts)
+        a = len(pn.props())
+        pn.clear()
+        pn.regenerate_models()
+        assert len(pn.props()) == a  # pn has NO models
+        b = len(geo.props())
+        geo.clear(mode='model_data')
+        pn.regenerate_models(deep=False)
+        assert len(geo.props()) == 0
+        pn.regenerate_models(deep=True)
+        assert len(geo.props()) == b
+
 
 if __name__ == '__main__':