Source code for openpnm.models.geometry.conduit_lengths._funcs

import numpy as np

from openpnm.models.geometry import _geodocs

__all__ = [
    "spheres_and_cylinders",
    "circles_and_rectangles",
    "cones_and_cylinders",
    "intersecting_cones",
    "hybrid_cones_and_cylinders",
    "trapezoids_and_rectangles",
    "hybrid_trapezoids_and_rectangles",
    "intersecting_trapezoids",
    "pyramids_and_cuboids",
    "intersecting_pyramids",
    "hybrid_pyramids_and_cuboids",
    "cubes_and_cuboids",
    "squares_and_rectangles",
]


[docs] @_geodocs def spheres_and_cylinders( network, pore_diameter="pore.diameter", throat_diameter="throat.diameter", ): r""" Calculates conduit lengths in the network assuming pores are spheres and throats are cylinders. A conduit is defined as ( 1/2 pore - full throat - 1/2 pore ). Parameters ---------- %(network)s %(Dp)s %(Dt)s Returns ------- lengths : ndarray Array (Nt by 3) containing conduit values for each element of the pore-throat-pore conduits. The array is formatted as ``[pore1, throat, pore2]``. """ L_ctc = _get_L_ctc(network) D1, Dt, D2 = network.get_conduit_data(pore_diameter.split(".", 1)[-1]).T # Handle the case where Dt > Dp if (Dt > D1).any() or (Dt > D2).any(): _raise_incompatible_data() # If spheres do not overlap: L1 = np.sqrt(D1**2 - Dt**2) / 2 L2 = np.sqrt(D2**2 - Dt**2) / 2 Lt = L_ctc - (L1 + L2) if np.any(Lt < 0): # Find pores that touch/overlap # Find diameter of overlap between spheres d = L_ctc R1 = D1/2 R2 = D2/2 # Check distance to the intersection L1_int = (d**2 - R2**2 + R1**2) / (2*d) if np.any(L1_int < 0) or np.any(L1_int > L_ctc): raise Exception('The pores overlap too much') D_int = 2/(2*d) * np.sqrt(4*d**2 * R1**2 - (d**2 - R2**2 + R1**2)**2) mask = D_int > Dt if np.any(mask): d = d[mask] R1 = R1[mask] R2 = R2[mask] L1[mask] = (d**2 - R2**2 + R1**2) / (2*d) L2[mask] = L_ctc[mask] - L1[mask] Lt[mask] = 1e-15 return np.vstack((L1, Lt, L2)).T
[docs] @_geodocs def circles_and_rectangles( network, pore_diameter="pore.diameter", throat_diameter="throat.diameter" ): r""" Calculates conduit lengths in the network assuming pores are circles and throats are rectangles. A conduit is defined as ( 1/2 pore - full throat - 1/2 pore ). Parameters ---------- %(network)s %(Dp)s %(Dt)s Returns ------- Notes ----- This model should only be used for true 2D networks, i.e. with planar symmetry. """ return spheres_and_cylinders( network=network, pore_diameter=pore_diameter, throat_diameter=throat_diameter, )
[docs] @_geodocs def cones_and_cylinders( network, pore_diameter="pore.diameter", throat_diameter="throat.diameter" ): r""" Calculates conduit lengths in the network assuming pores are cones and throats are cylinders. A conduit is defined as ( 1/2 pore - full throat - 1/2 pore ). Parameters ---------- %(network)s %(Dp)s %(Dt)s Returns ------- """ L_ctc = _get_L_ctc(network) D1, Dt, D2 = network.get_conduit_data(pore_diameter.split(".", 1)[-1]).T L1 = D1 / 2 L2 = D2 / 2 # Handle throats w/ overlapping pores _L1 = (4 * L_ctc**2 + D1**2 - D2**2) / (8 * L_ctc) mask = L_ctc - 0.5 * (D1 + D2) < 0 L1[mask] = _L1[mask] L2[mask] = (L_ctc - L1)[mask] Lt = np.maximum(L_ctc - (L1 + L2), 1e-15) return np.vstack((L1, Lt, L2)).T
[docs] @_geodocs def intersecting_cones(network, pore_coords="pore.coords", throat_coords="throat.coords"): r""" Calculates conduit lengths in the network assuming pores are cones that intersect. Therefore, the throat is the cross sectional plane where two pores meet and has negligible/zero volume. A conduit is defined as ( 1/2 pore - 1/2 pore ). Parameters ---------- %(network)s %(Pcoords)s %(Tcoords)s Returns ------- """ P12 = network["throat.conns"] p_coords = network[pore_coords] t_coords = network[throat_coords] L1 = np.sqrt(np.sum(((p_coords[P12[:, 0]] - t_coords)) ** 2, axis=1)) L2 = np.sqrt(np.sum(((p_coords[P12[:, 1]] - t_coords)) ** 2, axis=1)) Lt = np.zeros(len(network.Ts)) return np.vstack((L1, Lt, L2)).T
[docs] @_geodocs def hybrid_cones_and_cylinders( network, pore_diameter="pore.diameter", throat_coords="throat.coords" ): r""" Calculates conduit lengths in the network assuming pores are cones and throats are cylinders. A conduit is defined as ( 1/2 pore - full throat - 1/2 pore ). Parameters ---------- %(network)s %(Dp)s %(Tcoords)s Returns ------- """ L_ctc = _get_L_ctc(network) D1, Dt, D2 = network.get_conduit_data(pore_diameter.split(".", 1)[-1]).T L1 = D1 / 2 L2 = D2 / 2 Lt = np.maximum(L_ctc - (L1 + L2), 1e-15) # Handle intersecting pores XYp = network.coords[network.conns] XYt = network[throat_coords] _L1, _L2 = np.linalg.norm(XYp - XYt[:, None], axis=2).T mask1 = _L1 < L1 mask2 = _L2 < L2 mask = np.logical_or(mask1, mask2) if mask.any(): L1[mask] = _L1[mask] L2[mask] = _L2[mask] Lt[mask] = 0.0 return np.vstack((L1, Lt, L2)).T
[docs] @_geodocs def trapezoids_and_rectangles( network, pore_diameter="pore.diameter", throat_diameter="throat.diameter" ): r""" Calculates conduit lengths in the network assuming pores are trapezoids and throats are rectangles. Parameters ---------- %(network)s %(Dp)s %(Dt)s Returns ------- Notes ----- This model should only be used for true 2D networks, i.e. with planar symmetry. """ return cones_and_cylinders( network=network, pore_diameter=pore_diameter, throat_diameter=throat_diameter, )
[docs] @_geodocs def intersecting_trapezoids( network, pore_coords="pore.coords", throat_coords="throat.coords" ): r""" Calculates conduit lengths in the network assuming pores are intersecting trapezoids. Parameters ---------- %(network)s %(Pcoords)s %(Tcoords)s Returns ------- Notes ----- This model should only be used for true 2D networks, i.e. with planar symmetry. """ return intersecting_cones( network=network, pore_coords=pore_coords, throat_coords=throat_coords, )
[docs] @_geodocs def hybrid_trapezoids_and_rectangles( network, pore_diameter="pore.diameter", throat_coords="throat.coords" ): r""" Calculates conduit lengths in the network assuming pores are trapezoids and throats are rectangles. Parameters ---------- %(network)s %(Dp)s %(Tcoords)s Returns ------- Notes ----- This model should only be used for true 2D networks, i.e. with planar symmetry. """ return hybrid_cones_and_cylinders( network=network, pore_diameter=pore_diameter, throat_coords=throat_coords, )
[docs] @_geodocs def pyramids_and_cuboids( network, pore_diameter="pore.diameter", throat_diameter="throat.diameter" ): r""" Calculates conduit lengths in the network assuming pores are truncated pyramids and throats are cuboids. Parameters ---------- %(network)s %(Dp)s %(Dt)s Returns ------- """ return cones_and_cylinders( network=network, pore_diameter=pore_diameter, throat_diameter=throat_diameter, )
[docs] @_geodocs def intersecting_pyramids( network, pore_coords="pore.coords", throat_coords="throat.coords" ): r""" Calculates conduit lengths in the network assuming pores are intersecting pyramids. Parameters ---------- %(network)s %(Pcoords)s %(Tcoords)s Returns ------- """ return intersecting_cones( network=network, pore_coords=pore_coords, throat_coords=throat_coords, )
[docs] @_geodocs def hybrid_pyramids_and_cuboids( network, pore_diameter="pore.diameter", throat_coords="throat.coords" ): r""" Calculates conduit lengths in the network assuming pores are truncated pyramids that intersect. Therefore, the throat is the cross sectional plane where two pores meet and has negligible/zero volume. Parameters ---------- %(network)s %(Dp)s %(Tcoords)s Returns ------- """ return hybrid_cones_and_cylinders( network=network, pore_diameter=pore_diameter, throat_coords=throat_coords, )
[docs] @_geodocs def cubes_and_cuboids( network, pore_diameter="pore.diameter", throat_diameter="throat.diameter" ): r""" Calculates conduit lengths in the network assuming pores are cubes and throats are cuboids. Parameters ---------- %(network)s %(Dp)s %(Dt)s Returns ------- """ L_ctc = _get_L_ctc(network) D1, Dt, D2 = network.get_conduit_data(pore_diameter.split(".", 1)[-1]).T L1 = D1 / 2 L2 = D2 / 2 Lt = L_ctc - (L1 + L2) # Handle overlapping pores mask = (Lt < 0) & (L1 > L2) L2[mask] = (L_ctc - L1)[mask] mask = (Lt < 0) & (L2 > L1) L1[mask] = (L_ctc - L2)[mask] Lt = np.maximum(Lt, 1e-15) return np.vstack((L1, Lt, L2)).T
[docs] @_geodocs def squares_and_rectangles( network, pore_diameter="pore.diameter", throat_diameter="throat.diameter" ): r""" Calculates conduit lengths in the network assuming pores are squares and throats are rectangles. Parameters ---------- %(network)s %(Dp)s %(Dt)s Returns ------- Notes ----- This model should only be used for true 2D networks, i.e. with planar symmetry. """ return cubes_and_cuboids( network=network, pore_diameter=pore_diameter, throat_diameter=throat_diameter, )
# Dealing with errors and exceptions def _raise_incompatible_data(): msg = ( "'spheres_and_cylinders' can only be applied when throat diameter is" " smaller than that of adjacent pores." ) raise Exception(msg) def _get_L_ctc(network): """Returns throat spacing if it exists, otherwise calculates it.""" try: L_ctc = network["throat.spacing"] except KeyError: P12 = network["throat.conns"] C1 = network["pore.coords"][P12[:, 0]] C2 = network["pore.coords"][P12[:, 1]] L_ctc = np.linalg.norm(C1 - C2, axis=1) return L_ctc