Repository: PMEAL/OpenPNM
Pull request #1245 (2019-04-19 11:43:13)

Add 2D support


Patch:
diff --git a/.gitignore b/.gitignore
index e6cc78b48..1deb80e82 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,4 +1,5 @@
 *.pyc
+.idea/
 .spyproject/
 .pytest_cache/
 .vscode/
diff --git a/openpnm/algorithms/GenericTransport.py b/openpnm/algorithms/GenericTransport.py
index 90bce2e20..21cae550d 100644
--- a/openpnm/algorithms/GenericTransport.py
+++ b/openpnm/algorithms/GenericTransport.py
@@ -4,7 +4,6 @@ import scipy.sparse as sprs
 import scipy.sparse.csgraph as spgr
 from scipy.spatial import ConvexHull
 from scipy.spatial import cKDTree
-from decimal import Decimal as dc
 from openpnm.topotools import iscoplanar
 from openpnm.algorithms import GenericAlgorithm
 from openpnm.utils import logging
@@ -315,8 +314,8 @@ class GenericTransport(GenericAlgorithm):
 
         values = np.array(bcvalues)
         if values.size > 1 and values.size != pores.size:
-            raise Exception('The number of boundary values must match the ' +
-                            'number of locations')
+            raise Exception('The number of boundary values must match the '
+                            + 'number of locations')
 
         # Store boundary values
         if ('pore.bc_'+bctype not in self.keys()) or (mode == 'overwrite'):
@@ -375,7 +374,7 @@ class GenericTransport(GenericAlgorithm):
             phase = self.project.phases()[self.settings['phase']]
             g = phase[self.settings['conductance']]
             am = network.create_adjacency_matrix(weights=g, fmt='coo')
-            self._pure_A = spgr.laplacian(am)
+            self._pure_A = spgr.laplacian(am).astype(float)
         self.A = self._pure_A.copy()
 
     def _build_b(self, force=False):
@@ -429,7 +428,7 @@ class GenericTransport(GenericAlgorithm):
             ind = np.isfinite(self['pore.bc_rate'])
             self.b[ind] = self['pore.bc_rate'][ind]
         if 'pore.bc_value' in self.keys():
-            f = np.abs(self.A.data).mean()
+            f = np.abs(self.A.diagonal()).mean()
             # Update b (impose bc values)
             ind = np.isfinite(self['pore.bc_value'])
             self.b[ind] = self['pore.bc_value'][ind] * f
@@ -533,8 +532,8 @@ class GenericTransport(GenericAlgorithm):
                 x, exit_code = solver(A=A, b=b, atol=atol, tol=rtol,
                                       maxiter=self.settings['solver_maxiter'])
                 if exit_code > 0:
-                    raise Exception('SciPy solver did not converge! ' +
-                                    'Exit code: ' + str(exit_code))
+                    raise Exception('SciPy solver did not converge! '
+                                    + 'Exit code: ' + str(exit_code))
             else:
                 x = solver(A=A, b=b)
             return x
@@ -563,7 +562,7 @@ class GenericTransport(GenericAlgorithm):
             else:
                 raise Exception('pyamg is not installed.')
             ml = pyamg.ruge_stuben_solver(A)
-            x = ml.solve(b=b, tol=1e-6)
+            x = ml.solve(b=b, tol=1e-10)
             return x
 
     def results(self):
