Repository: PMEAL/OpenPNM

Issue:
Algorithms probably don't need to inherit from the Base class
We did some digging and the algorithms rarely call any of the ``Base`` methods like ``num_pores`` etc.  I think we might be able to just define ``GenericAlgorithm`` as a direct subclass of ``dict`` and that would free up the algorithms quite a bit.  For instance, the data would no longer need to be ``Np`` and ``Nt`` long.  This would allow solving for subsets of pores.  

Discussion:

Patch:
diff --git a/openpnm/algorithms/GenericAlgorithm.py b/openpnm/algorithms/GenericAlgorithm.py
index c2551b1ab..70bc915b5 100644
--- a/openpnm/algorithms/GenericAlgorithm.py
+++ b/openpnm/algorithms/GenericAlgorithm.py
@@ -21,7 +21,7 @@ class GenericAlgorithmSettings:
 
 @docstr.get_sections(base='GenericAlgorithm', sections=['Parameters'])
 @docstr.dedent
-class GenericAlgorithm(Base, LegacyMixin, LabelMixin):
+class GenericAlgorithm(Base, LegacyMixin):
     r"""
     Generic class to define the foundation of Algorithms
 
diff --git a/openpnm/algorithms/ReactiveTransport.py b/openpnm/algorithms/ReactiveTransport.py
index 185578d11..f0b815c14 100644
--- a/openpnm/algorithms/ReactiveTransport.py
+++ b/openpnm/algorithms/ReactiveTransport.py
@@ -91,7 +91,7 @@ class ReactiveTransport(GenericTransport):
             return
         # Remove item from label dictionary
         for item in self.settings['sources']:
-            self.pop(item)
+            self.pop(item, None)
         # Reset the settings dict
         self.settings['sources'] = []
 
@@ -131,7 +131,12 @@ class ReactiveTransport(GenericTransport):
         locs_BC = np.isfinite(self['pore.bc_value']) + np.isfinite(self['pore.bc_rate'])
         if (locs & locs_BC).any():
             raise Exception("BCs present in given pores, can't assign source term")
-        self.set_label(propname, pores=locs, mode=mode)
+        if mode == 'overwrite':
+            self[propname] = False
+        if mode == 'add':
+            if propname not in self.keys():
+                self[propname] = False
+        self[propname][locs] = True
         # Check if propname already in source term list
         if propname not in self.settings['sources']:
             self.settings['sources'].append(propname)
@@ -148,9 +153,11 @@ class ReactiveTransport(GenericTransport):
             The pore indices where the source term should be applied.
 
         """
-        locs = self.tomask(pores=pores or self.Ps)
-        self.set_label(propname, pores=locs, mode='remove')
-        # TODO: if pores=None: remove the label -> reuse in reset method
+        propname = self._parse_prop(propname, 'pore')
+        if pores is None:
+            self.pop(propname, None)
+        else:
+            self[propname][pores] = False
 
     def _update_iterative_props(self):
         """r
@@ -195,7 +202,7 @@ class ReactiveTransport(GenericTransport):
         phase = self.project[self.settings.phase]
         for item in self.settings['sources']:
             # Fetch linearized values of the source term
-            Ps = self.pores(item)
+            Ps = self[item]
             S1, S2 = [phase[f"{item}.{Si}"] for Si in ["S1", "S2"]]
             # Modify A and b: diag(A) += -S1, b += S2
             diag = self.A.diagonal()
diff --git a/openpnm/core/Base.py b/openpnm/core/Base.py
index 5595e7e3a..3470016ba 100644
--- a/openpnm/core/Base.py
+++ b/openpnm/core/Base.py
@@ -51,7 +51,7 @@ class Base(dict):
     class is a subclass of the standard ``dict`` so has the usual methods such
     as ``pop`` and ``keys``, and has extra methods for working specifically
     with OpenPNM data.
-    
+
     """
 
     def __new__(cls, *args, **kwargs):
@@ -834,6 +834,47 @@ class Base(dict):
             T = self.interpolate_data(propname='pore.'+prop, mode=mode)
         return np.vstack((P1, T, P2)).T
 
