Repository: PMEAL/OpenPNM

Issue:
Remove target argument from pores/throats
This should be replaced with the ``global=True/False`` keyword

Discussion:

Patch:
diff --git a/openpnm/core/Base.py b/openpnm/core/Base.py
index a486260b7..d1e2840ad 100644
--- a/openpnm/core/Base.py
+++ b/openpnm/core/Base.py
@@ -268,7 +268,10 @@ class Base(dict):
                 item['throat.' + name] = item.pop('throat.' + old_name)
 
     def _get_name(self):
-        return self.settings['name']
+        try:
+            return self.settings['name']
+        except AttributeError:
+            return None
 
     name = property(_get_name, _set_name)
 
@@ -668,58 +671,6 @@ class Base(dict):
         indices = self._parse_indices(mask)
         return indices
 
-    def to_global(self, pores=None, throats=None):
-        r"""
-        Convert local indices from a subdomain object to global values
-
-        Parameters
-        ----------
-        pores, throats : array_like
-            List of pore or throat indices to be converted
-
-        Returns
-        -------
-        indices : ndarray
-            An array of location indices
-        """
-        if pores is not None:
-            element = 'pore'
-            locs = pores
-        elif throats is not None:
-            element = 'throat'
-            locs = throats
-        mask = self.network[element + '.' + self.name]
-        inds = np.where(mask)[0]
-        return inds[locs]
-
-    def to_local(self, pores=None, throats=None, missing_vals=-1):
-        r"""
-        Convert global indices to local values relative to a subdomain object
-
-        Parameters
-        ----------
-        pores, throats : array_like
-            List of pore or throat indices to be converted
-        missing_values : scalar
-            The value to put into missing locations if global indices are not
-            found.
-
-        Returns
-        -------
-        indices : ndarray
-            An array of location indices
-        """
-        if pores is not None:
-            element = 'pore'
-            locs = pores
-        if throats is not None:
-            element = 'throat'
-            locs = throats
-        mask = np.ones_like(self.network[element + '.all'], dtype=int)*missing_vals
-        inds = np.where(self.network[element + '.' + self.name])[0]
-        mask[inds] = self.Ps
-        return mask[locs]
-
     def interleave_data(self, prop):
         r"""
         Retrieves requested property from associated objects, to produce a full
@@ -1231,737 +1182,3 @@ class Base(dict):
         if obj_type.lower() in mro:
             flag = True
         return flag
-
-
-class LegacyMixin:
-
-    def tomask(self, *args, **kwargs):
-        return self.to_mask(*args, **kwargs)
-
-    def toindices(self, *args, **kwargs):
-        return self.to_indices(*args, **kwargs)
-
-    def _map(self, ids, element, filtered):
-        ids = np.array(ids, dtype=np.int64)
-        locations = self._get_indices(element=element)
-        self_in_ids = np.isin(ids, self[element+'._id'], assume_unique=True)
-        ids_in_self = np.isin(self[element+'._id'], ids, assume_unique=True)
-        mask = np.zeros(shape=ids.shape, dtype=bool)
-        mask[self_in_ids] = True
-        ind = np.ones_like(mask, dtype=np.int64) * -1
-        ind[self_in_ids] = locations[ids_in_self]
-        if filtered:
-            return ind[mask]
-        t = namedtuple('index_map', ('indices', 'mask'))
-        return t(ind, mask)
-
-    def map_pores(self, pores, origin, filtered=True):
-        r"""
-        Given a list of pore on a target object, finds indices of those pores
-        on the calling object
-
-        Parameters
-        ----------
-        pores : array_like
-            The indices of the pores on the object specifiedin ``origin``
-
-        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 locations were not found.
-
-        Returns
-        -------
-        Pore indices on the calling object corresponding to the same pores
-        on the ``origin`` object.  Can be an array or a tuple containing an
-        array and a mask, depending on the value of ``filtered``.
-
-        See Also
-        --------
-        pores
-        map_throats
-
-        """
-        ids = origin['pore._id'][pores]
-        return self._map(element='pore', ids=ids, filtered=filtered)
-
-    def map_throats(self, throats, origin, filtered=True):
-        r"""
-        Given a list of throats on a target object, finds indices of
-        those throats on the calling object
-
-        Parameters
-        ----------
-        throats : array_like
-            The indices of the throats on the object specified in ``origin``
-
-        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 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 ``origin`` object.  Can be an array or a tuple containing an
-        array and a mask, depending on the value of ``filtered``.
-
-        See Also
-        --------
-        throats
-        map_pores
-
-        """
-        ids = origin['throat._id'][throats]
-        return self._map(element='throat', ids=ids, filtered=filtered)
-
-    def check_data_health(self):
-        r"""
-        Check the health of pore and throat data arrays.
-
-        Returns
-        -------
-        health: HealthDict object
-            A  basic dictionary with an added ``health`` attribute that is
-            ``True`` if all entries in the dict are deemed healthy
-            (empty lists), or ``False`` otherwise.
-
-        Examples
-        --------
-        >>> import openpnm
-        >>> pn = openpnm.network.Cubic(shape=[5, 5, 5])
-        >>> h = pn.check_data_health()
-        >>> h.health
-        True
-        """
-        health = self.project.check_data_health(obj=self)
-        return health
-
-
-class LabelMixin:
-    def _get_labels(self, element, locations, mode):
-        r"""
-        This is the actual label getter method, but it should not be called
-        directly.  Use ``labels`` instead.
-        """
-        # Parse inputs
-        locations = self._parse_indices(locations)
-        element = self._parse_element(element=element)
-        # Collect list of all pore OR throat labels
-        labels = self.keys(mode='labels', element=element)
-        labels.sort()
-        labels = np.array(labels)  # Convert to ND-array for following checks
-        # Make an 2D array with locations in rows and labels in cols
-        arr = np.vstack([self[item][locations] for item in labels]).T
-        num_hits = np.sum(arr, axis=0)  # Number of locations with each label
-        if mode in ['or', 'union', 'any']:
-            temp = labels[num_hits > 0]
-        elif mode in ['and', 'intersection']:
-            temp = labels[num_hits == locations.size]
-        elif mode in ['xor', 'exclusive_or']:
-            temp = labels[num_hits == 1]
-        elif mode in ['nor', 'not', 'none']:
-            temp = labels[num_hits == 0]
-        elif mode in ['nand']:
-            temp = labels[num_hits == (locations.size - 1)]
-        elif mode in ['xnor', 'nxor']:
-            temp = labels[num_hits > 1]
-        else:
-            raise Exception('Unrecognized mode:'+str(mode))
-        return PrintableList(temp)
-
-    def labels(self, pores=[], throats=[], element=None, mode='union'):
-        r"""
-        Returns a list of labels present on the object
-
-        Additionally, this function can return labels applied to a specified
-        set of pores or throats
-
-        Parameters
-        ----------
-        element : string
-            Controls whether pore or throat labels are returned.  If empty then
-            both are returned (default).
-
-        pores (or throats) : array_like
-            The pores (or throats) whose labels are sought.  If left empty a
-            list containing all pore and throat labels is returned.
-
-        mode : string, optional
-            Controls how the query should be performed.  Only applicable
-            when ``pores`` or ``throats`` are specified:
-
-            **'or', 'union', 'any'**: (default) Returns the labels that are
-            assigned to *any* of the given locations.
-
-            **'and', 'intersection', 'all'**: Labels that are present on *all*
-            the given locations.
-
-            **'xor', 'exclusive_or'** : Labels that are present on *only one*
-            of the given locations.
-
-            **'nor', 'none', 'not'**: Labels that are *not* present on any of
-            the given locations.
-
-            **'nand'**: Labels that are present on *all but one* of the given
-            locations
-
-            **'xnor'**: Labels that are present on *more than one* of the given
-            locations.  'nxor' is also accepted.
-
-        Returns
-        -------
-        A list containing the labels on the object.  If ``pores`` or
-        ``throats`` are given, the results are filtered according to the
-        specified ``mode``.
-
-        See Also
-        --------
-        props
-        keys
-
-        Notes
-        -----
-        Technically, *'nand'* and *'xnor'* should also return pores with *none*
-        of the labels but these are not included.  This makes the returned list
-        more useful.
-
-        Examples
-        --------
-        >>> import openpnm as op
-        >>> pn = op.network.Cubic(shape=[5, 5, 5])
-        >>> pn.labels(pores=[11, 12])
-        ['pore.all', 'pore.internal', 'pore.left', 'pore.surface']
-        """
-        # Short-circuit query when no pores or throats are given
-        if (np.size(pores) == 0) and (np.size(throats) == 0):
-            labels = PrintableList(self.keys(element=element, mode='labels'))
-        elif (np.size(pores) > 0) and (np.size(throats) > 0):
-            raise Exception('Cannot perform label query on pores and '
-                            + 'throats simultaneously')
-        elif np.size(pores) > 0:
-            labels = self._get_labels(element='pore', locations=pores,
-                                      mode=mode)
-        elif np.size(throats) > 0:
-            labels = self._get_labels(element='throat', locations=throats,
-                                      mode=mode)
-        return labels
-
-    def set_label(self, label, pores=None, throats=None, mode='add'):
-        r"""
-        Creates or updates a label array
-
-        Parameters
-        ----------
-        label : string
-                The label to apply to the specified locations
-        pores : array_like
-            A list of pore indices or a boolean mask of where given label
-            should be added or removed (see ``mode``)
-        throats : array_like
-            A list of throat indices or a boolean mask of where given label
-            should be added or removed (see ``mode``)
-        mode : string
-            Controls how the labels are handled.  Options are:
-
-            * 'add' (default)
-                Adds the given label to the specified locations while
-                keeping existing labels
-            * 'overwrite'
-                Removes existing label from all locations before
-                adding the label in the specified locations
-            * 'remove'
-                Removes the given label from the specified locations
-                leaving the remainder intact
-            * 'purge'
-                Removes the specified label from the object completely
-            * 'clear'
-                Sets all the labels to ``False`` but does not remove the label
-                array
-
-        """
-        self._parse_mode(mode=mode,
-                         allowed=['add', 'overwrite', 'remove', 'purge',
-                                  'clear'])
-
-        if label.split('.')[0] in ['pore', 'throat']:
-            label = label.split('.', 1)[1]
-
-        if (pores is not None) and (throats is not None):
-            self.set_label(label=label, pores=pores, mode=mode)
-            self.set_label(label=label, throats=throats, mode=mode)
-            return
-        elif pores is not None:
-            locs = self._parse_indices(pores)
-            element = 'pore'
-        elif throats is not None:
-            locs = self._parse_indices(throats)
-            element = 'throat'
-
-        if mode == 'add':
-            if element + '.' + label not in self.keys():
-                self[element + '.' + label] = False
-            self[element + '.' + label][locs] = True
-        if mode == 'overwrite':
-            self[element + '.' + label] = False
-            self[element + '.' + label][locs] = True
-        if mode== 'remove':
-            self[element + '.' + label][locs] = False
-        if mode == 'clear':
-            self['pore' + '.' + label] = False
-            self['throat' + '.' + label] = False
-        if mode == 'purge':
-            _ = self.pop('pore.' + label, None)
-            _ = self.pop('throat.' + label, None)
-
-
-    def _get_indices(self, element, labels='all', mode='or'):
-        r"""
-        This is the actual method for getting indices, but should not be called
-        directly.  Use ``pores`` or ``throats`` instead.
-        """
-        # Parse and validate all input values.
-        element = self._parse_element(element, single=True)
-        labels = self._parse_labels(labels=labels, element=element)
-        if element+'.all' not in self.keys():
-            raise Exception('Cannot proceed without {}.all'.format(element))
-
-        # Begin computing label array
-        if mode in ['or', 'any', 'union']:
-            union = np.zeros_like(self[element+'.all'], dtype=bool)
-            for item in labels:  # Iterate over labels and collect all indices
-                union = union + self[element+'.'+item.split('.')[-1]]
-            ind = union
-        elif mode in ['and', 'all', 'intersection']:
-            intersect = np.ones_like(self[element+'.all'], dtype=bool)
-            for item in labels:  # Iterate over labels and collect all indices
-                intersect = intersect*self[element+'.'+item.split('.')[-1]]
-            ind = intersect
-        elif mode in ['xor', 'exclusive_or']:
-            xor = np.zeros_like(self[element+'.all'], dtype=int)
-            for item in labels:  # Iterate over labels and collect all indices
-                info = self[element+'.'+item.split('.')[-1]]
-                xor = xor + np.int8(info)
-            ind = (xor == 1)
-        elif mode in ['nor', 'not', 'none']:
-            nor = np.zeros_like(self[element+'.all'], dtype=int)
-            for item in labels:  # Iterate over labels and collect all indices
-                info = self[element+'.'+item.split('.')[-1]]
-                nor = nor + np.int8(info)
-            ind = (nor == 0)
-        elif mode in ['nand']:
-            nand = np.zeros_like(self[element+'.all'], dtype=int)
-            for item in labels:  # Iterate over labels and collect all indices
-                info = self[element+'.'+item.split('.')[-1]]
-                nand = nand + np.int8(info)
-            ind = (nand < len(labels)) * (nand > 0)
-        elif mode in ['xnor', 'nxor']:
-            xnor = np.zeros_like(self[element+'.all'], dtype=int)
-            for item in labels:  # Iterate over labels and collect all indices
-                info = self[element+'.'+item.split('.')[-1]]
-                xnor = xnor + np.int8(info)
-            ind = (xnor > 1)
-        else:
-            raise Exception('Unsupported mode: '+mode)
-        # Extract indices from boolean mask
-        ind = np.where(ind)[0]
-        ind = ind.astype(dtype=int)
-        return ind
-
-    def pores(self, labels='all', mode='or', asmask=False, target=None):
-        r"""
-        Returns pore indicies where given labels exist, according to the logic
-        specified by the ``mode`` argument.
-
-        Parameters
-        ----------
-        labels : string or list of strings
-            The label(s) whose pores locations are requested.  This argument
-            also accepts '*' for wildcard searches.
-
-        mode : string
-            Specifies how the query should be performed.  The options are:
-
-            **'or', 'union', 'any'** : (default) Pores with *one or more* of
-            the given labels are returned.
-
-            **'and', 'intersection', 'all'** : Pores with *all* of the given
-            labels are returned.
-
-            **'xor', 'exclusive_or'** : Pores with *only one* of the given
-            labels are returned.
-
-            **'nor', 'none', 'not'** : Pores with *none* of the given labels
-            are returned.
-
-            **'nand'** : Pores with *not all* of the given labels are
-            returned.
-
-            **'xnor'** : Pores with *more than one* of the given labels are
-            returned.
-
-        asmask : boolean
-            If ``True`` then a boolean array of length Np is returned with
-            ``True`` values indicating the pores that satisfy the query.
-
-        target : OpenPNM Base object
-            If given, the returned indices will be indexed relative to the
-            ``target`` object.  This can be used to determine how indices on
-            one object map onto another object.
-
-        Returns
-        -------
-        A Numpy array containing pore indices filtered by the logic specified
-        in ``mode``.
-
-        See Also
-        --------
-        throats
-        map_pores
-
-        Notes
-        -----
-        Technically, *nand* and *xnor* should also return pores with *none* of
-        the labels but these are not included.  This makes the returned list
-        more useful.
-
-        To perform more complex or compound queries, you can opt to receive
-        the result a a boolean mask (``asmask=True``), then manipulate the
-        arrays manually.
-
-        Examples
-        --------
-        >>> import openpnm as op
-        >>> pn = op.network.Cubic(shape=[5, 5, 5])
-        >>> Ps = pn.pores(labels=['top', 'back'], mode='union')
-        >>> Ps[:5]  # Look at first 5 pore indices
-        array([ 4,  9, 14, 19, 20])
-        >>> pn.pores(labels=['top', 'back'], mode='xnor')
-        array([ 24,  49,  74,  99, 124])
-        """
-        ind = self._get_indices(element='pore', labels=labels, mode=mode)
-        if target is not None:
-            ind = target.map_pores(pores=ind, origin=self, filtered=True)
-        if asmask:
-            if target is not None:
-                ind = target.tomask(pores=ind)
-            else:
-                ind = self.tomask(pores=ind)
-        return ind
-
-    def throats(self, labels='all', mode='or', asmask=False, target=None):
-        r"""
-        Returns throat locations where given labels exist, according to the
-        logic specified by the ``mode`` argument.
-
-        Parameters
-        ----------
-        labels : string or list of strings
-            The throat label(s) whose locations are requested.  If omitted,
-            'all' throat inidices are returned.  This argument also accepts
-            '*' for wildcard searches.
-
-        mode : string
-            Specifies how the query should be performed.  The options are:
-
-            **'or', 'union', 'any'** : (default) Throats with *one or more* of
-            the given labels are returned.
-
-            **'and', 'intersection', 'all'** : Throats with *all* of the given
-            labels are returned.
-
-            **'xor', 'exclusive_or'** : Throats with *only one* of the given
-            labels are returned.
-
-            **'nor', 'none', 'not'** : Throats with *none* of the given labels
-            are returned.
-
-            **'nand'** : Throats with *not all* of the given labels are
-            returned.
-
-            **'xnor'** : Throats with *more than one* of the given labels are
-            returned.
-
-        asmask : boolean
-            If ``True`` then a boolean array of length Nt is returned with
-            ``True`` values indicating the throats that satisfy the query.
-
-        target : OpenPNM Base object
-            If given, the returned indices will be indexed relative to the
-            ``target`` object.  This can be used to determine how indices on
-            one object map onto another object.
-
-        Returns
-        -------
-        A Numpy array containing throat indices filtered by the logic specified
-        in ``mode``.
-
-        See Also
-        --------
-        pores
-        map_throats
-
-        Examples
-        --------
-        >>> import openpnm as op
-        >>> pn = op.network.Cubic(shape=[3, 3, 3])
-        >>> Ts = pn.throats()
-        >>> Ts[0:5]  # Look at first 5 throat indices
-        array([0, 1, 2, 3, 4])
-
-        """
-        ind = self._get_indices(element='throat', labels=labels, mode=mode)
-        if target is not None:
-            ind = target.map_throats(throats=ind, origin=self, filtered=True)
-        if asmask:
-            if target is not None:
-                ind = target.tomask(throats=ind)
-            else:
-                ind = self.tomask(throats=ind)
-        return ind
-
-    def filter_by_label(self, pores=[], throats=[], labels=None, mode='or'):
-        r"""
-        Returns which of the supplied pores (or throats) has the specified
-        label(s)
-
-        Parameters
-        ----------
-        pores, or throats : array_like
-            List of pores or throats to be filtered
-
-        labels : list of strings
-            The labels to apply as a filter
-
-        mode : string
-
-            Controls how the filter is applied.  Options include:
-
-            **'or', 'union', 'any'**: (default) Returns a list of the given
-            locations where *any* of the given labels exist.
-
-            **'and', 'intersection', 'all'**: Only locations where *all* the
-            given labels are found.
-
-            **'xor', 'exclusive_or'**: Only locations where exactly *one* of
-            the given labels are found.
-
-            **'nor', 'none', 'not'**: Only locations where *none* of the given
-            labels are found.
-
-            **'nand'** : Only locations with *some but not all* of the given
-            labels are returned.
-
-            **'xnor'** : Only locations with *more than one* of the given
-            labels are returned.
-
-        Returns
-        -------
-        A list of pores (or throats) that have been filtered according the
-        given criteria.  The returned list is a subset of the received list of
-        pores (or throats).
-
-        See Also
-        --------
-        pores
-        throats
-
-        Examples
-        --------
-        >>> import openpnm as op
-        >>> pn = op.network.Cubic(shape=[5, 5, 5])
-        >>> pn.filter_by_label(pores=[0, 1, 25, 32], labels='left')
-        array([0, 1])
-        >>> Ps = pn.pores(['top', 'bottom', 'back'], mode='or')
-        >>> pn.filter_by_label(pores=Ps, labels=['top', 'back'],
-        ...                    mode='and')
-        array([ 24,  49,  74,  99, 124])
-        """
-        # Convert inputs to locations and element
-        if (np.size(throats) > 0) and (np.size(pores) > 0):
-            raise Exception('Can only filter either pores OR labels')
-        if np.size(pores) > 0:
-            element = 'pore'
-            locations = self._parse_indices(pores)
-        elif np.size(throats) > 0:
-            element = 'throat'
-            locations = self._parse_indices(throats)
-        else:
-            return(np.array([], dtype=int))
-        labels = self._parse_labels(labels=labels, element=element)
-        labels = [element+'.'+item.split('.')[-1] for item in labels]
-        all_locs = self._get_indices(element=element, labels=labels, mode=mode)
-        mask = self._tomask(indices=all_locs, element=element)
-        ind = mask[locations]
-        return locations[ind]
-
-    def num_pores(self, labels='all', mode='or'):
-        r"""
-        Returns the number of pores of the specified labels
-
-        Parameters
-        ----------
-        labels : list of strings, optional
-            The pore labels that should be included in the count.
-            If not supplied, all pores are counted.
-
-        labels : list of strings
-            Label of pores to be returned
-
-        mode : string, optional
-            Specifies how the count should be performed.  The options are:
-
-            **'or', 'union', 'any'** : (default) Pores with *one or more* of
-            the given labels are counted.
-
-            **'and', 'intersection', 'all'** : Pores with *all* of the given
-            labels are counted.
-
-            **'xor', 'exclusive_or'** : Pores with *only one* of the given
-            labels are counted.
-
-            **'nor', 'none', 'not'** : Pores with *none* of the given labels
-            are counted.
-
-            **'nand'** : Pores with *some but not all* of the given labels are
-            counted.
-
-            **'xnor'** : Pores with *more than one* of the given labels are
-            counted.
-
-        Returns
-        -------
-        Np : int
-            Number of pores with the specified labels
-
-        See Also
-        --------
-        num_throats
-        count
-
-        Notes
-        -----
-        Technically, *'nand'* and *'xnor'* should also count pores with *none*
-        of the labels, however, to make the count more useful these are not
-        included.
-
-        Examples
-        --------
-        >>> import openpnm as op
-        >>> pn = op.network.Cubic(shape=[5, 5, 5])
-        >>> pn.num_pores()
-        125
-        >>> pn.num_pores(labels=['top'])
-        25
-        >>> pn.num_pores(labels=['top', 'front'], mode='or')
-        45
-        >>> pn.num_pores(labels=['top', 'front'], mode='xnor')
-        5
-
-        """
-        # Count number of pores of specified type
-        Ps = self._get_indices(labels=labels, mode=mode, element='pore')
-        Np = np.shape(Ps)[0]
-        return Np
-
-    def num_throats(self, labels='all', mode='union'):
-        r"""
-        Return the number of throats of the specified labels
-
-        Parameters
-        ----------
-        labels : list of strings, optional
-            The throat labels that should be included in the count.
-            If not supplied, all throats are counted.
-
-        mode : string, optional
-            Specifies how the count should be performed.  The options are:
-
-            **'or', 'union', 'any'** : (default) Throats with *one or more* of
-            the given labels are counted.
-
-            **'and', 'intersection', 'all'** : Throats with *all* of the given
-            labels are counted.
-
-            **'xor', 'exclusive_or'** : Throats with *only one* of the given
-            labels are counted.
-
-            **'nor', 'none', 'not'** : Throats with *none* of the given labels
-            are counted.
-
-            **'nand'** : Throats with *some but not all* of the given labels
-            are counted.
-
-            **'xnor'** : Throats with *more than one* of the given labels are
-            counted.
-
-        Returns
-        -------
-        Nt : int
-            Number of throats with the specified labels
-
-        See Also
-        --------
-        num_pores
-        count
-
-        Notes
-        -----
-        Technically, *'nand'* and *'xnor'* should also count throats with
-        *none* of the labels, however, to make the count more useful these are
-        not included.
-
-        """
-        # Count number of pores of specified type
-        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(super(Base, self).__getitem__(element+'.all'))
-        return temp
diff --git a/openpnm/core/Mixins.py b/openpnm/core/Mixins.py
index e5ad5bd15..a81443fbe 100644
--- a/openpnm/core/Mixins.py
+++ b/openpnm/core/Mixins.py
@@ -1,4 +1,6 @@
-from openpnm.utils import PrintableDict
+from collections import namedtuple
+import numpy as np
+from openpnm.utils import PrintableDict, PrintableList
 
 
 class ParamMixin:
@@ -36,3 +38,736 @@ class ParamMixin:
         Return parameter names and values in a dictionary
         """
         return self._params