@@ -667,8 +666,8 @@ class GenericTransport(GenericAlgorithm):
 
         """
         if self.settings['quantity'] not in self.keys():
-            raise Exception('The algorithm has not been run yet. Cannot ' +
-                            'calculate effective property.')
+            raise Exception('The algorithm has not been run yet. Cannot '
+                            + 'calculate effective property.')
 
         Ps = np.isfinite(self['pore.bc_value'])
         BCs = np.unique(self['pore.bc_value'][Ps])
@@ -718,12 +717,12 @@ class GenericTransport(GenericAlgorithm):
             logger.error('Detected outlet pores are not coplanar')
         Nin = np.ptp(inlets, axis=0) > 0
         if Nin.all():
-            logger.warning('Detected inlets are not oriented along a ' +
-                           'principle axis')
+            logger.warning('Detected inlets are not oriented along a '
+                           + 'principle axis')
         Nout = np.ptp(outlets, axis=0) > 0
         if Nout.all():
-            logger.warning('Detected outlets are not oriented along a ' +
-                           'principle axis')
+            logger.warning('Detected outlets are not oriented along a '
+                           + 'principle axis')
         hull_in = ConvexHull(points=inlets[:, Nin])
         hull_out = ConvexHull(points=outlets[:, Nout])
         if hull_in.volume != hull_out.volume:
@@ -732,8 +731,8 @@ class GenericTransport(GenericAlgorithm):
         return area
 
     def _get_domain_length(self, inlets=None, outlets=None):
-        logger.warning('Attempting to estimate domain length... ' +
-                       'could be low if boundary pores were not added')
+        logger.warning('Attempting to estimate domain length... '
+                       + 'could be low if boundary pores were not added')
         network = self.project.network
         if inlets is None:
             inlets = self._get_inlets()
diff --git a/openpnm/geometry/Boundary_2D.py b/openpnm/geometry/Boundary_2D.py
new file mode 100644
index 000000000..02bdd2f46
--- /dev/null
+++ b/openpnm/geometry/Boundary_2D.py
@@ -0,0 +1,101 @@
+# -*- coding: utf-8 -*-
+"""
+===============================================================================
+Boundary -- Subclass of GenericGeometry for Boundary Pores
+===============================================================================
+
+"""
+
+from openpnm.models import geometry as gm
+from openpnm.models import misc as mm
+from openpnm.geometry import GenericGeometry
+
+
+class Boundary_2D(GenericGeometry):
+    r"""
+    Boundary subclass of GenericGeometry.
+
+    The aim of this class is to assign geometric properties to boundary pores
+    that are appropriate, such as 0 volume pores (for invasion simulations and
+    porosity calculations) and very large throats (to offer negligible
+    resistance in transport calculations).  When necessary, this class assumes
+    that the pores are spherical and the throats are cylindrical.
+
+    Parameters
+    ----------
+    network : OpenPNM Network object
+        The Network to which the Geometry object should be associated
+
+    pores : array_like
+        The pores indice where the Geometry should be applied
+
+    throats : array_like
+        The throat indices where the Geometry should be applied
+
+    name : string, optional
+        A name to help identify the object, if not given one is automatically
+        generated
+
+    project : OpenPNM Project, optional
+        Either a Network or a Project must be specified
+
+    Examples
+    --------
+    >>> import openpnm
+    >>> pn = openpnm.network.Cubic(shape=[3, 3, 3])
+    >>> pn.add_boundary_pores()
+    >>> Ps_int = pn.pores(labels=['*boundary'], mode='not')
+    >>> Ps_boun = pn.pores(labels=['*boundary'])
+    >>> Ts_int = pn.throats(labels=['*boundary'], mode='not')
+    >>> Ts_boun = pn.throats(labels=['*boundary'])
+    >>> geo = openpnm.geometry.StickAndBall(network=pn,
+    ...                                     pores=Ps_int, throats=Ts_int)
+    >>> boun = openpnm.geometry.Boundary(network=pn, pores=Ps_boun,
+    ...                                  throats=Ts_boun)
+
+    """
+
+    def __init__(self, **kwargs):
+        super().__init__(**kwargs)
+
+        self.add_model(propname='pore.area',
+                       model=gm.pore_area.circle)
+
+        self.add_model(propname='pore.volume',
+                       model=mm.constant, value=0.0)
+
+        self.add_model(propname='pore.seed',
+                       model=mm.constant,
+                       value=1.0)
+
+        self.add_model(propname='throat.seed',
+                       model=mm.constant, value=1.0)
+
+        self.add_model(propname='pore.diameter',
+                       model=mm.constant, value=0.0)
+
+        self.add_model(propname='throat.diameter',
+                       model=mm.from_neighbor_pores,
+                       pore_prop='pore.diameter', mode='max')
+
+        self.add_model(propname='throat.endpoints',
+                       model=gm.throat_endpoints.circular_pores)
+
+        self.add_model(propname='throat.length',
+                       model=gm.throat_length.piecewise)
+
+        self.add_model(propname='throat.conduit_lengths',
+                       model=gm.throat_length.conduit_lengths)
+
+        self.add_model(propname='throat.area',
+                       model=gm.throat_area.rectangle)
+
+        self.add_model(propname='pore.area',
+                       model=mm.from_neighbor_throats,
+                       throat_prop='throat.area', mode='max')
+
+        self.add_model(propname='throat.surface_area',
+                       model=gm.throat_surface_area.rectangle)
+
+        self.add_model(propname='throat.volume',
+                       model=gm.throat_volume.extrusion)
diff --git a/openpnm/geometry/StickAndBall_2D.py b/openpnm/geometry/StickAndBall_2D.py
new file mode 100644
index 000000000..d568f428d
--- /dev/null
+++ b/openpnm/geometry/StickAndBall_2D.py
@@ -0,0 +1,232 @@
+import openpnm.models as mods
+from openpnm.geometry import GenericGeometry
+
+
+class StickAndBall_2D(GenericGeometry):
+    r"""
+    2D Stick and Ball subclass of GenericGeometry.  This subclass is meant as a
+    basic default geometry to get started quickly.
+
+    Pore diameters are randomly assigned between 0 and the largest sphere that
+    does not overlap with it's nearest neighbor.
+
+    Throat diameters are half the diameter of the smaller of it's two
+    neighboring pores.
+
+    Parameters
+    ----------
+    network : OpenPNM Network object
+        The network with which this Geometry should be associated
+
+    project : OpenPNM Project object, optional
+        Can be supplied instead of a ``network``
+
+    pores : array_like
+        The pores in the domain where this Geometry applies
+
+    throats : array_like
+        The throats in the domain where this Geometry applies
+
+    name : string
+        The name of the object, which is also used as the label where this
+        geometry is defined.
+
+    Examples
+    --------
+    Geometry objects (along with Physics objects) can be applied to a subset
+    of pores and/or throats.  This allows for different geometrical property
+    models to be applied in different regions.  This is illustrated in the
+    following code:
+
+    >>> import openpnm as op
+    >>> import scipy as sp
+    >>> import matplotlib.pyplot as plt
+    >>> pn = op.network.CubicDual(shape=[5, 5, 5])
+    >>> Ps = pn.pores('primary')
+    >>> Ts = pn.throats('primary')
+    >>> geo1 = op.geometry.StickAndBall_2D(network=pn, pores=Ps, throats=Ts)
+    >>> Ps = pn.pores('secondary')
+    >>> Ts = pn.throats(['secondary', 'interconnect'])
+    >>> geo2 = op.geometry.StickAndBall_2D(network=pn, pores=Ps, throats=Ts)
+
+    Now override the 'pore.diameter' values on the ``geo2`` object:
+
+    >>> geo2.remove_model('pore.diameter')  # Remove model and data
+    >>> geo2['pore.diameter'] = sp.rand(geo2.Np)*0.05
+
+    Look at the 'pore.diameter' distributions on each object:
+
+    >>> fig = plt.hist(geo1['pore.diameter'], bins=20, alpha=0.5)
+    >>> fig = plt.hist(geo2['pore.diameter'], bins=20, alpha=0.5)
+
+    The resulting figure shows that these two Geometry object each have a
+    different pore size distribution, with ``geo2`` being much smaller:
+
+    .. image:: /../docs/static/images/stick_and_ball_histogram.png
+        :align: center
+
+    Notes
+    -----
+    The table below gives a summary of the all the pore-scale models that are
+    included on this class.
+
+    All of these parameters can be adjusted manually by editing the entries in
+    the **ModelsDict** stored in the ``models`` attribute of the object.
+
+    For a full listing of models and their parameters use ``print(obj.models)``
+    where ``obj`` is the handle to the object.
+
+    In addition to these models, this class also has a number of constant
+    values assigned to it which can be found by running
+    ``props(mode='constants')``.
+
+    +----+----------------------+------------------+--------------------------+
+    | #  | Property Name        | Parameter        | Value                    |
+    +====+======================+==================+==========================+
+    | 1  | pore.seed            | model:           | random                   |
+    +----+----------------------+------------------+--------------------------+
+    |    |                      | element          | pore                     |
+    +----+----------------------+------------------+--------------------------+
+    |    |                      | num_range        | [0, 0.1]                 |
+    +----+----------------------+------------------+--------------------------+
+    |    |                      | seed             | None                     |
+    +----+----------------------+------------------+--------------------------+
+    |    |                      | regen_mode       | normal                   |
+    +----+----------------------+------------------+--------------------------+
+    | 2  | pore.max_size        | model:           | largest_sphere           |
+    +----+----------------------+------------------+--------------------------+
+    |    |                      | iters            | 10                       |
+    +----+----------------------+------------------+--------------------------+
+    |    |                      | regen_mode       | normal                   |
+    +----+----------------------+------------------+--------------------------+
+    |    |                      | fixed_diameter   | pore.fixed_diameter      |
+    +----+----------------------+------------------+--------------------------+
+    | 3  | pore.diameter        | model:           | product                  |
+    +----+----------------------+------------------+--------------------------+
+    |    |                      | prop1            | pore.max_size            |
+    +----+----------------------+------------------+--------------------------+
+    |    |                      | prop2            | pore.seed                |
+    +----+----------------------+------------------+--------------------------+
+    |    |                      | regen_mode       | normal                   |
+    +----+----------------------+------------------+--------------------------+
+    | 4  | pore.area            | model:           | sphere                   |
+    +----+----------------------+------------------+--------------------------+
+    |    |                      | pore_diameter    | pore.diameter            |
+    +----+----------------------+------------------+--------------------------+
+    |    |                      | regen_mode       | normal                   |
+    +----+----------------------+------------------+--------------------------+
+    | 5  | pore.volume          | model:           | sphere                   |
+    +----+----------------------+------------------+--------------------------+
+    |    |                      | pore_diameter    | pore.diameter            |
+    +----+----------------------+------------------+--------------------------+
+    |    |                      | regen_mode       | normal                   |
+    +----+----------------------+------------------+--------------------------+
+    | 6  | throat.max_size      | model:           | from_neighbor_pores      |
+    +----+----------------------+------------------+--------------------------+
+    |    |                      | mode             | min                      |
+    +----+----------------------+------------------+--------------------------+
+    |    |                      | pore_prop        | pore.diameter            |
+    +----+----------------------+------------------+--------------------------+
+    |    |                      | regen_mode       | normal                   |
+    +----+----------------------+------------------+--------------------------+
+    | 7  | throat.diameter      | model:           | scaled                   |
+    +----+----------------------+------------------+--------------------------+
+    |    |                      | factor           | 0.5                      |
+    +----+----------------------+------------------+--------------------------+
+    |    |                      | prop             | throat.max_size          |
+    +----+----------------------+------------------+--------------------------+
+    |    |                      | regen_mode       | normal                   |
+    +----+----------------------+------------------+--------------------------+
+    | 8  | throat.length        | model:           | piecewise                |
+    +----+----------------------+------------------+--------------------------+
+    |    |                      | pore_diameter    | pore.diameter            |
+    +----+----------------------+------------------+--------------------------+
+    |    |                      | regen_mode       | normal                   |
+    +----+----------------------+------------------+--------------------------+
+    | 9  | throat.surface_area  | model:           | cylinder                 |
+    +----+----------------------+------------------+--------------------------+
+    |    |                      | throat_diameter  | throat.diameter          |
+    +----+----------------------+------------------+--------------------------+
+    |    |                      | throat_length    | throat.length            |
+    +----+----------------------+------------------+--------------------------+
+    |    |                      | regen_mode       | normal                   |
+    +----+----------------------+------------------+--------------------------+
+    | 10 | throat.volume        | model:           | cylinder                 |
+    +----+----------------------+------------------+--------------------------+
+    |    |                      | throat_diameter  | throat.diameter          |
+    +----+----------------------+------------------+--------------------------+
+    |    |                      | throat_length    | throat.length            |
+    +----+----------------------+------------------+--------------------------+
+    |    |                      | regen_mode       | normal                   |
+    +----+----------------------+------------------+--------------------------+
+    | 11 | throat.area          | model:           | cylinder                 |
+    +----+----------------------+------------------+--------------------------+
+    |    |                      | throat_diameter  | throat.diameter          |
+    +----+----------------------+------------------+--------------------------+
+    |    |                      | regen_mode       | normal                   |
+    +----+----------------------+------------------+--------------------------+
+
+    """
+
+    def __init__(self, **kwargs):
+        super().__init__(**kwargs)
+
+        self.add_model(propname='pore.seed',
+                       model=mods.misc.random,
+                       element='pore',
+                       num_range=[0.5, 0.5],
+                       seed=None)
+
+        self.add_model(propname='pore.max_size',
+                       model=mods.geometry.pore_size.largest_sphere,
+                       iters=10)
+
+        self.add_model(propname='pore.diameter',
+                       model=mods.misc.product,
+                       prop1='pore.max_size',
+                       prop2='pore.seed')
+
+        self.add_model(propname='pore.area',
+                       model=mods.geometry.pore_area.circle,
+                       pore_diameter='pore.diameter')
+
+        self.add_model(propname='pore.volume',
+                       model=mods.geometry.pore_volume.circle,
+                       pore_diameter='pore.diameter')
+
+        self.add_model(propname='throat.max_size',
+                       model=mods.misc.from_neighbor_pores,
+                       mode='min',
+                       pore_prop='pore.diameter')
+
+        self.add_model(propname='throat.diameter',
+                       model=mods.misc.scaled,
+                       factor=0.5,
+                       prop='throat.max_size')
+
+        self.add_model(propname='throat.endpoints',
+                       model=mods.geometry.throat_endpoints.circular_pores,
+                       pore_diameter='pore.diameter',
+                       throat_diameter='throat.diameter')
+
+        self.add_model(propname='throat.length',
+                       model=mods.geometry.throat_length.piecewise,
+                       throat_endpoints='throat.endpoints')
+
+        self.add_model(propname='throat.surface_area',
+                       model=mods.geometry.throat_surface_area.rectangle,
+                       throat_length='throat.length')
+
+        self.add_model(propname='throat.volume',
+                       model=mods.geometry.throat_volume.rectangle,
+                       throat_diameter='throat.diameter',
+                       throat_length='throat.length')
+
+        self.add_model(propname='throat.area',
+                       model=mods.geometry.throat_area.rectangle,
+                       throat_diameter='throat.diameter')
+
+        self.add_model(propname='throat.conduit_lengths',
+                       model=mods.geometry.throat_length.conduit_lengths,
+                       throat_endpoints='throat.endpoints',
+                       throat_length='throat.length')
diff --git a/openpnm/geometry/__init__.py b/openpnm/geometry/__init__.py
index a47aaad05..2bb205056 100644
--- a/openpnm/geometry/__init__.py
+++ b/openpnm/geometry/__init__.py
@@ -90,4 +90,6 @@ that change can be cascaded to all dependent properties ('pore.diameter').
 """
 from .GenericGeometry import GenericGeometry
 from .StickAndBall import StickAndBall
