Skip to content

Commit 1546fa1

Browse files
authored
[Hotfix] Fixing handling of throat.centroid for overlapping but non-aligned pores
2 parents a1ea84e + 35f1f7b commit 1546fa1

File tree

9 files changed

+75
-62
lines changed

9 files changed

+75
-62
lines changed

VERSIONING.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
## Release Management and Versioning
22

3-
OpenPNM uses [Semantic Versioning](http://semver.org) (i.e. X.Y.Z) to label releases. All major and minor versions (X.Y.z) are available on [PyPI](https://pypi.python.org/pypi), but bugfix releases (x.y.Z) are not generally pushed unless the bug is important. Bubfix releases are available via download of the source code from Github.
3+
`OpenPNM` uses [Semantic Versioning](http://semver.org) (i.e. X.Y.Z) to label releases. All major and minor versions (X.Y.z) are available on [PyPI](https://pypi.python.org/pypi), but bugfix releases (x.y.Z) are not generally pushed unless the bug is important. Bugfix releases are available via download of the source code from Github.
44

5-
OpenPNM uses the [Github Flow](https://guides.github.com/introduction/flow/) system of Git branching, except instead of merging PRs into *master*, they are merged into a branch called *dev*. Any code added to *dev* is done via Pull Requests (PRs). When new PRs are merged into the *dev* branch, they are *not* given a new version number. Once enough new features have been added, the *dev* branch is merged into the *master* branch, and the minor release number (x.Y.z) will be incremented. An exception to this rule are bugfixes which may be found on *master*. In these cases a PR can be merged into *master* and the version number will be incremented (x.y.Z) to indicate the fix.
5+
`OpenPNM` uses the [Github Flow](https://guides.github.com/introduction/flow/) system of Git branching, except instead of merging PRs into *master*, they are merged into a branch called *dev*. Any code added to *dev* is done via Pull Requests (PRs). When new PRs are merged into the *dev* branch, they are *not* given a new version number. Once enough new features have been added, the *dev* branch is merged into the *master* branch, and the minor release number (x.Y.z) will be incremented. An exception to this rule are bugfixes which may be found on *master*. In these cases a PR can be merged into *master* and the version number will be incremented (x.y.Z) to indicate the fix.
66

7-
OpenPNM depends on several other packages widely known as the [Scipy Stack](https://www.scipy.org/stackspec.html). It is our policy to always support the latest version of all these packages and their dependencies.
7+
`OpenPNM` depends on several other packages widely known as the [Scipy Stack](https://www.scipy.org/stackspec.html). It is our policy to always support the latest version of all these packages and their dependencies.

openpnm/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@
5252
5353
"""
5454

55-
__version__ = '2.3.0'
55+
__version__ = '2.3.1'
5656

5757
import numpy as np
5858
np.seterr(divide='ignore', invalid='ignore')

openpnm/algorithms/GenericTransport.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -706,7 +706,7 @@ def _check_for_nans(self):
706706
if root_props:
707707
msg = (
708708
f"Found NaNs in A matrix, possibly caused by NaNs in "
709-
f"{', '.join(root_props)} \n{'-' * 80}\nThe issue might get "
709+
f"{', '.join(root_props)}. The issue might get "
710710
f"resolved if you call regenerate_models on the following "
711711
f"object(s): {', '.join(root_objs)}"
712712
)

openpnm/models/geometry/throat_endpoints.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -113,11 +113,11 @@ def spherical_pores(target, pore_diameter='pore.diameter',
113113
Notes
114114
-----
115115
(1) This model should not be applied to true 2D networks. Use
116-
`circular_pores` model instead.
116+
``circular_pores`` model instead.
117117
118118
(2) By default, this model assumes that throat centroid and pore
119119
coordinates are colinear. If that's not the case, such as in extracted
120-
networks, `throat_centroid` could be passed as an optional argument, and
120+
networks, ``throat_centroid`` could be passed as an optional argument, and
121121
the model takes care of the rest.
122122
123123
"""
@@ -153,11 +153,12 @@ def spherical_pores(target, pore_diameter='pore.diameter',
153153
EP2 = xyz[cn[:, 1]] + L2[:, None] * unit_vec_P2T
154154
# Handle throats w/ overlapping pores
155155
L1 = (4*L**2 + D1**2 - D2**2) / (8*L)
156-
# L2 = (4*L**2 + D2**2 - D1**2) / (8*L)
156+
L2 = (4*L**2 + D2**2 - D1**2) / (8*L)
157157
h = (2*_np.sqrt(D1**2/4 - L1**2)).real
158158
overlap = L - 0.5 * (D1+D2) < 0
159159
mask = overlap & (Dt < h)
160-
EP1[mask] = EP2[mask] = (xyz[cn[:, 0]] + L1[:, None] * unit_vec_P1T)[mask]
160+
EP1[mask] = (xyz[cn[:, 0]] + L1[:, None] * unit_vec_P1T)[mask]
161+
EP2[mask] = (xyz[cn[:, 1]] + L2[:, None] * unit_vec_P2T)[mask]
161162
return {'head': EP1, 'tail': EP2}
162163

163164

openpnm/models/geometry/throat_length.py

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
77
"""
88
import numpy as _np
9-
from scipy import sqrt as _sqrt
9+
from numpy.linalg import norm as _norm
1010
from openpnm.utils import logging as _logging
1111
_logger = _logging.getLogger(__name__)
1212

@@ -35,7 +35,7 @@ def ctc(target):
3535
cn = network['throat.conns'][throats]
3636
C1 = network['pore.coords'][cn[:, 0]]
3737
C2 = network['pore.coords'][cn[:, 1]]
38-
value = _sqrt(((C1 - C2)**2).sum(axis=1))
38+
value = _norm(C1 - C2, axis=1)
3939
return value
4040

4141

@@ -77,19 +77,19 @@ def piecewise(target, throat_endpoints='throat.endpoints',
7777
EP1 = network[throat_endpoints + '.head'][throats]
7878
EP2 = network[throat_endpoints + '.tail'][throats]
7979
# Calculate throat length
80-
Lt = _sqrt(((EP1 - EP2)**2).sum(axis=1))
80+
Lt = _norm(EP1 - EP2, axis=1)
8181
# Handle the case where pores & throat centroids are not colinear
8282
try:
8383
Ct = network[throat_centroid][throats]
84-
Lt = _sqrt(((Ct - EP1)**2).sum(axis=1)) + \
85-
_sqrt(((Ct - EP2)**2).sum(axis=1))
84+
Lt = _norm(Ct - EP1, axis=1) + _norm(Ct - EP2, axis=1)
8685
except KeyError:
8786
pass
8887
return Lt
8988

9089

9190
def conduit_lengths(target, throat_endpoints='throat.endpoints',
92-
throat_length='throat.length'):
91+
throat_length='throat.length',
92+
throat_centroid='throat.centroid'):
9393
r"""
9494
Calculate conduit lengths. A conduit is defined as half pore + throat
9595
+ half pore.
@@ -130,11 +130,11 @@ def conduit_lengths(target, throat_endpoints='throat.endpoints',
130130
# Look up throat length if given
131131
Lt = network[throat_length][throats]
132132
except KeyError:
133-
# Calculate throat length otherwise
134-
Lt = _sqrt(((EP1 - EP2)**2).sum(axis=1))
135-
# Calculate conduit lengths
136-
L1 = _sqrt(((C1 - EP1)**2).sum(axis=1))
137-
L2 = _sqrt(((C2 - EP2)**2).sum(axis=1))
133+
# Calculate throat length otherwise based on piecewise model
134+
Lt = piecewise(target, throat_endpoints, throat_centroid)
135+
# Calculate conduit lengths for pore 1 and pore 2
136+
L1 = _norm(C1 - EP1, axis=1)
137+
L2 = _norm(C2 - EP2, axis=1)
138138
return {'pore1': L1, 'throat': Lt, 'pore2': L2}
139139

140140

@@ -156,8 +156,6 @@ def classic(target, pore_diameter='pore.diameter'):
156156
network = target.project.network
157157
throats = network.map_throats(throats=target.Ts, origin=target)
158158
cn = network['throat.conns'][throats]
159-
C1 = network['pore.coords'][cn[:, 0]]
160-
C2 = network['pore.coords'][cn[:, 1]]
161-
D = _sqrt(((C1 - C2)**2).sum(axis=1))
162-
value = D - _np.sum(network[pore_diameter][cn], axis=1)/2
159+
ctc_dist = ctc(target)
160+
value = ctc_dist - network[pore_diameter][cn].sum(axis=1)/2
163161
return value

openpnm/models/physics/hydraulic_conductance.py

Lines changed: 18 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,17 @@
77
"""
88
import scipy as _sp
99
import numpy as _np
10-
from numpy import pi as _pi
1110

1211

1312
def hagen_poiseuille(
14-
target,
15-
pore_area="pore.area",
16-
throat_area="throat.area",
17-
pore_viscosity="pore.viscosity",
18-
throat_viscosity="throat.viscosity",
19-
conduit_lengths="throat.conduit_lengths",
20-
conduit_shape_factors="throat.flow_shape_factors"):
13+
target,
14+
pore_area="pore.area",
15+
throat_area="throat.area",
16+
pore_viscosity="pore.viscosity",
17+
throat_viscosity="throat.viscosity",
18+
conduit_lengths="throat.conduit_lengths",
19+
conduit_shape_factors="throat.flow_shape_factors"
20+
):
2121
r"""
2222
Calculate the hydraulic conductance of conduits in network, where a
2323
conduit is ( 1/2 pore - full throat - 1/2 pore ). See the notes section.
@@ -94,24 +94,14 @@ def hagen_poiseuille(
9494
SF2 = phase[conduit_shape_factors + ".pore2"][throats]
9595
except KeyError:
9696
SF1 = SF2 = SFt = 1.0
97-
# Interpolate pore phase property values to throats
98-
try:
99-
Dt = phase[throat_viscosity][throats]
100-
except KeyError:
101-
Dt = phase.interpolate_data(propname=pore_viscosity)[throats]
102-
try:
103-
D1 = phase[pore_viscosity][cn[:, 0]]
104-
D2 = phase[pore_viscosity][cn[:, 1]]
105-
except KeyError:
106-
D1 = phase.interpolate_data(propname=throat_viscosity)[cn[:, 0]]
107-
D2 = phase.interpolate_data(propname=throat_viscosity)[cn[:, 1]]
97+
Dt = phase[throat_viscosity][throats]
98+
D1, D2 = phase[pore_viscosity][cn].T
10899
# Find g for half of pore 1, throat, and half of pore 2
109-
pi = _sp.pi
110-
g1[m1] = A1[m1] ** 2 / (8 * pi * D1 * L1)[m1]
111-
g2[m2] = A2[m2] ** 2 / (8 * pi * D2 * L2)[m2]
112-
gt[mt] = At[mt] ** 2 / (8 * pi * Dt * Lt)[mt]
100+
g1[m1] = A1[m1] ** 2 / (8 * _sp.pi * D1 * L1)[m1]
101+
g2[m2] = A2[m2] ** 2 / (8 * _sp.pi * D2 * L2)[m2]
102+
gt[mt] = At[mt] ** 2 / (8 * _sp.pi * Dt * Lt)[mt]
113103
# Apply shape factors and calculate the final conductance
114-
return (1 / gt / SFt + 1 / g1 / SF1 + 1 / g2 / SF2) ** (-1)
104+
return (1/gt/SFt + 1/g1/SF1 + 1/g2/SF2) ** (-1)
115105

116106

117107
def hagen_poiseuille_2D(
@@ -191,22 +181,14 @@ def hagen_poiseuille_2D(
191181
except KeyError:
192182
SF1 = SF2 = SFt = 1.0
193183
# Getting viscosity values
194-
try:
195-
mut = phase[throat_viscosity][throats]
196-
except KeyError:
197-
mut = phase.interpolate_data(propname=pore_viscosity)[throats]
198-
try:
199-
mu1 = phase[pore_viscosity][cn[:, 0]]
200-
mu2 = phase[pore_viscosity][cn[:, 1]]
201-
except KeyError:
202-
mu1 = phase.interpolate_data(propname=throat_viscosity)[cn[:, 0]]
203-
mu2 = phase.interpolate_data(propname=throat_viscosity)[cn[:, 1]]
184+
mut = phase[throat_viscosity][throats]
185+
mu1, mu2 = phase[pore_viscosity][cn].T
204186
# Find g for half of pore 1, throat, and half of pore 2
205187
g1 = D1 ** 3 / (12 * mu1 * L1)
206188
g2 = D2 ** 3 / (12 * mu2 * L2)
207189
gt = Dt ** 3 / (12 * mut * Lt)
208190

209-
return (1 / gt / SFt + 1 / g1 / SF1 + 1 / g2 / SF2) ** (-1)
191+
return (1/gt/SFt + 1/g1/SF1 + 1/g2/SF2) ** (-1)
210192

211193

212194
def hagen_poiseuille_power_law(
@@ -418,7 +400,7 @@ def hagen_poiseuille_power_law(
418400
gt[mt] = At[mt] ** 2 / ((8 * pi * Lt) * mut)[mt]
419401

420402
# Apply shape factors and calculate the final conductance
421-
return (1 / gt / SFt + 1 / g1 / SF1 + 1 / g2 / SF2) ** (-1)
403+
return (1/gt/SFt + 1/g1/SF1 + 1/g2/SF2) ** (-1)
422404

423405

424406
def valvatne_blunt(

tests/unit/core/ModelsTest.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ def test_models_dict_print(self):
2222
geo = op.geometry.StickAndBall(network=net, pores=net.Ps,
2323
throats=net.Ts)
2424
s = geo.models.__str__().split('\n')
25-
assert len(s) == 69
25+
assert len(s) == 70
2626
assert s.count('―'*85) == 15
2727

2828
def test_regenerate_models(self):

tests/unit/models/geometry/ThroatEndpointsTest.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,23 @@ def test_spherical_pores_with_throat_centroid(self):
6161
assert_allclose(EP2, desired=EP2d)
6262
del self.geo['throat.centroid']
6363

64+
def test_spherical_pores_with_throat_centroid_and_overlap(self):
65+
self.geo['throat.centroid'] = np.array([[0, 0.5, 1],
66+
[0, 1.5, 0]]) + self.base
67+
self.geo['pore.diameter'] = [1.5, 1.0, 0.5]
68+
self.geo['throat.diameter'] = 0.25
69+
self.geo.add_model(propname='throat.endpoints',
70+
model=mods.spherical_pores,
71+
regen_mode='normal')
72+
# Only check throat 1->2, 2->3 is already tested (no-overlap)
73+
EP12_head = self.geo['throat.endpoints.head'][0]
74+
EP12_tail = self.geo['throat.endpoints.tail'][0]
75+
EP12_head_desired = np.array([0, 0.29348392, 0.58696784]) + self.base
76+
EP12_tail_desired = np.array([0, 0.84627033, 0.30745935]) + self.base
77+
assert_allclose(EP12_head, desired=EP12_head_desired)
78+
assert_allclose(EP12_tail, desired=EP12_tail_desired)
79+
del self.geo["throat.centroid"]
80+
6481
def test_cubic_pores(self):
6582
self.geo['pore.diameter'] = 0.5
6683
self.geo['throat.diameter'] = 0.25

tests/unit/models/geometry/ThroatLengthTest.py

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ def test_piecewise_with_centroid(self):
2929
actual = self.geo['throat.length'][0]
3030
assert_approx_equal(actual, desired=1.1216117)
3131

32-
def test_conduit_lengths(self):
32+
def test_conduit_lengths_wo_centroid(self):
33+
del self.geo["throat.centroid"]
3334
self.geo['throat.length'] = 0.5
3435
self.geo.add_model(propname='throat.conduit_lengths',
3536
model=mods.conduit_lengths,
@@ -41,6 +42,20 @@ def test_conduit_lengths(self):
4142
assert_approx_equal(actual=L2, desired=0.3)
4243
assert_approx_equal(actual=Lt, desired=0.5)
4344

45+
def test_conduit_lengths_w_centroid(self):
46+
# Delete "throat.length" key, otherwise, it won't get re-calculated
47+
del self.geo['throat.length']
48+
self.geo["throat.centroid"] = np.array([[0, 0.5, 0.5]]) + self.base
49+
self.geo.add_model(propname='throat.conduit_lengths',
50+
model=mods.conduit_lengths,
51+
regen_mode='normal')
52+
L1 = self.geo['throat.conduit_lengths.pore1'][0]
53+
L2 = self.geo['throat.conduit_lengths.pore2'][0]
54+
Lt = self.geo['throat.conduit_lengths.throat'][0]
55+
assert_approx_equal(actual=L1, desired=0.2)
56+
assert_approx_equal(actual=L2, desired=0.3)
57+
assert_approx_equal(actual=Lt, desired=1.12161167)
58+
4459

4560
if __name__ == '__main__':
4661

0 commit comments

Comments
 (0)