+
+
+class LegacyMixin:
+
+    def tomask(self, *args, **kwargs):
+        return self.to_mask(*args, **kwargs)
+
+    def toindices(self, *args, **kwargs):
+        return self.to_indices(*args, **kwargs)
+
+    def _map(self, ids, element, filtered):
+        ids = np.array(ids, dtype=np.int64)
+        locations = self._get_indices(element=element)
+        self_in_ids = np.isin(ids, self[element+'._id'], assume_unique=True)
+        ids_in_self = np.isin(self[element+'._id'], ids, assume_unique=True)
+        mask = np.zeros(shape=ids.shape, dtype=bool)
+        mask[self_in_ids] = True
+        ind = np.ones_like(mask, dtype=np.int64) * -1
+        ind[self_in_ids] = locations[ids_in_self]
+        if filtered:
+            return ind[mask]
+        t = namedtuple('index_map', ('indices', 'mask'))
+        return t(ind, mask)
+
+    def map_pores(self, pores, origin, filtered=True):
+        r"""
+        Given a list of pore on a target object, finds indices of those pores
+        on the calling object
+
+        Parameters
+        ----------
+        pores : array_like
+            The indices of the pores on the object specifiedin ``origin``
+
+        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 locations were not found.
+
+        Returns
+        -------
+        Pore indices on the calling object corresponding to the same pores
+        on the ``origin`` object.  Can be an array or a tuple containing an
+        array and a mask, depending on the value of ``filtered``.
+
+        See Also
+        --------
+        pores
+        map_throats
+
+        """
+        ids = origin['pore._id'][pores]
+        return self._map(element='pore', ids=ids, filtered=filtered)
+
+    def map_throats(self, throats, origin, filtered=True):
+        r"""
+        Given a list of throats on a target object, finds indices of
+        those throats on the calling object
+
+        Parameters
+        ----------
+        throats : array_like
+            The indices of the throats on the object specified in ``origin``
+
+        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 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 ``origin`` object.  Can be an array or a tuple containing an
+        array and a mask, depending on the value of ``filtered``.
+
+        See Also
+        --------
+        throats
+        map_pores
+
+        """
+        ids = origin['throat._id'][throats]
+        return self._map(element='throat', ids=ids, filtered=filtered)
+
+    def check_data_health(self):
+        r"""
+        Check the health of pore and throat data arrays.
+
+        Returns
+        -------
+        health: HealthDict object
+            A  basic dictionary with an added ``health`` attribute that is
+            ``True`` if all entries in the dict are deemed healthy
+            (empty lists), or ``False`` otherwise.
+
+        Examples
+        --------
+        >>> import openpnm
+        >>> pn = openpnm.network.Cubic(shape=[5, 5, 5])
+        >>> h = pn.check_data_health()
+        >>> h.health
+        True
+        """
+        health = self.project.check_data_health(obj=self)
+        return health
+
+
+class LabelMixin:
+    def _get_labels(self, element, locations, mode):
+        r"""
+        This is the actual label getter method, but it should not be called
+        directly.  Use ``labels`` instead.
+        """
+        # Parse inputs
+        locations = self._parse_indices(locations)
+        element = self._parse_element(element=element)
+        # Collect list of all pore OR throat labels
+        labels = self.keys(mode='labels', element=element)
+        labels.sort()
+        labels = np.array(labels)  # Convert to ND-array for following checks
+        # Make an 2D array with locations in rows and labels in cols
+        arr = np.vstack([self[item][locations] for item in labels]).T
+        num_hits = np.sum(arr, axis=0)  # Number of locations with each label
+        if mode in ['or', 'union', 'any']:
+            temp = labels[num_hits > 0]
+        elif mode in ['and', 'intersection']:
+            temp = labels[num_hits == locations.size]
+        elif mode in ['xor', 'exclusive_or']:
+            temp = labels[num_hits == 1]
+        elif mode in ['nor', 'not', 'none']:
+            temp = labels[num_hits == 0]
+        elif mode in ['nand']:
+            temp = labels[num_hits == (locations.size - 1)]
+        elif mode in ['xnor', 'nxor']:
+            temp = labels[num_hits > 1]
+        else:
+            raise Exception('Unrecognized mode:'+str(mode))
+        return PrintableList(temp)
+
+    def labels(self, pores=[], throats=[], element=None, mode='union'):
+        r"""
+        Returns a list of labels present on the object
+
+        Additionally, this function can return labels applied to a specified
+        set of pores or throats
+
+        Parameters
+        ----------
+        element : string
+            Controls whether pore or throat labels are returned.  If empty then
+            both are returned (default).
+
+        pores (or throats) : array_like
+            The pores (or throats) whose labels are sought.  If left empty a
+            list containing all pore and throat labels is returned.
+
+        mode : string, optional
+            Controls how the query should be performed.  Only applicable
+            when ``pores`` or ``throats`` are specified:
+
+            **'or', 'union', 'any'**: (default) Returns the labels that are
+            assigned to *any* of the given locations.
+
+            **'and', 'intersection', 'all'**: Labels that are present on *all*
+            the given locations.
+
+            **'xor', 'exclusive_or'** : Labels that are present on *only one*
+            of the given locations.
+
+            **'nor', 'none', 'not'**: Labels that are *not* present on any of
+            the given locations.
+
+            **'nand'**: Labels that are present on *all but one* of the given
+            locations
+
+            **'xnor'**: Labels that are present on *more than one* of the given
+            locations.  'nxor' is also accepted.
+
+        Returns
+        -------
+        A list containing the labels on the object.  If ``pores`` or
+        ``throats`` are given, the results are filtered according to the
+        specified ``mode``.
+
+        See Also
+        --------
+        props
+        keys
+
+        Notes
+        -----
+        Technically, *'nand'* and *'xnor'* should also return pores with *none*
+        of the labels but these are not included.  This makes the returned list
+        more useful.
+
+        Examples
+        --------
+        >>> import openpnm as op
+        >>> pn = op.network.Cubic(shape=[5, 5, 5])
+        >>> pn.labels(pores=[11, 12])
+        ['pore.all', 'pore.internal', 'pore.left', 'pore.surface']
+        """
+        # Short-circuit query when no pores or throats are given
+        if (np.size(pores) == 0) and (np.size(throats) == 0):
+            labels = PrintableList(self.keys(element=element, mode='labels'))
+        elif (np.size(pores) > 0) and (np.size(throats) > 0):
+            raise Exception('Cannot perform label query on pores and '
+                            + 'throats simultaneously')
+        elif np.size(pores) > 0:
+            labels = self._get_labels(element='pore', locations=pores,
+                                      mode=mode)
+        elif np.size(throats) > 0:
+            labels = self._get_labels(element='throat', locations=throats,
+                                      mode=mode)
+        return labels
+
+    def set_label(self, label, pores=None, throats=None, mode='add'):
+        r"""
+        Creates or updates a label array
+
+        Parameters
+        ----------
+        label : string
+                The label to apply to the specified locations
+        pores : array_like
+            A list of pore indices or a boolean mask of where given label
+            should be added or removed (see ``mode``)
+        throats : array_like
+            A list of throat indices or a boolean mask of where given label
+            should be added or removed (see ``mode``)
+        mode : string
+            Controls how the labels are handled.  Options are:
+
+            * 'add' (default)
+                Adds the given label to the specified locations while
+                keeping existing labels
+            * 'overwrite'
+                Removes existing label from all locations before
+                adding the label in the specified locations
+            * 'remove'
+                Removes the given label from the specified locations
+                leaving the remainder intact
+            * 'purge'
+                Removes the specified label from the object completely
+            * 'clear'
+                Sets all the labels to ``False`` but does not remove the label
+                array
+
+        """
+        self._parse_mode(mode=mode,
+                         allowed=['add', 'overwrite', 'remove', 'purge',
+                                  'clear'])
+
+        if label.split('.')[0] in ['pore', 'throat']:
+            label = label.split('.', 1)[1]
+
+        if (pores is not None) and (throats is not None):
+            self.set_label(label=label, pores=pores, mode=mode)
+            self.set_label(label=label, throats=throats, mode=mode)
+            return
+        elif pores is not None:
+            locs = self._parse_indices(pores)
+            element = 'pore'
+        elif throats is not None:
+            locs = self._parse_indices(throats)
+            element = 'throat'
+
+        if mode == 'add':
+            if element + '.' + label not in self.keys():
+                self[element + '.' + label] = False
+            self[element + '.' + label][locs] = True
+        if mode == 'overwrite':
+            self[element + '.' + label] = False
+            self[element + '.' + label][locs] = True
+        if mode == 'remove':
+            self[element + '.' + label][locs] = False
+        if mode == 'clear':
+            self['pore' + '.' + label] = False
+            self['throat' + '.' + label] = False
+        if mode == 'purge':
+            _ = self.pop('pore.' + label, None)
+            _ = self.pop('throat.' + label, None)
+
+    def _get_indices(self, element, labels='all', mode='or'):
+        r"""
+        This is the actual method for getting indices, but should not be called
+        directly.  Use ``pores`` or ``throats`` instead.
+        """
+        # Parse and validate all input values.
+        element = self._parse_element(element, single=True)
+        labels = self._parse_labels(labels=labels, element=element)
+        if element+'.all' not in self.keys():
+            raise Exception('Cannot proceed without {}.all'.format(element))
+
+        # Begin computing label array
+        if mode in ['or', 'any', 'union']:
+            union = np.zeros_like(self[element+'.all'], dtype=bool)
+            for item in labels:  # Iterate over labels and collect all indices
+                union = union + self[element+'.'+item.split('.')[-1]]
+            ind = union
+        elif mode in ['and', 'all', 'intersection']:
+            intersect = np.ones_like(self[element+'.all'], dtype=bool)
+            for item in labels:  # Iterate over labels and collect all indices
+                intersect = intersect*self[element+'.'+item.split('.')[-1]]
+            ind = intersect
+        elif mode in ['xor', 'exclusive_or']:
+            xor = np.zeros_like(self[element+'.all'], dtype=int)
+            for item in labels:  # Iterate over labels and collect all indices
+                info = self[element+'.'+item.split('.')[-1]]
+                xor = xor + np.int8(info)
+            ind = (xor == 1)
+        elif mode in ['nor', 'not', 'none']:
+            nor = np.zeros_like(self[element+'.all'], dtype=int)
+            for item in labels:  # Iterate over labels and collect all indices
+                info = self[element+'.'+item.split('.')[-1]]
+                nor = nor + np.int8(info)
+            ind = (nor == 0)
+        elif mode in ['nand']:
+            nand = np.zeros_like(self[element+'.all'], dtype=int)
+            for item in labels:  # Iterate over labels and collect all indices
+                info = self[element+'.'+item.split('.')[-1]]
+                nand = nand + np.int8(info)
+            ind = (nand < len(labels)) * (nand > 0)
+        elif mode in ['xnor', 'nxor']:
+            xnor = np.zeros_like(self[element+'.all'], dtype=int)
+            for item in labels:  # Iterate over labels and collect all indices
+                info = self[element+'.'+item.split('.')[-1]]
+                xnor = xnor + np.int8(info)
+            ind = (xnor > 1)
+        else:
+            raise Exception('Unsupported mode: '+mode)
+        # Extract indices from boolean mask
+        ind = np.where(ind)[0]
+        ind = ind.astype(dtype=int)
+        return ind
+
+    def pores(self, labels='all', mode='or', asmask=False, to_global=False):
+        r"""
+        Returns pore indicies where given labels exist, according to the logic
+        specified by the ``mode`` argument.
+
+        Parameters
+        ----------
+        labels : string or list of strings
+            The label(s) whose pores locations are requested.  This argument
+            also accepts '*' for wildcard searches.
+
+        mode : string
+            Specifies how the query should be performed.  The options are:
+
+            **'or', 'union', 'any'** : (default) Pores with *one or more* of
+            the given labels are returned.
+
+            **'and', 'intersection', 'all'** : Pores with *all* of the given
+            labels are returned.
+
+            **'xor', 'exclusive_or'** : Pores with *only one* of the given
+            labels are returned.
+
+            **'nor', 'none', 'not'** : Pores with *none* of the given labels
+            are returned.
+
+            **'nand'** : Pores with *not all* of the given labels are
+            returned.
+
+            **'xnor'** : Pores with *more than one* of the given labels are
+            returned.
+
+        asmask : boolean
+            If ``True`` then a boolean array of length Np is returned with
+            ``True`` values indicating the pores that satisfy the query.
+
+        to_global : bool
+            If ``True``, the returned indices will be indexed relative to the
+            full domain.  This only has an effect when the calling object
+            is a Subdomain.
+
+        Returns
+        -------
+        A Numpy array containing pore indices filtered by the logic specified
+        in ``mode``.
+
+        See Also
+        --------
+        throats
+        map_pores
+
+        Notes
+        -----
+        Technically, *nand* and *xnor* should also return pores with *none* of
+        the labels but these are not included.  This makes the returned list
+        more useful.
+
+        To perform more complex or compound queries, you can opt to receive
+        the result a a boolean mask (``asmask=True``), then manipulate the
+        arrays manually.
+
+        Examples
+        --------
+        >>> import openpnm as op
+        >>> pn = op.network.Cubic(shape=[5, 5, 5])
+        >>> Ps = pn.pores(labels=['top', 'back'], mode='union')
+        >>> Ps[:5]  # Look at first 5 pore indices
+        array([ 4,  9, 14, 19, 20])
+        >>> pn.pores(labels=['top', 'back'], mode='xnor')
+        array([ 24,  49,  74,  99, 124])
+        """
+        ind = self._get_indices(element='pore', labels=labels, mode=mode)
+        if to_global and hasattr(self, 'to_global'):
+            ind = self.to_global(pores=ind)
+            if asmask:
+                ind = self._domain.tomask(pores=ind)
+        else:
+            if asmask:
+                ind = self.tomask(pores=ind)
+        return ind
+
+    def throats(self, labels='all', mode='or', asmask=False, to_global=False):
+        r"""
+        Returns throat locations where given labels exist, according to the
+        logic specified by the ``mode`` argument.
+
+        Parameters
+        ----------
+        labels : string or list of strings
+            The throat label(s) whose locations are requested.  If omitted,
+            'all' throat inidices are returned.  This argument also accepts
+            '*' for wildcard searches.
+
+        mode : string
+            Specifies how the query should be performed.  The options are:
+
+            **'or', 'union', 'any'** : (default) Throats with *one or more* of
+            the given labels are returned.
+
+            **'and', 'intersection', 'all'** : Throats with *all* of the given
+            labels are returned.
+
+            **'xor', 'exclusive_or'** : Throats with *only one* of the given
+            labels are returned.
+
+            **'nor', 'none', 'not'** : Throats with *none* of the given labels
+            are returned.
+
+            **'nand'** : Throats with *not all* of the given labels are
+            returned.
+
+            **'xnor'** : Throats with *more than one* of the given labels are
+            returned.
+
+        asmask : boolean
+            If ``True`` then a boolean array of length Nt is returned with
+            ``True`` values indicating the throats that satisfy the query.
+
+        to_global : bool
+            If ``True``, the returned indices will be indexed relative to the
+            full domain.  This only has an effect when the calling object
+            is a Subdomain.
+
+        Returns
+        -------
+        A Numpy array containing throat indices filtered by the logic specified
+        in ``mode``.
+
+        See Also
+        --------
+        pores
+        map_throats
+
+        Examples
+        --------
+        >>> import openpnm as op
+        >>> pn = op.network.Cubic(shape=[3, 3, 3])
+        >>> Ts = pn.throats()
+        >>> Ts[0:5]  # Look at first 5 throat indices
+        array([0, 1, 2, 3, 4])
+
+        """
+        ind = self._get_indices(element='throat', labels=labels, mode=mode)
+        if to_global and hasattr(self, 'to_global'):
+            ind = self.to_global(throats=ind)
+            if asmask:
+                ind = self._domain.tomask(throats=ind)
+        else:
+            if asmask:
+                ind = self.tomask(throats=ind)
+        return ind
+
+    def filter_by_label(self, pores=[], throats=[], labels=None, mode='or'):
+        r"""
+        Returns which of the supplied pores (or throats) has the specified
+        label(s)
+
+        Parameters
+        ----------
+        pores, or throats : array_like
+            List of pores or throats to be filtered
+
+        labels : list of strings
+            The labels to apply as a filter
+
+        mode : string
+
+            Controls how the filter is applied.  Options include:
+
+            **'or', 'union', 'any'**: (default) Returns a list of the given
+            locations where *any* of the given labels exist.
+
+            **'and', 'intersection', 'all'**: Only locations where *all* the
+            given labels are found.
+
+            **'xor', 'exclusive_or'**: Only locations where exactly *one* of
+            the given labels are found.
+
+            **'nor', 'none', 'not'**: Only locations where *none* of the given
+            labels are found.
+
+            **'nand'** : Only locations with *some but not all* of the given
+            labels are returned.
+
+            **'xnor'** : Only locations with *more than one* of the given
+            labels are returned.
+
+        Returns
+        -------
+        A list of pores (or throats) that have been filtered according the
+        given criteria.  The returned list is a subset of the received list of
+        pores (or throats).
+
+        See Also
+        --------
+        pores
+        throats
+
+        Examples
+        --------
+        >>> import openpnm as op
+        >>> pn = op.network.Cubic(shape=[5, 5, 5])
+        >>> pn.filter_by_label(pores=[0, 1, 25, 32], labels='left')
+        array([0, 1])
+        >>> Ps = pn.pores(['top', 'bottom', 'back'], mode='or')
+        >>> pn.filter_by_label(pores=Ps, labels=['top', 'back'],
+        ...                    mode='and')
+        array([ 24,  49,  74,  99, 124])
+        """
+        # Convert inputs to locations and element
+        if (np.size(throats) > 0) and (np.size(pores) > 0):
+            raise Exception('Can only filter either pores OR labels')
+        if np.size(pores) > 0:
+            element = 'pore'
+            locations = self._parse_indices(pores)
+        elif np.size(throats) > 0:
+            element = 'throat'
+            locations = self._parse_indices(throats)
+        else:
+            return(np.array([], dtype=int))
+        labels = self._parse_labels(labels=labels, element=element)
+        labels = [element+'.'+item.split('.')[-1] for item in labels]
+        all_locs = self._get_indices(element=element, labels=labels, mode=mode)
+        mask = self._tomask(indices=all_locs, element=element)
+        ind = mask[locations]
+        return locations[ind]
+
+    def num_pores(self, labels='all', mode='or'):
+        r"""
+        Returns the number of pores of the specified labels
+
+        Parameters
+        ----------
+        labels : list of strings, optional
+            The pore labels that should be included in the count.
+            If not supplied, all pores are counted.
+
+        labels : list of strings
+            Label of pores to be returned
+
+        mode : string, optional
+            Specifies how the count should be performed.  The options are:
+
+            **'or', 'union', 'any'** : (default) Pores with *one or more* of
+            the given labels are counted.
+
+            **'and', 'intersection', 'all'** : Pores with *all* of the given
+            labels are counted.
+
+            **'xor', 'exclusive_or'** : Pores with *only one* of the given
+            labels are counted.
+
+            **'nor', 'none', 'not'** : Pores with *none* of the given labels
+            are counted.
+
+            **'nand'** : Pores with *some but not all* of the given labels are
+            counted.
+
+            **'xnor'** : Pores with *more than one* of the given labels are
+            counted.
+
+        Returns
+        -------
+        Np : int
+            Number of pores with the specified labels
+
+        See Also
+        --------
+        num_throats
+        count
+
+        Notes
+        -----
+        Technically, *'nand'* and *'xnor'* should also count pores with *none*
+        of the labels, however, to make the count more useful these are not
+        included.
+
+        Examples
+        --------
+        >>> import openpnm as op
+        >>> pn = op.network.Cubic(shape=[5, 5, 5])
+        >>> pn.num_pores()
+        125
+        >>> pn.num_pores(labels=['top'])
+        25
+        >>> pn.num_pores(labels=['top', 'front'], mode='or')
+        45
+        >>> pn.num_pores(labels=['top', 'front'], mode='xnor')
+        5
+
+        """
+        # Count number of pores of specified type
+        Ps = self._get_indices(labels=labels, mode=mode, element='pore')
+        Np = np.shape(Ps)[0]
+        return Np
+
+    def num_throats(self, labels='all', mode='union'):
+        r"""
+        Return the number of throats of the specified labels
+
+        Parameters
+        ----------
+        labels : list of strings, optional
+            The throat labels that should be included in the count.
+            If not supplied, all throats are counted.
+
+        mode : string, optional
+            Specifies how the count should be performed.  The options are:
+
+            **'or', 'union', 'any'** : (default) Throats with *one or more* of
+            the given labels are counted.
+
+            **'and', 'intersection', 'all'** : Throats with *all* of the given
+            labels are counted.
+
+            **'xor', 'exclusive_or'** : Throats with *only one* of the given
+            labels are counted.
+
+            **'nor', 'none', 'not'** : Throats with *none* of the given labels
+            are counted.
+
+            **'nand'** : Throats with *some but not all* of the given labels
+            are counted.
+
+            **'xnor'** : Throats with *more than one* of the given labels are
+            counted.
+
+        Returns
+        -------
+        Nt : int
+            Number of throats with the specified labels
+
+        See Also
+        --------
+        num_pores
+        count
+
+        Notes
+        -----
+        Technically, *'nand'* and *'xnor'* should also count throats with
+        *none* of the labels, however, to make the count more useful these are
+        not included.
+
+        """
+        # Count number of pores of specified type
+        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
diff --git a/openpnm/core/Subdomain.py b/openpnm/core/Subdomain.py
index ef8c71933..1916fa1b5 100644
--- a/openpnm/core/Subdomain.py
+++ b/openpnm/core/Subdomain.py
@@ -161,3 +161,56 @@ class Subdomain(Base, LegacyMixin, LabelMixin):
 
         # Finally assign mask to boss
         boss[element + '.' + self.name] = np.copy(mask)