+from .StickAndBall_2D import StickAndBall_2D
 from .Boundary import Boundary
+from .Boundary_2D import Boundary_2D
diff --git a/openpnm/models/geometry/pore_area.py b/openpnm/models/geometry/pore_area.py
index 206c3933f..c6b7b0256 100644
--- a/openpnm/models/geometry/pore_area.py
+++ b/openpnm/models/geometry/pore_area.py
@@ -51,3 +51,46 @@ def cube(target, pore_diameter='pore.diameter'):
     diams = target[pore_diameter]
     value = diams**2
     return value
+
+
+def circle(target, pore_diameter='pore.diameter'):
+    r"""
+    Calculate cross-sectional area assuming the pore body is a circle
+
+    Only suitable for true 2D simulations
+
+    Parameters
+    ----------
+    target : OpenPNM Geometry Object
+        The Geometry object which this model is associated with. This controls
+        the length of the calculated array, and also provides access to other
+        necessary geometric properties.
+
+    pore_diameter : string
+        The dictionary key of the array on the Geometry object containing the
+        pore diameter values necessary to find the area.
+
+    """
+    return target[pore_diameter]
+
+
+def square(target, pore_diameter='pore.diameter'):
+    r"""
+    Calculate cross-sectional area assuming the pore body is a square
+
+    Only suitable for true 2D simulations
+
+    Parameters
+    ----------
+    target : OpenPNM Geometry Object
+        The Geometry object which this model is associated with. This controls
+        the length of the calculated array, and also provides access to other
+        necessary geometric properties.
+
+    pore_diameter : string
+        The dictionary key of the array on the Geometry object containing the
+        pore diameter values necessary to find the area.
+
+
+    """
+    return target[pore_diameter]
diff --git a/openpnm/models/geometry/pore_surface_area.py b/openpnm/models/geometry/pore_surface_area.py
index d9338e4d2..601c175e9 100644
--- a/openpnm/models/geometry/pore_surface_area.py
+++ b/openpnm/models/geometry/pore_surface_area.py
@@ -43,6 +43,42 @@ def sphere(target, pore_diameter='pore.diameter', throat_area='throat.area'):
     return value
 
 
+def circle(target, pore_diameter='pore.diameter', throat_area='throat.area'):
+    r"""
+    Calculates internal surface area of pore bodies assuming they are circular
+    then subtracts the area of the neighboring throats in a crude way, by
+    simply considering the throat cross-sectional area, thus not accounting
+    for the actual curvature of the intersection.
+
+    Parameters
+    ----------
+    target : OpenPNM Object
+        The object for which these values are being calculated.  This
+        controls the length of the calculated array, and also provides
+        access to other necessary thermofluid properties.
+
+    pore_diameter : string
+        The dictionary key to the pore diameter array.
+
+    throat_area : string
+        The dictioanry key to the throat area array.  Throat areas are needed
+        since their insection with the pore are removed from the computation.
+
+    Returns
+    -------
+    value : NumPy ndarray
+        Array containing pore surface area values.
+
+    """
+    network = target.project.network
+    R = target[pore_diameter]/2
+    Asurf = 2*_np.pi*R
+    Tn = network.find_neighbor_throats(pores=target.Ps, flatten=False)
+    Tsurf = _np.array([_np.sum(network[throat_area][Ts]) for Ts in Tn])
+    value = Asurf - Tsurf
+    return value
+
+
 def cube(target, pore_diameter='pore.diameter', throat_area='throat.area'):
     r"""
     Calculates internal surface area of pore bodies assuming they are cubes
@@ -74,3 +110,36 @@ def cube(target, pore_diameter='pore.diameter', throat_area='throat.area'):
     Tsurf = _np.array([_np.sum(network[throat_area][Ts]) for Ts in Tn])
     value = 6*D**2 - Tsurf
     return value
+
+
+def square(target, pore_diameter='pore.diameter', throat_area='throat.area'):
+    r"""
+    Calculates internal surface area of pore bodies assuming they are squares
+    then subtracts the area of the neighboring throats.
+
+    Parameters
+    ----------
+    target : OpenPNM Object
+        The object for which these values are being calculated.  This
+        controls the length of the calculated array, and also provides
+        access to other necessary thermofluid properties.
+
+    pore_diameter : string
+        The dictionary key to the pore diameter array.
+
+    throat_area : string
+        The dictioanry key to the throat area array.  Throat areas are needed
+        since their insection with the pore are removed from the computation.
+
+    Returns
+    -------
+    value : NumPy ndarray
+        Array containing pore surface area values.
+
+    """
+    network = target.project.network
+    D = target[pore_diameter]
+    Tn = network.find_neighbor_throats(pores=target.Ps, flatten=False)
+    Tsurf = _np.array([_np.sum(network[throat_area][Ts]) for Ts in Tn])
+    value = 4*D - Tsurf
+    return value
diff --git a/openpnm/models/geometry/pore_volume.py b/openpnm/models/geometry/pore_volume.py
index 432ef35ce..6963a2cae 100644
--- a/openpnm/models/geometry/pore_volume.py
+++ b/openpnm/models/geometry/pore_volume.py
@@ -29,9 +29,7 @@ def sphere(target, pore_diameter='pore.diameter'):
         Array containing pore volume values.
 
     """
-    diams = target[pore_diameter]
-    value = _pi/6*diams**3
-    return value
+    return _pi/6*target[pore_diameter]**3
 
 
 def cube(target, pore_diameter='pore.diameter'):