+    def _count(self, element=None):
+        r"""
+        Returns a dictionary containing the number of pores and throats in
+        the network, stored under the keys 'pore' or 'throat'
+
+        Parameters
+        ----------
+        element : string, optional
+            Can be either 'pore' , 'pores', 'throat' or 'throats', which
+            specifies which count to return.
+
+        Returns
+        -------
+        A dictionary containing the number of pores and throats under the
+        'pore' and 'throat' key respectively.
+
+        See Also
+        --------
+        num_pores
+        num_throats
+
+        Notes
+        -----
+        The ability to send plurals is useful for some types of 'programmatic'
+        access.  For instance, the standard argument for locations is pores
+        or throats.  If these are bundled up in a **kwargs dict then you can
+        just use the dict key in count() without removing the 's'.
+
+        Examples
+        --------
+        >>> import openpnm as op
+        >>> pn = op.network.Cubic(shape=[5, 5, 5])
+        >>> pn._count('pore')
+        125
+        >>> pn._count('throat')
+        300
+        """
+        element = self._parse_element(element=element, single=True)
+        temp = np.size(self.__getitem__(element+'.all'))
+        return temp
+
     def show_hist(self,
                   props=['pore.diameter', 'throat.diameter', 'throat.length'],
                   bins=20, fontsize=14, **kwargs):
diff --git a/openpnm/core/Mixins.py b/openpnm/core/Mixins.py
index a81443fbe..b3961f6a9 100644
--- a/openpnm/core/Mixins.py
+++ b/openpnm/core/Mixins.py
@@ -730,44 +730,3 @@ class LabelMixin:
         Ts = self._get_indices(labels=labels, mode=mode, element='throat')
         Nt = np.shape(Ts)[0]
         return Nt
-
-    def _count(self, element=None):
-        r"""
-        Returns a dictionary containing the number of pores and throats in
-        the network, stored under the keys 'pore' or 'throat'
-
-        Parameters
-        ----------
-        element : string, optional
-            Can be either 'pore' , 'pores', 'throat' or 'throats', which
-            specifies which count to return.
-
-        Returns
-        -------
-        A dictionary containing the number of pores and throats under the
-        'pore' and 'throat' key respectively.
-
-        See Also
-        --------
-        num_pores
-        num_throats
-
-        Notes
-        -----
-        The ability to send plurals is useful for some types of 'programmatic'
-        access.  For instance, the standard argument for locations is pores
-        or throats.  If these are bundled up in a **kwargs dict then you can
-        just use the dict key in count() without removing the 's'.
-
-        Examples
-        --------
-        >>> import openpnm as op
-        >>> pn = op.network.Cubic(shape=[5, 5, 5])
-        >>> pn._count('pore')
-        125
-        >>> pn._count('throat')
-        300
-        """
-        element = self._parse_element(element=element, single=True)
-        temp = np.size(self.__getitem__(element+'.all'))
-        return temp


Test patch:
diff --git a/tests/unit/algorithms/IPTest.py b/tests/unit/algorithms/IPTest.py
index 4803639bd..efdf7e3f8 100644
--- a/tests/unit/algorithms/IPTest.py
+++ b/tests/unit/algorithms/IPTest.py
@@ -57,7 +57,7 @@ class IPTest:
         alg.set_inlets(pores=self.net.pores("top"))
         alg.run()
         alg.apply_trapping(outlets=self.net.pores("bottom"))
-        assert "pore.trapped" in alg.labels()
+        assert "pore.trapped" in alg.keys()
 
     def test_plot_intrusion_curve(self):
         alg = op.algorithms.InvasionPercolation(network=self.net, phase=self.water)
diff --git a/tests/unit/algorithms/ReactiveTransportTest.py b/tests/unit/algorithms/ReactiveTransportTest.py
index 41b10b2fc..4646908c7 100644
--- a/tests/unit/algorithms/ReactiveTransportTest.py
+++ b/tests/unit/algorithms/ReactiveTransportTest.py
@@ -146,7 +146,7 @@ class ReactiveTransportTest:
         self.alg.remove_source(propname='pore.reaction', pores=[0, 2])
         assert self.alg['pore.reaction'].sum() == 2
         self.alg.remove_source(propname='pore.reaction')
-        assert self.alg['pore.reaction'].sum() == 0
+        assert 'pore.reaction' not in self.alg.keys()
 
     def test_source_relaxation_consistency_w_base_solution(self):
         self.alg.reset(bcs=True, source_terms=True)