+
+    def to_global(self, pores=None, throats=None):
+        r"""
+        Convert local indices from a subdomain object to global values
+
+        Parameters
+        ----------
+        pores, throats : array_like
+            List of pore or throat indices to be converted
+
+        Returns
+        -------
+        indices : ndarray
+            An array of location indices
+        """
+        if pores is not None:
+            element = 'pore'
+            locs = pores
+        elif throats is not None:
+            element = 'throat'
+            locs = throats
+        mask = self._domain[element + '.' + self.name]
+        inds = np.where(mask)[0]
+        return inds[locs]
+
+    def to_local(self, pores=None, throats=None, missing_vals=-1):
+        r"""
+        Convert global indices to local values relative to a subdomain object
+
+        Parameters
+        ----------
+        pores, throats : array_like
+            List of pore or throat indices to be converted
+        missing_values : scalar
+            The value to put into missing locations if global indices are not
+            found.
+
+        Returns
+        -------
+        indices : ndarray
+            An array of location indices
+        """
+        if pores is not None:
+            element = 'pore'
+            locs = pores
+        if throats is not None:
+            element = 'throat'
+            locs = throats
+        mask = np.ones_like(self._domain[element + '.all'],
+                            dtype=int)*missing_vals
+        inds = np.where(self._domain[element + '.' + self.name])[0]
+        mask[inds] = self.Ps
+        return mask[locs]
diff --git a/openpnm/core/__init__.py b/openpnm/core/__init__.py
index 89c759eb3..45c71c52d 100644
--- a/openpnm/core/__init__.py
+++ b/openpnm/core/__init__.py
@@ -103,6 +103,6 @@ and run.
 """
 
 from .ModelsMixin import ModelsMixin, ModelsDict
-from .Base import Base, LegacyMixin, LabelMixin
-from .Mixins import ParamMixin
+from .Base import Base
+from .Mixins import ParamMixin, LegacyMixin, LabelMixin
 from .Subdomain import Subdomain
diff --git a/openpnm/models/geometry/misc.py b/openpnm/models/geometry/misc.py
index 29f96b1a2..fa4df4578 100644
--- a/openpnm/models/geometry/misc.py
+++ b/openpnm/models/geometry/misc.py
@@ -9,7 +9,7 @@ def _get_conduit_diameters(target, pore_diameter, throat_diameter):
     Private helper methods for fetching conduit diameters.
     """
     network = target.project.network