@@ -54,6 +52,40 @@ def cube(target, pore_diameter='pore.diameter'):
         Array containing pore volume values.
 
     """
-    diams = target[pore_diameter]
-    value = diams**3
-    return value
+    return target[pore_diameter]**3
+
+
+def circle(target, pore_diameter='pore.diameter'):
+    r"""
+    Calculate pore volume from diameter assuming a spherical pore body
+
+    Parameters
+    ----------
+    target : OpenPNM Object
+        The object which this model is associated with. This controls
+        the length of the calculated array, and also provides access to other
+        necessary geometric properties.
+
+    pore_diameter : string
+        The dictionary key of the pore diameter values
+
+    """
+    return _pi/4 * target[pore_diameter]**2
+
+
+def square(target, pore_diameter='pore.diameter'):
+    r"""
+    Calculate pore volume from diameter assuming a cubic pore body
+
+    Parameters
+    ----------
+    target : OpenPNM Object
+        The object which this model is associated with. This controls
+        the length of the calculated array, and also provides access to other
+        necessary geometric properties.
+
+    pore_diameter : string
+        The dictionary key of the pore diameter values
+
+    """
+    return target[pore_diameter]**2
diff --git a/openpnm/models/geometry/throat_area.py b/openpnm/models/geometry/throat_area.py
index 38e8dcf28..b0ecb4983 100644
--- a/openpnm/models/geometry/throat_area.py
+++ b/openpnm/models/geometry/throat_area.py
@@ -55,3 +55,21 @@ def cuboid(target, throat_diameter='throat.diameter'):
     diams = target[throat_diameter]
     value = (diams)**2
     return value
+
+
+def rectangle(target, throat_diameter='throat.diameter'):
+    r"""
+    Calculate throat cross-sectional area for a rectangular throat
+
+    Parameters
+    ----------
+    target : OpenPNM Object
+        The object which this model is associated with. This controls the
+        length of the calculated array, and also provides access to other
+        necessary properties.
+
+    throat_diameter : string
+        Dictionary key of the throat diameter values
+
+    """
+    return target[throat_diameter]
diff --git a/openpnm/models/geometry/throat_endpoints.py b/openpnm/models/geometry/throat_endpoints.py
index ff2ca19ee..ad207b79a 100644
--- a/openpnm/models/geometry/throat_endpoints.py
+++ b/openpnm/models/geometry/throat_endpoints.py
@@ -49,6 +49,37 @@ def cubic_pores(target, pore_diameter='pore.diameter'):
     return {'head': EP1, 'tail': EP2}
 
 
+def square_pores(target, pore_diameter='pore.diameter'):
+    r"""
+    Calculate coordinates of throat endpoints, assuming throats don't overlap
+    with their adjacent pores. This model could be applied to conduits such as
+    cuboids or cylinders in series in true 2D simulations.
+
+    Parameters
+    ----------
+    target : OpenPNM Object
+        The object which this model is associated with. This controls the
+        length of the calculated array, and also provides access to other
+        necessary properties.
+
+    pore_diameter : string
+        Dictionary key of the pore diameter values
+
+    Returns
+    -------
+    EP : dictionary
+        Coordinates of throat endpoints stored in Dict form. Can be accessed
+        via the dict keys 'head' and 'tail'.
+
+    Notes
+    -----
+    This model is only accurate for cubic networks without diagonal
+    connections.
+
+    """
+    return cubic_pores(target, pore_diameter=pore_diameter)
+
+
 def spherical_pores(target, pore_diameter='pore.diameter',
                     throat_diameter='throat.diameter',
                     throat_centroid='throat.centroid'):
@@ -168,7 +199,7 @@ def circular_pores(target, pore_diameter='pore.diameter',
     the model takes care of the rest.
 
     """
-    return spherical_pores(target=target, pore_diameter=pore_diameter,
+    return spherical_pores(target, pore_diameter=pore_diameter,
                            throat_diameter=throat_diameter)
 
 
diff --git a/openpnm/models/geometry/throat_perimeter.py b/openpnm/models/geometry/throat_perimeter.py
index 17e70e2e5..f6c6a5d59 100644
--- a/openpnm/models/geometry/throat_perimeter.py
+++ b/openpnm/models/geometry/throat_perimeter.py
@@ -51,3 +51,26 @@ def cuboid(target, throat_diameter='throat.diameter'):
 
     """
     return target[throat_diameter]*4
+
+
+def rectangle(target, throat_diameter='throat.diameter'):
+    r"""
+    Calcuate the throat perimeter assuming a rectangular cross-section (2D)
+
+    Parameters
+    ----------
+    target : OpenPNM Object
+        The object which this model is associated with. This controls the
+        length of the calculated array, and also provides access to other
+        necessary properties.
+
+    throat_diameter : string
+        The dictionary key of the array containing the throat diameter values
+
+    Returns
+    -------
+    value : NumPy ndarray
+        Array containing throat perimeter values.
+
+    """
+    return 1.0
diff --git a/openpnm/models/geometry/throat_surface_area.py b/openpnm/models/geometry/throat_surface_area.py
index 4c09c9edb..f5ec04ad2 100644
--- a/openpnm/models/geometry/throat_surface_area.py
+++ b/openpnm/models/geometry/throat_surface_area.py
@@ -33,10 +33,7 @@ def cylinder(target, throat_diameter='throat.diameter',
         Array containing throat surface area values.
 
     """
-    D = target[throat_diameter]
-    L = target[throat_length]
-    value = _sp.pi*D*L
-    return value
+    return _sp.pi * target[throat_diameter] * target[throat_length]
 
 
 def cuboid(target, throat_diameter='throat.diameter',
@@ -64,10 +61,7 @@ def cuboid(target, throat_diameter='throat.diameter',
         Array containing throat surface area values.
 
     """
-    D = target[throat_diameter]
-    L = target[throat_length]
-    value = 4*D*L
-    return value
+    return 4 * target[throat_diameter] * target[throat_length]
 
 
 def extrusion(target, throat_perimeter='throat.perimeter',
@@ -96,7 +90,24 @@ def extrusion(target, throat_perimeter='throat.perimeter',
         Array containing throat surface area values.
 
     """
-    P = target[throat_perimeter]
-    L = target[throat_length]
-    value = P*L
-    return value
+    return target[throat_perimeter] * target[throat_length]
+
+
+def rectangle(target, throat_length='throat.length'):
+    r"""
+    Calculate surface area for a rectangular throat
+
+    Only suitable for true 2D simulations
+
+    Parameters
+    ----------
+    target : OpenPNM Object
+        The object which this model is associated with. This controls the
+        length of the calculated array, and also provides access to other
+        necessary properties.
+
+    throat_length : string
+        Dictionary key to the throat length array.  Default is 'throat.length'.
+
+    """
+    return 2 * target[throat_length]
diff --git a/openpnm/models/geometry/throat_volume.py b/openpnm/models/geometry/throat_volume.py
index cf71b9a8b..5a4953c8e 100644
--- a/openpnm/models/geometry/throat_volume.py
+++ b/openpnm/models/geometry/throat_volume.py
@@ -106,3 +106,27 @@ def extrusion(target, throat_length='throat.length',
     area = target[throat_area]
     value = leng*area
     return value
+
+
+def rectangle(target, throat_length='throat.length',
+              throat_diameter='throat.diameter'):
+    r"""
+    Calculate throat volume assuing a rectangular shape
+
+    Parameters
+    ----------
+    target : OpenPNM Object
+        The object which this model is associated with. This controls the
+        length of the calculated array, and also provides access to other
+        necessary properties.
+
+    throat_length and throat_diameter : strings
+        The dictionary keys containing the arrays with the throat diameter and
+        length values.
+
+    Notes
+    -----
+    At present this models does NOT account for the volume reprsented by the
+    intersection of the throat with a spherical pore body.
+    """
+    return target[throat_length] * target[throat_diameter]
diff --git a/openpnm/models/physics/flow_shape_factors.py b/openpnm/models/physics/flow_shape_factors.py
index 211406dcf..9c369c084 100644
--- a/openpnm/models/physics/flow_shape_factors.py
+++ b/openpnm/models/physics/flow_shape_factors.py
@@ -145,10 +145,6 @@ def conical_frustum_and_stick(target, pore_area='pore.area',
         poiseuille hydraulic conductance model. Shape factors are accessible
         via the keys: 'pore1', 'pore2' and 'throat'.
 
-    Notes
-    -----
-    This model accounts for the variable cross-section area in spheres.
-
     References
     ----------
     Akbari, M., Sinton, D., & Bahrami, M. (2011). Viscous flow in variable
@@ -190,3 +186,90 @@ def conical_frustum_and_stick(target, pore_area='pore.area',
     SFt[mt] = (Lt / (At**2 * Ft))[mt]
     _np.warnings.filterwarnings('default', category=RuntimeWarning)
     return {'pore1': SF1, 'throat': SFt, 'pore2': SF2}
+
+
+def ball_and_stick_2D(target, pore_area='pore.area',
+                      throat_area='throat.area',
+                      pore_diameter='pore.diameter',
+                      throat_diameter='throat.diameter',
+                      conduit_lengths='throat.conduit_lengths'):
+    r"""
+    Calculate conduit shape factors for hydraulic conductance, assuming
+    pores and throats are circles (balls) and rectangles (sticks).
+
+    Parameters
+    ----------
+    target : OpenPNM Object
+        The object which this model is associated with. This controls the
+        length of the calculated array, and also provides access to other
+        necessary properties.
+
+    pore_area : string
+        Dictionary key of the pore area values
+
+    throat_area : string
+        Dictionary key of the throat area values
+
+    pore_diameter : string
+        Dictionary key of the pore diameter values
+
+    throat_diameter : string
+        Dictionary key of the throat diameter values
+
+    conduit_lengths : string
+        Dictionary key of the conduit lengths' values
+
+    Returns
+    -------
+    SF : dictionary
+        Dictionary containing conduit shape factors to be used in hagen-
+        poiseuille hydraulic conductance model. Shape factors are accessible
+        via the keys: 'pore1', 'pore2' and 'throat'.
+
+    Notes
+    -----
+    (1) This model accounts for the variable cross-section area in spheres.
+
+    (2) WARNING: This model could break if `conduit_lengths` does not
+    correspond to an actual ball and stick! Example: pore length is greater
+    than pore radius --> :(
+
+    References
+    ----------
+    Akbari, M., Sinton, D., & Bahrami, M. (2011). Viscous flow in variable
+    cross-section microchannels of arbitrary shapes. International Journal of
+    Heat and Mass Transfer, 54(17-18), 3970-3978.
+
+    """
+    _np.warnings.filterwarnings('ignore', category=RuntimeWarning)
+    network = target.project.network
+    throats = network.map_throats(throats=target.Ts, origin=target)
+    cn = network['throat.conns'][throats]
+    # Get pore diameter
+    D1 = network[pore_diameter][cn[:, 0]]
+    D2 = network[pore_diameter][cn[:, 1]]
+    # Get conduit lengths
+    L1 = network[conduit_lengths + '.pore1'][throats]
+    L2 = network[conduit_lengths + '.pore2'][throats]
+    Lt = network[conduit_lengths + '.throat'][throats]
+    # Get pore/throat baseline areas (the one used in generic conductance)
+    A1 = network[pore_area][cn[:, 0]]
+    A2 = network[pore_area][cn[:, 1]]
+    At = network[throat_area][throats]
+    # Preallocating F, SF
+    # F is INTEGRAL(1/A^2) dx , x : 0 --> L
+    F1, F2, Ft = _sp.zeros((3, len(Lt)))
+    SF1, SF2, SFt = _sp.ones((3, len(Lt)))
+    # Setting SF to 1 when Li = 0 (ex. boundary pores)
+    # INFO: This is needed since area could also be zero, which confuses NumPy
+    m1, m2, mt = [Li != 0 for Li in [L1, L2, Lt]]
+    SF1[~m1] = SF2[~m2] = SFt[~mt] = 1
+    F1[m1] = (_atanh(2*L1/D1) / (2*D1))[m1]
+    F2[m2] = (_atanh(2*L2/D2) / (2*D2))[m2]
+    Ft[mt] = (Lt / At**2)[mt]
+    # Calculate conduit shape factors
+    SF1[m1] = (L1 / (A1**2 * F1))[m1]
+    SF2[m2] = (L2 / (A2**2 * F2))[m2]
+    SFt[mt] = (Lt / (At**2 * Ft))[mt]
+    _np.warnings.filterwarnings('default', category=RuntimeWarning)
+    return {'pore1': SF1, 'throat': SFt, 'pore2': SF2}
diff --git a/openpnm/models/physics/hydraulic_conductance.py b/openpnm/models/physics/hydraulic_conductance.py
index 8e2b965c2..83919c04c 100644
--- a/openpnm/models/physics/hydraulic_conductance.py
+++ b/openpnm/models/physics/hydraulic_conductance.py
@@ -306,7 +306,6 @@ def classic_hagen_poiseuille(target,
     Ps = network['throat.conns']
     # Get properties in every pore in the network
     phase = target.project.find_phase(target)
-    mup = phase[pore_viscosity]
     mut = phase.interpolate_data(propname=pore_viscosity)[throats]
     pdia = network[pore_diameter]
     # Get pore lengths
@@ -521,3 +520,96 @@ def generic_conductance(target, transport_type, pore_area, throat_area,
                         ' "flow" or "flow_powar_law"')
     # Apply shape factors and calculate the final conductance
     return (1/gt/SFt + 1/g1/SF1 + 1/g2/SF2)**(-1)
+
+
+def hagen_poiseuille_2D(target,
+                        pore_diameter='pore.diameter',
+                        throat_diameter='throat.diameter',
+                        pore_viscosity='pore.viscosity',
+                        throat_viscosity='throat.viscosity',
+                        conduit_lengths='throat.conduit_lengths',
+                        conduit_shape_factors='throat.flow_shape_factors'):
+    r"""
+    Calculate the hydraulic conductance of conduits in a 2D network, where a
+    conduit is ( 1/2 pore - full throat - 1/2 pore ). See the notes section.
+
+    Parameters
+    ----------
+    target : OpenPNM Object
+        The object which this model is associated with. This controls the
+        length of the calculated array, and also provides access to other
+        necessary properties.
+
+    pore_area : string
+        Dictionary key of the pore area values
+
+    throat_area : string
+        Dictionary key of the throat area values
+
+    pore_viscosity : string
+        Dictionary key of the pore viscosity values
+
+    throat_viscosity : string
+        Dictionary key of the throat viscosity values
+
+    conduit_lengths : string
+        Dictionary key of the conduit length values
+
+    conduit_shape_factors : string
+        Dictionary key of the conduit flow shape factor values
+
+    Returns
+    -------
+    g : ndarray
+        Array containing hydraulic conductance values for conduits in the
+        geometry attached to the given physics object.
+
+    Notes
+    -----
+    (1) This function requires that all the necessary phase properties already
+    be calculated.
+
+    (2) This function calculates the specified property for the *entire*
+    network then extracts the values for the appropriate throats at the end.
+
+    (3) This function assumes rectangular (2D) throats. Corrections for
+    different shapes and variable cross-section area can be imposed by passing
+    the proper flow_shape_factor argument.
+
+    """
+    network = target.project.network
+    throats = network.map_throats(throats=target.Ts, origin=target)
+    phase = target.project.find_phase(target)
+    cn = network['throat.conns'][throats]
+    # Getting pore/throat diameters
+    D1 = network[pore_diameter][cn[:, 0]]
+    Dt = network[throat_diameter][throats]
+    D2 = network[pore_diameter][cn[:, 1]]
+    # Getting conduit lengths
+    L1 = network[conduit_lengths + '.pore1'][throats]
+    Lt = network[conduit_lengths + '.throat'][throats]
+    L2 = network[conduit_lengths + '.pore2'][throats]
+    # Getting shape factors
+    try:
+        SF1 = phase[conduit_shape_factors+'.pore1'][throats]
+        SFt = phase[conduit_shape_factors+'.throat'][throats]
+        SF2 = phase[conduit_shape_factors+'.pore2'][throats]
+    except KeyError:
+        SF1 = SF2 = SFt = 1.0
+    # Getting viscosity values
+    try:
+        mut = phase[throat_viscosity][throats]
+    except KeyError:
+        mut = phase.interpolate_data(propname=pore_viscosity)[throats]
+    try:
+        mu1 = phase[pore_viscosity][cn[:, 0]]
+        mu2 = phase[pore_viscosity][cn[:, 1]]
+    except KeyError:
+        mu1 = phase.interpolate_data(propname=throat_viscosity)[cn[:, 0]]
+        mu2 = phase.interpolate_data(propname=throat_viscosity)[cn[:, 1]]
+    # Find g for half of pore 1, throat, and half of pore 2
+    g1 = D1**3 / (12*mu1*L1)
+    g2 = D2**3 / (12*mu2*L2)
+    gt = Dt**3 / (12*mut*Lt)
+
+    return (1/gt/SFt + 1/g1/SF1 + 1/g2/SF2)**(-1)
diff --git a/openpnm/models/physics/poisson_shape_factors.py b/openpnm/models/physics/poisson_shape_factors.py
index 921e35262..97be46672 100644
--- a/openpnm/models/physics/poisson_shape_factors.py
+++ b/openpnm/models/physics/poisson_shape_factors.py
@@ -8,6 +8,7 @@ r"""
 
 from scipy import pi as _pi
 from scipy import arctanh as _atanh
+from scipy import sqrt as _sqrt
 import scipy as _sp
 import numpy as _np
 
@@ -181,3 +182,85 @@ def conical_frustum_and_stick(target, pore_area='pore.area',
     SFt[mt] = (Lt / (At*Ft))[mt]
     _np.warnings.filterwarnings('default', category=RuntimeWarning)
     return {'pore1': SF1, 'throat': SFt, 'pore2': SF2}
+
+
+def ball_and_stick_2D(target, pore_area='pore.area',
+                      throat_area='throat.area',
+                      pore_diameter='pore.diameter',
+                      throat_diameter='throat.diameter',
+                      conduit_lengths='throat.conduit_lengths'):
+    r"""
+    Calculate conduit shape factors for throat conductance associated with
+    diffusion-like physics (ex. thermal/diffusive/electrical conductance),
+    assuming pores and throats are circles (balls) and rectangles (sticks).
+
+    Parameters
+    ----------
+    target : OpenPNM Object
+        The object which this model is associated with. This controls the
+        length of the calculated array, and also provides access to other
+        necessary properties.
+
+    pore_area : string
+        Dictionary key of the pore area values
+
+    throat_area : string
+        Dictionary key of the throat area values
+
+    pore_diameter : string
+        Dictionary key of the pore diameter values
+
+    throat_diameter : string
+        Dictionary key of the throat diameter values
+
+    conduit_lengths : string
+        Dictionary key of the conduit lengths' values
+
+    Returns
+    -------
+    SF : dictionary
+        Dictionary containing conduit shape factors to be used in conductance
+        models associated with diffusion-like physics. Shape factors are
+        accessible via the keys: 'pore1', 'pore2' and 'throat'.
+
+    Notes
+    -----
+    (1) This model accounts for the variable cross-section area in circles.
+
+    (2) WARNING: This model could break if `conduit_lengths` does not
+    correspond to an actual ball and stick! Example: pore length is greater
+    than pore radius --> :(
+
+    """
+    _np.warnings.filterwarnings('ignore', category=RuntimeWarning)
+    network = target.project.network
+    throats = network.map_throats(throats=target.Ts, origin=target)
+    cn = network['throat.conns'][throats]
+    # Get pore diameter
+    D1 = network[pore_diameter][cn[:, 0]]
+    D2 = network[pore_diameter][cn[:, 1]]
+    # Get conduit lengths
+    L1 = network[conduit_lengths + '.pore1'][throats]
+    L2 = network[conduit_lengths + '.pore2'][throats]
+    Lt = network[conduit_lengths + '.throat'][throats]
+    # Get pore/throat baseline areas (the one used in generic conductance)
+    A1 = network[pore_area][cn[:, 0]]
+    A2 = network[pore_area][cn[:, 1]]
+    At = network[throat_area][throats]
+    # Preallocating F, SF
+    # F is INTEGRAL(1/A) dx , x : 0 --> L
+    F1, F2, Ft = _sp.zeros((3, len(Lt)))
+    SF1, SF2, SFt = _sp.ones((3, len(Lt)))
+    # Setting SF to 1 when Li = 0 (ex. boundary pores)
+    # INFO: This is needed since area could also be zero, which confuses NumPy
+    m1, m2, mt = [Li != 0 for Li in [L1, L2, Lt]]
+    SF1[~m1] = SF2[~m2] = SFt[~mt] = 1
+    F1[m1] = (0.5 * _atanh(2*L1/_sqrt(D1**2 - 4*L1**2)))[m1]
+    F2[m2] = (0.5 * _atanh(2*L2/_sqrt(D2**2 - 4*L2**2)))[m2]
+    Ft[mt] = (Lt/At)[mt]
+    # Calculate conduit shape factors
+    SF1[m1] = (L1 / (A1*F1))[m1]
+    SF2[m2] = (L2 / (A2*F2))[m2]
+    SFt[mt] = (Lt / (At*Ft))[mt]
+    _np.warnings.filterwarnings('default', category=RuntimeWarning)
+    return {'pore1': SF1, 'throat': SFt, 'pore2': SF2}
diff --git a/openpnm/physics/Standard_2D.py b/openpnm/physics/Standard_2D.py
new file mode 100644
index 000000000..c3ac0ce84
--- /dev/null
+++ b/openpnm/physics/Standard_2D.py
@@ -0,0 +1,62 @@
+from openpnm.utils import Workspace, logging
+from openpnm.physics import GenericPhysics
+from openpnm.models import physics as mods
+logger = logging.getLogger(__name__)
+ws = Workspace()
+
+
+class Standard_2D(GenericPhysics):
+    r"""
+    Generic class to generate Physics objects for true 2D simulations.
+
+    Parameters
+    ----------
+    network : OpenPNM Network object
+        The network to which this Physics should be attached
+
+    phase : OpenPNM Phase object
+        The Phase object to which this Physics applies
+
+    geometry : OpenPNM Geometry object
+        The Geometry object that defines the pores/throats where this Physics
+        should be applied.
+
+    name : str, optional
+        A unique string name to identify the Physics object, typically same as
+        instance name but can be anything.  If left blank, and name will be
+        generated that include the class name and a random string.
+
+    """
+
+    def __init__(self, project=None, network=None, phase=None,
+                 geometry=None, settings={}, **kwargs):
+
+        # Define some default settings
+        self.settings.update({'prefix': 'phys'})
+        # Overwrite with user supplied settings, if any
+        self.settings.update(settings)
+
+        # Deal with network or project arguments
+        if network is not None:
+            if project is not None:
+                assert network is project.network
+            else:
+                project = network.project
+
+        super().__init__(project=project, phase=phase, geometry=geometry,
+                         **kwargs)
+
+        self.add_model(propname='throat.flow_shape_factors',
+                       model=mods.flow_shape_factors.ball_and_stick_2D)
+        self.add_model(propname='throat.hydraulic_conductance',
+                       model=mods.hydraulic_conductance.hagen_poiseuille_2D)
+        self.add_model(propname='throat.poisson_shape_factors',
+                       model=mods.poisson_shape_factors.ball_and_stick_2D)
+        self.add_model(propname='throat.diffusive_conductance',
+                       model=mods.diffusive_conductance.ordinary_diffusion)
+        self.add_model(propname='throat.entry_pressure',
+                       model=mods.capillary_pressure.washburn)
+        self.add_model(propname='throat.thermal_conductance',
+                       model=mods.thermal_conductance.series_resistors)
+        self.add_model(propname='throat.electrical_conductance',
+                       model=mods.electrical_conductance.series_resistors)
diff --git a/openpnm/physics/__init__.py b/openpnm/physics/__init__.py
index b02ef8871..d1ad3ce6b 100644
--- a/openpnm/physics/__init__.py
+++ b/openpnm/physics/__init__.py
@@ -25,4 +25,5 @@ classes that include common pore-scale physical models
 
 from .GenericPhysics import GenericPhysics
 from .Standard import Standard
+from .Standard_2D import Standard_2D
 from .Classic import Classic
diff --git a/openpnm/utils/misc.py b/openpnm/utils/misc.py
index aef3b9258..f62da57e1 100644
--- a/openpnm/utils/misc.py
+++ b/openpnm/utils/misc.py
@@ -223,7 +223,7 @@ def toc(quiet=False):
     if '_startTime_for_tictoc' in globals():
         t = _time.time() - _startTime_for_tictoc
         if quiet is False:
-            print('Elapsed time in seconds: ', t)
+            print(f'Elapsed time in seconds: {t:0.2f}')
         else:
             return t
     else:
diff --git a/setup.cfg b/setup.cfg
index b311a7ae7..0f8cdcce9 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -1,13 +1,13 @@
 [flake8]
-ignore = E127,E222,E226,E225,E241,E402
+ignore = E127,E222,E226,E225,E241,E402,W503
 max-line-length = 90
 
 [pycodestyle]
-ignore = E127,E222,E226,E225,E241,E402
+ignore = E127,E222,E226,E225,E241,E402,W503
 max-line-length = 90
 
 [pep8]
-ignore = E127,E222,E226,E225,E241,E402
+ignore = E127,E222,E226,E225,E241,E402,W503
 max-line-length = 90
 
 [pep8_pre_commit_hook]


Test patch:
diff --git a/pytest.ini b/pytest.ini
index 22478befa..3c17612a0 100644
--- a/pytest.ini
+++ b/pytest.ini
@@ -11,6 +11,7 @@ pep8ignore =
     E225    # missing whitespace around operator
     E241    # multiple spaces after ','
     E402    # module level import not at top of file
+    W503    # line break after operator
     openpnm/topotools/topotools.py ALL
 
 addopts =
diff --git a/tests/unit/algorithms/SubclassedTransportTest.py b/tests/unit/algorithms/SubclassedTransportTest.py
index eebde86f0..6860e19a9 100644
--- a/tests/unit/algorithms/SubclassedTransportTest.py
+++ b/tests/unit/algorithms/SubclassedTransportTest.py
@@ -13,7 +13,6 @@ class SubclassedTransportTest:
         self.phys = op.physics.GenericPhysics(network=self.net,
                                               phase=self.phase,
                                               geometry=self.geo)
-        self.phase['pore.viscosity'] = 1e-3
 
     def test_fickian_diffusion(self):
         alg = op.algorithms.FickianDiffusion(network=self.net,
@@ -24,11 +23,13 @@ class SubclassedTransportTest:
         alg.set_value_BC(pores=Pin, values=1)
         alg.set_value_BC(pores=Pout, values=0)
         alg.run()
-        Deff = alg.calc_effective_diffusivity()
-        Deff = alg.calc_effective_diffusivity(domain_area=81, domain_length=9)
-        Deff = alg.calc_effective_diffusivity(domain_area=81, domain_length=9,
-                                              inlets=Pin, outlets=Pout)
-        assert_allclose(Deff, 7.282894736)
+        rate_in = alg.rate(pores=Pin)[0]
+        rate_out = alg.rate(pores=Pout)[0]
+        rate_total = alg.rate(self.net.pores("surface"))[0]
+        assert_allclose(rate_in, 10.125)
+        assert_allclose(rate_in, -rate_out)
+        assert rate_in > 0
+        assert abs(rate_total) < rate_in * 1e-14
 
     def test_stokes_flow(self):
         alg = op.algorithms.StokesFlow(network=self.net, phase=self.phase)
@@ -38,11 +39,13 @@ class SubclassedTransportTest:
         alg.set_value_BC(pores=Pin, values=101325)
         alg.set_value_BC(pores=Pout, values=0)
         alg.run()
-        Keff = alg.calc_effective_permeability()
-        Keff = alg.calc_effective_permeability(domain_area=81, domain_length=9)
-        Keff = alg.calc_effective_permeability(domain_area=81, domain_length=9,
-                                               inlets=Pin, outlets=Pout)
-        assert_allclose(Keff, 0.0072828947)
+        rate_in = alg.rate(pores=Pin)[0]
+        rate_out = alg.rate(pores=Pout)[0]
+        rate_total = alg.rate(self.net.pores("surface"))[0]
+        assert_allclose(rate_in, 1025915.625)
+        assert_allclose(rate_in, -rate_out)
+        assert rate_in > 0
+        assert abs(rate_total) < rate_in * 1e-14
 
     def test_forurier_conduction(self):
         alg = op.algorithms.FourierConduction(network=self.net,
@@ -50,28 +53,32 @@ class SubclassedTransportTest:
         self.phys['throat.thermal_conductance'] = 1
         Pin = self.net.pores('top')
         Pout = self.net.pores('bottom')
-        alg.set_value_BC(pores=Pin, values=101325)
+        alg.set_value_BC(pores=Pin, values=273)
         alg.set_value_BC(pores=Pout, values=0)
         alg.run()
-        Keff = alg.calc_effective_conductivity()
-        Keff = alg.calc_effective_conductivity(domain_area=81, domain_length=9)
-        Keff = alg.calc_effective_conductivity(domain_area=81, domain_length=9,
-                                               inlets=Pin, outlets=Pout)
-        assert_allclose(Keff, 7.282894736)
+        rate_in = alg.rate(pores=Pin)[0]
+        rate_out = alg.rate(pores=Pout)[0]
+        rate_total = alg.rate(self.net.pores("surface"))[0]
+        assert_allclose(rate_in, 2764.125)
+        assert_allclose(rate_in, -rate_out)
+        assert rate_in > 0
+        assert abs(rate_total) < rate_in * 1e-14
 
     def test_ohmic_conduction(self):
         alg = op.algorithms.OhmicConduction(network=self.net, phase=self.phase)
         self.phys['throat.electrical_conductance'] = 1
         Pin = self.net.pores('top')
         Pout = self.net.pores('bottom')
-        alg.set_value_BC(pores=Pin, values=101325)
+        alg.set_value_BC(pores=Pin, values=1)
         alg.set_value_BC(pores=Pout, values=0)
         alg.run()
-        Keff = alg.calc_effective_conductivity()
-        Keff = alg.calc_effective_conductivity(domain_area=81, domain_length=9)
-        Keff = alg.calc_effective_conductivity(domain_area=81, domain_length=9,
-                                               inlets=Pin, outlets=Pout)
-        assert_allclose(Keff, 7.282894736)
+        rate_in = alg.rate(pores=Pin)[0]
+        rate_out = alg.rate(pores=Pout)[0]
+        rate_total = alg.rate(self.net.pores("surface"))[0]
+        assert_allclose(rate_in, 10.125)
+        assert_allclose(rate_in, -rate_out)
+        assert rate_in > 0
+        assert abs(rate_total) < rate_in * 1e-14
 
     def teardown_class(self):
         ws = op.Workspace()
diff --git a/tests/unit/models/geometry/PoreAreaTest.py b/tests/unit/models/geometry/PoreAreaTest.py
index 733017c57..ab9d849b8 100644
--- a/tests/unit/models/geometry/PoreAreaTest.py
+++ b/tests/unit/models/geometry/PoreAreaTest.py
@@ -33,6 +33,22 @@ class PoreAreaTest:
         b = np.unique(self.geo['pore.area'])
         assert_approx_equal(a, b)
 
+    def test_circle(self):
+        self.geo.add_model(propname='pore.area',
+                           model=mods.circle,
+                           regen_mode='normal')
+        a = np.array([1.0])
+        b = np.unique(self.geo['pore.area'])
+        assert_approx_equal(a, b)
+
+    def test_square(self):
+        self.geo.add_model(propname='pore.area',
+                           model=mods.square,
+                           regen_mode='normal')
+        a = np.array([1.0])
+        b = np.unique(self.geo['pore.area'])
+        assert_approx_equal(a, b)
+
 
 if __name__ == '__main__':
 
diff --git a/tests/unit/models/geometry/PoreSurfaceAreaTest.py b/tests/unit/models/geometry/PoreSurfaceAreaTest.py
index b963828ff..9bb7275e5 100644
--- a/tests/unit/models/geometry/PoreSurfaceAreaTest.py
+++ b/tests/unit/models/geometry/PoreSurfaceAreaTest.py
@@ -1,7 +1,7 @@
 import openpnm as op
 import openpnm.models.geometry.pore_surface_area as mods
 import numpy as np
-from numpy.testing import assert_array_almost_equal_nulp
+from numpy.testing import assert_allclose
 
 
 class PoreSurfaceAreaTest:
@@ -18,21 +18,36 @@ class PoreSurfaceAreaTest:
         self.geo['throat.area'] = 0.1
 
     def test_sphere(self):
-        self.geo.add_model(propname='pore.area',
+        self.geo.add_model(propname='pore.surface_area',
                            model=mods.sphere,
                            regen_mode='normal')
         a = np.array([2.54159265, 2.64159265, 2.74159265, 2.84159265])
-        b = np.unique(self.geo['pore.area'])
-        assert_array_almost_equal_nulp(a, b, nulp=1e7)
-        # nulp = 1e7 means 16-7 = 9 significant figures accurate.
+        b = np.unique(self.geo['pore.surface_area'])
+        assert_allclose(a, b)
+
+    def test_circle(self):
+        self.geo.add_model(propname='pore.surface_area',
+                           model=mods.circle,
+                           regen_mode='normal')
+        a = np.array([2.54159265, 2.64159265, 2.74159265, 2.84159265])
+        b = np.unique(self.geo['pore.surface_area'])
+        assert_allclose(a, b)
 
     def test_cube(self):
-        self.geo.add_model(propname='pore.area',
+        self.geo.add_model(propname='pore.surface_area',
                            model=mods.cube,
                            regen_mode='normal')
         a = np.array([5.4, 5.5, 5.6, 5.7])
-        b = np.unique(self.geo['pore.area'])
-        assert_array_almost_equal_nulp(a, b)
+        b = np.unique(self.geo['pore.surface_area'])
+        assert_allclose(a, b)
+
+    def test_square(self):
+        self.geo.add_model(propname='pore.surface_area',
+                           model=mods.square,
+                           regen_mode='normal')
+        a = np.array([3.4, 3.5, 3.6, 3.7])
+        b = np.unique(self.geo['pore.surface_area'])
+        assert_allclose(a, b)
 
 
 if __name__ == '__main__':
diff --git a/tests/unit/models/geometry/PoreVolumeTest.py b/tests/unit/models/geometry/PoreVolumeTest.py
index 12a2384fa..fdd446e89 100644
--- a/tests/unit/models/geometry/PoreVolumeTest.py
+++ b/tests/unit/models/geometry/PoreVolumeTest.py
@@ -14,14 +14,22 @@ class PoreVolumeTest:
         self.phys = op.physics.GenericPhysics(network=self.net,
                                               phase=self.air,
                                               geometry=self.geo)
-        self.geo['pore.diameter'] = 1.0
+        self.geo['pore.diameter'] = 1.05
         self.geo['throat.area'] = 0.1
 
     def test_sphere(self):
         self.geo.add_model(propname='pore.volume',
                            model=mods.sphere,
                            regen_mode='normal')
-        a = np.array([0.52359878])
+        a = np.array([0.52359878*1.05**3])
+        b = np.unique(self.geo['pore.volume'])
+        assert_approx_equal(a, b)
+
+    def test_circle(self):
+        self.geo.add_model(propname='pore.volume',
+                           model=mods.circle,
+                           regen_mode='normal')
+        a = np.array([3.14159265/4*1.05**2])
         b = np.unique(self.geo['pore.volume'])
         assert_approx_equal(a, b)
 
@@ -29,7 +37,15 @@ class PoreVolumeTest:
         self.geo.add_model(propname='pore.volume',
                            model=mods.cube,
                            regen_mode='normal')
-        a = np.array([1.0])
+        a = np.array([1.0*1.05**3])
+        b = np.unique(self.geo['pore.volume'])
+        assert_approx_equal(a, b)
+
+    def test_square(self):
+        self.geo.add_model(propname='pore.volume',
+                           model=mods.square,
+                           regen_mode='normal')
+        a = np.array([1.0*1.05**2])
         b = np.unique(self.geo['pore.volume'])
         assert_approx_equal(a, b)
 
diff --git a/tests/unit/models/geometry/ThroatAreaTest.py b/tests/unit/models/geometry/ThroatAreaTest.py
index 23c4fc4a8..32accff9a 100644
--- a/tests/unit/models/geometry/ThroatAreaTest.py
+++ b/tests/unit/models/geometry/ThroatAreaTest.py
@@ -34,6 +34,14 @@ class ThroatSurfaceAreaTest:
         b = np.unique(self.geo['throat.area'])
         assert_approx_equal(a, b)
 
+    def test_rectangle(self):
+        self.geo.add_model(propname='throat.area',
+                           model=mods.rectangle,
+                           regen_mode='normal')
+        a = np.array([0.1])
+        b = np.unique(self.geo['throat.area'])
+        assert_approx_equal(a, b)
+
 
 if __name__ == '__main__':
 
diff --git a/tests/unit/models/geometry/ThroatEndpointsTest.py b/tests/unit/models/geometry/ThroatEndpointsTest.py
index 178b2e184..2ea16567e 100644
--- a/tests/unit/models/geometry/ThroatEndpointsTest.py
+++ b/tests/unit/models/geometry/ThroatEndpointsTest.py
@@ -82,6 +82,27 @@ class ThroatEndpointsTest:
         assert_allclose(EP1, desired=EP1d)
         assert_allclose(EP2, desired=EP2d)
 
+    def test_square_pores(self):
+        self.geo['pore.diameter'] = 0.5
+        self.geo['throat.diameter'] = 0.25
+        self.geo.add_model(propname='throat.endpoints',
+                           model=mods.square_pores,
+                           regen_mode='normal')
+        # Throat 1->2
+        EP1 = self.geo['throat.endpoints.head'][0]
+        EP2 = self.geo['throat.endpoints.tail'][0]
+        EP1d = np.array([0, 0.25, 0]) + self.base
+        EP2d = np.array([0, 1 - 0.25, 0]) + self.base
+        assert_allclose(EP1, desired=EP1d)
+        assert_allclose(EP2, desired=EP2d)
+        # Throat 2->3
+        EP1 = self.geo['throat.endpoints.head'][1]
+        EP2 = self.geo['throat.endpoints.tail'][1]
+        EP1d = np.array([0, 1 + 0.25, 0]) + self.base
+        EP2d = np.array([0, 2 - 0.25, 0]) + self.base
+        assert_allclose(EP1, desired=EP1d)
+        assert_allclose(EP2, desired=EP2d)
+
     def test_spherical_pores_with_apparent_overlap(self):
         # Apparent overlap means pores are overlapping, but since throat
         # diameter is large enough, it encompasses the pores' intersection area
diff --git a/tests/unit/models/geometry/ThroatPerimeterTest.py b/tests/unit/models/geometry/ThroatPerimeterTest.py
index 6b73985cc..fc801a1c8 100644
--- a/tests/unit/models/geometry/ThroatPerimeterTest.py
+++ b/tests/unit/models/geometry/ThroatPerimeterTest.py
@@ -24,7 +24,7 @@ class ThroatPerimeterTest:
         b = np.unique(self.geo['throat.perimeter'])
         assert_approx_equal(a, b)
 
-    def test_cube(self):
+    def test_cuboid(self):
         self.geo.add_model(propname='throat.perimeter',
                            model=mods.cuboid,
                            regen_mode='normal')
@@ -32,6 +32,14 @@ class ThroatPerimeterTest:
         b = np.unique(self.geo['throat.perimeter'])
         assert_approx_equal(a, b)
 
+    def test_rectangle(self):
+        self.geo.add_model(propname='throat.perimeter',
+                           model=mods.rectangle,
+                           regen_mode='normal')
+        a = np.array([1.0])
+        b = np.unique(self.geo['throat.perimeter'])
+        assert_approx_equal(a, b)
+
 
 if __name__ == '__main__':
 
diff --git a/tests/unit/models/geometry/ThroatSurfaceAreaTest.py b/tests/unit/models/geometry/ThroatSurfaceAreaTest.py
index fdbe327f8..3db15180f 100644
--- a/tests/unit/models/geometry/ThroatSurfaceAreaTest.py
+++ b/tests/unit/models/geometry/ThroatSurfaceAreaTest.py
@@ -1,4 +1,6 @@
 import openpnm as op
+import openpnm.models.geometry.throat_surface_area as tsa
+from numpy.testing import assert_allclose
 import scipy as sp
 
 
@@ -13,25 +15,24 @@ class ThroatSurfaceAreaTest:
         self.geo['throat.perimeter'] = sp.ones((self.geo.Nt, ))
 
     def test_cylinder(self):
-        f = op.models.geometry.throat_surface_area.cylinder
         self.geo.add_model(propname='throat.surface_area',
-                           model=f)
-        self.geo.regenerate_models()
-        assert sp.all(self.geo['throat.surface_area'] == sp.pi)
+                           model=tsa.cylinder, regen_mode="normal")
+        assert_allclose(self.geo['throat.surface_area'].mean(), sp.pi)
 
     def test_cuboid(self):
-        f = op.models.geometry.throat_surface_area.cuboid
         self.geo.add_model(propname='throat.surface_area',
-                           model=f,)
-        self.geo.regenerate_models()
-        assert sp.all(self.geo['throat.surface_area'] == 4)
+                           model=tsa.cuboid, regen_mode="normal")
+        assert_allclose(self.geo['throat.surface_area'].mean(), 4)
+
+    def test_rectangle(self):
+        self.geo.add_model(propname='throat.surface_area',
+                            model=tsa.rectangle, regen_mode="normal")
+        assert_allclose(self.geo['throat.surface_area'].mean(), 2)
 
     def test_extrusion(self):
-        f = op.models.geometry.throat_surface_area.extrusion
         self.geo.add_model(propname='throat.surface_area',
-                           model=f)
-        self.geo.regenerate_models()
-        assert sp.all(self.geo['throat.surface_area'] == 1)
+                           model=tsa.extrusion, regen_mode="normal")
+        assert_allclose(self.geo['throat.surface_area'].mean(), 1)
 
 
 if __name__ == '__main__':
diff --git a/tests/unit/models/geometry/ThroatVolumeTest.py b/tests/unit/models/geometry/ThroatVolumeTest.py
index 72cad4da0..51f6263e1 100644
--- a/tests/unit/models/geometry/ThroatVolumeTest.py
+++ b/tests/unit/models/geometry/ThroatVolumeTest.py
@@ -33,6 +33,13 @@ class ThroatVolumeTest:
         b = np.unique(self.geo['throat.volume'])
         assert_approx_equal(a, b)
 
+    def test_rectangle(self):
+        self.geo.add_model(propname='throat.volume',
+                           model=mods.rectangle)
+        a = np.array([0.1])
+        b = np.unique(self.geo['throat.volume'])
+        assert_approx_equal(a, b)
+
     def test_extrusion(self):
         self.geo.add_model(propname='throat.volume',
                            throat_area='throat.area',
diff --git a/tests/unit/models/physics/HydraulicConductanceTest.py b/tests/unit/models/physics/HydraulicConductanceTest.py
index 120bbc5ea..b6808c054 100644
--- a/tests/unit/models/physics/HydraulicConductanceTest.py
+++ b/tests/unit/models/physics/HydraulicConductanceTest.py
@@ -10,6 +10,8 @@ class HydraulicConductanceTest:
         self.geo = op.geometry.GenericGeometry(network=self.net,
                                                pores=self.net.Ps,
                                                throats=self.net.Ts)
+        self.geo['pore.diameter'] = 1.0
+        self.geo['throat.diameter'] = 0.5
         self.geo['pore.area'] = 1.0
         self.geo['throat.area'] = 0.5
         self.phase = op.phases.GenericPhase(network=self.net)
@@ -27,11 +29,19 @@ class HydraulicConductanceTest:
         self.geo['throat.conduit_lengths.throat'] = 0.6
         self.geo['throat.conduit_lengths.pore2'] = 0.15
         mod = op.models.physics.hydraulic_conductance.hagen_poiseuille
-        self.phys.add_model(propname='throat.hydraulic_conductance',
-                            model=mod)
+        self.phys.add_model(propname='throat.hydraulic_conductance', model=mod)
         actual = self.phys['throat.hydraulic_conductance'].mean()
         assert_approx_equal(actual, desired=1421.0262776)
 
+    def test_hagen_poiseuille_2D(self):
+        self.geo['throat.conduit_lengths.pore1'] = 0.25
+        self.geo['throat.conduit_lengths.throat'] = 0.6
+        self.geo['throat.conduit_lengths.pore2'] = 0.15
+        mod = op.models.physics.hydraulic_conductance.hagen_poiseuille_2D
+        self.phys.add_model(propname='throat.hydraulic_conductance', model=mod)
+        actual = self.phys['throat.hydraulic_conductance'].mean()
+        assert_approx_equal(actual, desired=1602.564)
+
     def test_classic_hagen_poiseuille(self):
         self.geo['pore.diameter'] = 1.0
         self.geo['throat.diameter'] = 1.0