-    throats = target.throats(target=network)
+    throats = target.throats(to_global=True)
     cn = network["throat.conns"][throats]
 
     D1 = network[pore_diameter][cn[:, 0]]
diff --git a/openpnm/models/physics/diffusive_conductance.py b/openpnm/models/physics/diffusive_conductance.py
index ff68d7da1..937af682f 100644
--- a/openpnm/models/physics/diffusive_conductance.py
+++ b/openpnm/models/physics/diffusive_conductance.py
@@ -270,7 +270,7 @@ def multiphase_diffusion(target,
     """
     # Fetch openpnm objects
     network = target.network
-    throats = target.throats(target=network)
+    throats = target.throats(to_global=True)
     phase = target.project.find_phase(target)
     cn = network.conns[throats]
     F = network[size_factors]
diff --git a/openpnm/models/physics/hydraulic_conductance.py b/openpnm/models/physics/hydraulic_conductance.py
index 108a4df7e..c770f6ce0 100644
--- a/openpnm/models/physics/hydraulic_conductance.py
+++ b/openpnm/models/physics/hydraulic_conductance.py
@@ -39,7 +39,7 @@ def generic_hydraulic(target,
 
     """
     network = target.network
-    throats = target.throats(target=network)
+    throats = target.throats(to_global=True)
     conns = network.conns[throats]
     phase = target.project.find_phase(target)
     F = network[size_factors]
diff --git a/openpnm/network/GenericNetwork.py b/openpnm/network/GenericNetwork.py
index 33159b875..514f66b58 100644
--- a/openpnm/network/GenericNetwork.py
+++ b/openpnm/network/GenericNetwork.py
@@ -126,18 +126,11 @@ class GenericNetwork(ParamMixin, Base, ModelsMixin, LegacyMixin, LabelMixin):
     future use to save construction time.
 
     """
-    def __new__(cls, *args, **kwargs):
-        instance = super(GenericNetwork, cls).__new__(cls, *args, **kwargs)
-        # Initialize adjacency and incidence matrix dictionaries
-        instance._im = {}
-        instance._am = {}
-        return instance
-
     def __init__(self, conns=None, coords=None, settings={}, **kwargs):
         self.settings = SettingsAttr(NetworkSettings, settings)
         super().__init__(settings=self.settings, **kwargs)
-        self.settings._getdocs(NetworkSettings)
-
+        self._am = {}
+        self._im = {}
         if coords is not None:
             Np = np.shape(coords)[0]
             self['pore.all'] = np.ones(Np, dtype=bool)


Test patch:
diff --git a/tests/unit/core/BaseTest.py b/tests/unit/core/BaseTest.py
index cc0c0264f..f82d99875 100644
--- a/tests/unit/core/BaseTest.py
+++ b/tests/unit/core/BaseTest.py
@@ -111,7 +111,7 @@ class BaseTest:
         b = self.net.pores(labels=['top', 'front'], mode='or')
         assert np.all(np.where(a)[0] == b)
 
-    def test_pores_with_target(self):
+    def test_pores_and_throats_with_to_global(self):
         net = op.network.Cubic(shape=[2, 2, 2])
         geo1 = op.geometry.GenericGeometry(network=net,
                                            pores=[1, 3, 5, 7],
@@ -119,27 +119,10 @@ class BaseTest:
         geo2 = op.geometry.GenericGeometry(network=net,
                                            pores=[0, 2, 4, 6],
                                            throats=range(6, 12))
-        assert np.all(net.pores('top', target=geo1) == [0, 1, 2, 3])
-        assert len(net.pores('top', target=geo2)) == 0
-        mapped = net.map_pores(pores=[0, 1, 2, 3], origin=geo1)
-        assert np.all(mapped == net.pores('geo_01'))
-        mapped = net.map_pores(pores=[0, 1, 2, 3], origin=geo2)
-        assert np.all(mapped == net.pores('geo_02'))
-
-    def test_throats_with_target(self):
-        net = op.network.Cubic(shape=[2, 2, 2])
-        geo1 = op.geometry.GenericGeometry(network=net,
-                                           pores=[1, 3, 5, 7],
-                                           throats=range(6))
-        geo2 = op.geometry.GenericGeometry(network=net,
-                                           pores=[0, 2, 4, 6],
-                                           throats=range(6, 12))
-        assert np.all(net.throats('surface', target=geo1) == [0, 1, 2, 3, 4, 5])
-        assert np.all(net.throats('surface', target=geo2) == [0, 1, 2, 3, 4, 5])
-        mapped = net.map_throats(throats=[0, 1, 2, 3, 4, 5], origin=geo1)
-        assert np.all(mapped == net.throats('geo_01'))
-        mapped = net.map_throats(throats=[0, 1, 2, 3, 4, 5], origin=geo2)
-        assert np.all(mapped == net.throats('geo_02'))
+        assert np.all(net.throats('geo_01') == geo1.throats(to_global=True))
+        assert np.all(net.throats('geo_02') == geo2.throats(to_global=True))
+        assert np.all(net.pores('geo_01') == geo1.pores(to_global=True))
+        assert np.all(net.pores('geo_02') == geo2.pores(to_global=True))
 
     def test_throats(self):
         a = self.net.throats()
diff --git a/tests/unit/io/PickleTest.py b/tests/unit/io/PickleTest.py
index 9e978db21..8a761ae10 100644
--- a/tests/unit/io/PickleTest.py
+++ b/tests/unit/io/PickleTest.py
@@ -10,6 +10,11 @@ class PickleTest:
     def setup_class(self):
         pass
 
+    def teardown_class(self, tmpdir=None):
+        os.remove('test.pkl')
+        os.remove("proj.pkl")
+        os.remove("pn.pkl")
+
     def test_create_save_and_load_workspace(self, tmpdir=None):
         ws = op.Workspace()
         ws.clear()
@@ -21,7 +26,6 @@ class PickleTest:
         assert len(ws) == 2
         ws = op.io.Pickle.load_workspace('test.pkl', overwrite=True)
         assert len(ws) == 1
-        os.remove('test.pkl')
 
     def test_create_save_and_load_project(self, tmpdir=None):
         ws = op.Workspace()
@@ -32,7 +36,6 @@ class PickleTest:
         proj = op.io.Pickle.load_project('proj.pkl')
         assert isinstance(proj, list)
         assert len(ws) == 2
-        os.remove('proj.pkl')
 
     def test_load_project_with_multiple_projects(self, tmpdir=None):
         ws = op.Workspace()
@@ -43,7 +46,6 @@ class PickleTest:
         op.io.Pickle.save_workspace(filename='proj.pkl')
         with pytest.raises(Exception):
             proj = op.io.Pickle.load_project('proj.pkl')
-        os.remove('proj.pkl')
 
     def test_load_handmade_project(self, tmpdir=None):
         ws = op.Workspace()
@@ -54,14 +56,12 @@ class PickleTest:
         proj = op.io.Pickle.load_project('proj.pkl')
         assert isinstance(proj, op.Project)
         assert len(ws.keys()) == 2
-        os.remove('proj.pkl')
 
     def test_load_poorly_made_project(self, tmpdir=None):
         proj = 5
         pickle.dump(proj, open('proj.pkl', 'wb'))
         with pytest.raises(Exception):
             proj = op.io.Pickle.load_project('proj.pkl')
-        os.remove('proj.pkl')
 
     def test_load_from_saved_dict(self, tmpdir=None):
         ws = op.Workspace()
@@ -75,7 +75,6 @@ class PickleTest:
                                             overwrite=False)
         assert len(ws.keys()) == 2
         assert isinstance(ws, op.Workspace)
-        os.remove("test.pkl")
 
     def test_load_workspace_from_poorly_made_dict(self):
         ws = op.Workspace()
@@ -84,7 +83,6 @@ class PickleTest:
         pickle.dump(pn, open('pn.pkl', 'wb'))
         with pytest.raises(Exception):
             ws = op.io.Pickle.load_workspace('pn.pkl')
-        os.remove("pn.pkl")
 
 
 if __name__ == '__main__':
@@ -99,3 +97,4 @@ if __name__ == '__main__':
                 t.__getattribute__(item)()
             except TypeError:
                 t.__getattribute__(item)(tmpdir=py.path.local())
+    t.teardown_class()