Skip to content

Commit e570177

Browse files
committed
Merge pull request #504 from PMEAL/improved_pore_diameter_models
Improved pore diameter models
2 parents b33496f + 22734fb commit e570177

22 files changed

+846
-254
lines changed

OpenPNM/Algorithms/__Drainage__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ class Drainage(GenericAlgorithm):
4343
>>> import OpenPNM as op
4444
>>> pn = op.Network.Cubic(shape=[20, 20, 20], spacing=10)
4545
>>> pn.add_boundary_pores(pores=pn.pores('top'),
46-
... offset=[0, 0, 1],
46+
... offset=[0, 0, 10],
4747
... apply_label='boundary_top')
4848
>>> geo = op.Geometry.Stick_and_Ball(network=pn, pores=pn.Ps, throats=pn.Ts)
4949
>>> water = op.Phases.Water(network=pn)

OpenPNM/Geometry/__Boundary__.py

Lines changed: 10 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -34,48 +34,30 @@ class Boundary(GenericGeometry):
3434
>>> geo = OpenPNM.Geometry.Cube_and_Cuboid(network=pn,
3535
... pores=Ps_int,
3636
... throats=Ts_int)
37-
>>> boun = OpenPNM.Geometry.Boundary(network=pn, pores=Ps_boun, throats=Ts_boun)
37+
>>> boun = OpenPNM.Geometry.Boundary(network=pn, pores=Ps_boun,
38+
... throats=Ts_boun)
3839
3940
"""
4041

4142
def __init__(self, shape='spheres', **kwargs):
4243
super().__init__(**kwargs)
43-
self._generate(shape)
44-
45-
def _generate(self, shape):
46-
try:
47-
self['pore.seed']
48-
seeds = True
49-
except:
50-
seeds = False
51-
52-
if seeds:
53-
self.models.add(propname='pore.seed',
54-
model=gm.pore_misc.constant,
55-
value=0.9999)
56-
self.models.add(propname='pore.diameter',
57-
model=gm.pore_misc.constant,
58-
value=0)
59-
if seeds:
60-
self.models.add(propname='throat.seed',
61-
model=gm.throat_misc.neighbor,
62-
pore_prop='pore.seed',
63-
mode='max')
44+
self['pore.diameter'] = 0
6445
self.models.add(propname='throat.diameter',
6546
model=gm.throat_misc.neighbor,
6647
pore_prop='pore.diameter',
6748
mode='max')
6849
self['pore.volume'] = 0.0
69-
self['pore.seed'] = 0.0
70-
self.models.add(propname='throat.length', model=gm.throat_length.straight)
7150
self['throat.volume'] = 0.0
72-
self['throat.seed'] = 0.0
51+
self.models.add(propname='throat.length',
52+
model=gm.throat_length.straight)
53+
self['pore.area'] = 1.0
7354
if shape == 'spheres':
74-
self.models.add(propname='throat.area', model=gm.throat_area.cylinder)
55+
self.models.add(propname='throat.area',
56+
model=gm.throat_area.cylinder)
7557
self.models.add(propname='throat.surface_area',
7658
model=gm.throat_surface_area.cylinder)
7759
elif shape == 'cubes':
78-
self.models.add(propname='throat.area', model=gm.throat_area.cuboid)
60+
self.models.add(propname='throat.area',
61+
model=gm.throat_area.cuboid)
7962
self.models.add(propname='throat.surface_area',
8063
model=gm.throat_surface_area.cuboid)
81-
self['pore.area'] = 1.0

OpenPNM/Geometry/__Cube_and_Cuboid__.py

Lines changed: 18 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
===============================================================================
66
77
"""
8-
8+
import scipy as _sp
99
from OpenPNM.Geometry import models as gm
1010
from OpenPNM.Geometry import GenericGeometry
1111

@@ -18,31 +18,32 @@ class Cube_and_Cuboid(GenericGeometry):
1818

1919
def __init__(self, **kwargs):
2020
super().__init__(**kwargs)
21-
self._generate()
2221

23-
def _generate(self):
2422
self.models.add(propname='pore.seed',
2523
model=gm.pore_misc.random)
26-
self.models.add(propname='throat.seed',
27-
model=gm.throat_misc.neighbor,
28-
pore_prop='pore.seed',
29-
mode='min')
24+
# Find Network spacing
25+
Ps = self._net.pores(self.name)
26+
Ts = self._net.find_neighbor_throats(pores=Ps, mode='intersection')
27+
P1 = self._net['throat.conns'][:, 0][Ts]
28+
P2 = self._net['throat.conns'][:, 1][Ts]
29+
C1 = self._net['pore.coords'][P1]
30+
C2 = self._net['pore.coords'][P2]
31+
E = _sp.sqrt(_sp.sum((C1-C2)**2, axis=1)) # Euclidean distance
32+
if _sp.allclose(E, E[0]):
33+
spacing = E[0]
34+
else:
35+
raise Exception('A unique value of spacing could not be inferred')
3036
self.models.add(propname='pore.diameter',
31-
model=gm.pore_diameter.sphere,
32-
psd_name='weibull_min',
33-
psd_shape=1.5,
34-
psd_loc=14e-6,
35-
psd_scale=2e-6)
37+
model=gm.pore_diameter.normal,
38+
loc=spacing/2,
39+
scale=spacing/10)
3640
self.models.add(propname='pore.area',
3741
model=gm.pore_area.cubic)
3842
self.models.add(propname='pore.volume',
3943
model=gm.pore_volume.cube)
4044
self.models.add(propname='throat.diameter',
41-
model=gm.throat_diameter.cylinder,
42-
tsd_name='weibull_min',
43-
tsd_shape=1.5,
44-
tsd_loc=14e-6,
45-
tsd_scale=2e-6)
45+
model=gm.throat_diameter.minpore,
46+
factor=0.5)
4647
self.models.add(propname='throat.length',
4748
model=gm.throat_length.straight)
4849
self.models.add(propname='throat.volume',

OpenPNM/Geometry/__Stick_and_Ball__.py

Lines changed: 18 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
===============================================================================
66
77
"""
8-
8+
import scipy as _sp
99
from OpenPNM.Geometry import models as gm
1010
from OpenPNM.Geometry import GenericGeometry
1111

@@ -25,31 +25,33 @@ class Stick_and_Ball(GenericGeometry):
2525

2626
def __init__(self, **kwargs):
2727
super().__init__(**kwargs)
28-
self._generate()
2928

30-
def _generate(self):
3129
self.models.add(propname='pore.seed',
3230
model=gm.pore_misc.random,
3331
regen_mode='constant')
34-
self.models.add(propname='throat.seed',
35-
model=gm.throat_misc.neighbor,
36-
mode='min')
32+
# Find Network spacing
33+
Ps = self._net.pores(self.name)
34+
Ts = self._net.find_neighbor_throats(pores=Ps, mode='intersection')
35+
P1 = self._net['throat.conns'][:, 0][Ts]
36+
P2 = self._net['throat.conns'][:, 1][Ts]
37+
C1 = self._net['pore.coords'][P1]
38+
C2 = self._net['pore.coords'][P2]
39+
E = _sp.sqrt(_sp.sum((C1-C2)**2, axis=1)) # Euclidean distance
40+
if _sp.allclose(E, E[0]):
41+
spacing = E[0]
42+
else:
43+
raise Exception('A unique value of spacing could not be inferred')
3744
self.models.add(propname='pore.diameter',
38-
model=gm.pore_diameter.sphere,
39-
psd_name='weibull_min',
40-
psd_shape=2.5,
41-
psd_loc=0,
42-
psd_scale=0.5)
45+
model=gm.pore_diameter.normal,
46+
loc=spacing/2,
47+
scale=spacing/10)
4348
self.models.add(propname='pore.area',
4449
model=gm.pore_area.spherical)
4550
self.models.add(propname='pore.volume',
4651
model=gm.pore_volume.sphere)
4752
self.models.add(propname='throat.diameter',
48-
model=gm.throat_diameter.cylinder,
49-
tsd_name='weibull_min',
50-
tsd_shape=2.5,
51-
tsd_loc=0,
52-
tsd_scale=0.5)
53+
model=gm.throat_diameter.minpore,
54+
factor=0.5)
5355
self.models.add(propname='throat.length',
5456
model=gm.throat_length.straight)
5557
self.models.add(propname='throat.volume',

OpenPNM/Geometry/__Voronoi__.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -93,10 +93,8 @@ def _generate(self):
9393
model=gm.pore_area.spherical)
9494
self.models.add(propname='throat.diameter',
9595
model=gm.throat_diameter.equivalent_circle)
96-
self.models.add(propname='throat.length',
97-
model=gm.throat_length.constant,
98-
const=self._fibre_rad*2)
9996
self['throat.volume'] = 0.0
97+
self['throat.length'] = self._fibre_rad*2
10098
self.models.add(propname='throat.surface_area',
10199
model=gm.throat_surface_area.extrusion)
102100
self.models.add(propname='throat.c2c',

OpenPNM/Geometry/models/misc.py

Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
r"""
2+
===============================================================================
3+
Submodule -- Miscillaneous functions
4+
===============================================================================
5+
6+
"""
7+
import scipy as _sp
8+
import scipy.stats as _spts
9+
10+
11+
def random(N, seed=None, num_range=[0, 1], **kwargs):
12+
r"""
13+
Create an array of random numbers of a specified size.
14+
15+
Parameters
16+
----------
17+
N : int
18+
The length of the random array to be generated. This should correspond
19+
to the number of pores or throats on the Geometry object. For instance
20+
N = geom.Nt or N = geom.Np.
21+
22+
seed : int
23+
The starting seed value to send to Scipy's random number generator.
24+
The default is None, which means different distribution is returned
25+
each time the model is run.
26+
27+
num_range : list
28+
A two element list indicating the low and high end of the returned
29+
numbers.
30+
31+
"""
32+
range_size = num_range[1] - num_range[0]
33+
range_min = num_range[0]
34+
_sp.random.seed(seed)
35+
value = _sp.random.rand(N,)
36+
value = value*range_size + range_min
37+
return value
38+
39+
40+
def scaled(geometry, prop, factor, **kwargs):
41+
r"""
42+
Scales an existing value by a factor. Useful for constricting some throat
43+
property.
44+
45+
Parameters
46+
----------
47+
geometry : OpenPNM Geometry Object
48+
The object with which this function as associated. This argument
49+
is required to (1) set number of values to generate (geom.Np or
50+
geom.Nt) and (2) provide access to other necessary values
51+
(i.e. geom['pore.seed']).
52+
53+
prop : string
54+
The dictionary key of the array containing the values to be scaled.
55+
56+
factor : scalar
57+
The factor by which the values should be scaled.
58+
"""
59+
value = geometry[prop]*factor
60+
return value
61+
62+
63+
def weibull(geometry, shape, scale, loc, seeds, **kwargs):
64+
r"""
65+
Produces values from a Weibull distribution given a set of random numbers.
66+
67+
Parameters
68+
----------
69+
geometry : OpenPNM Geometry Object
70+
The object with which this function as associated. This argument
71+
is required to (1) set number of values to generate (geom.Np or
72+
geom.Nt) and (2) provide access to other necessary values
73+
(i.e. geom['pore.seed']).
74+
75+
shape : float
76+
This controls the skewness of the distribution, with 'shape' < 1 giving
77+
values clustered on the low end of the range with a long tail, and
78+
'shape' > 1 giving a more symmetrical distribution.
79+
80+
scale : float
81+
This controls the width of the distribution with most of values falling
82+
below this number.
83+
84+
loc : float
85+
Applies an offset to the distribution such that the smallest values are
86+
above this number.
87+
88+
seeds : string, optional
89+
The dictionary key on the Geometry object containing random seed values
90+
(between 0 and 1) to use in the statistical distribution. If none is
91+
specified, then an array of random numbers will be automatically
92+
generated and stored on the Geometry object.
93+
94+
Examples
95+
--------
96+
The following code illustrates the inner workings of this function,
97+
which uses the 'weibull_min' method of the scipy.stats module. This can
98+
be used to find suitable values of 'shape', 'scale'` and 'loc'. Note that
99+
'shape' is represented by 'c' in the actual function call.
100+
101+
.. code-block::
102+
103+
import scipy.stats as spst
104+
import matplotlib.pyplot as plt
105+
x = spst.weibull_min.ppf(q=sp.rand(10000), c=1.5, scale=0.0001, loc=0)
106+
plt.hist(x, bins=50)
107+
108+
"""
109+
seeds = geometry[seeds]
110+
value = _spts.weibull_min.ppf(q=seeds, c=shape, scale=scale, loc=loc)
111+
return value
112+
113+
114+
def normal(geometry, scale, loc, seeds, **kwargs):
115+
r"""
116+
Produces values from a Weibull distribution given a set of random numbers.
117+
118+
Parameters
119+
----------
120+
geometry : OpenPNM Geometry Object
121+
The object with which this function as associated. This argument
122+
is required to (1) set number of values to generate (geom.Np or
123+
geom.Nt) and (2) provide access to other necessary values
124+
(i.e. geom['pore.seed']).
125+
126+
scale : float
127+
The standard deviation of the Normal distribution
128+
129+
loc : float
130+
The mean of the Normal distribution
131+
132+
seeds : string, optional
133+
The dictionary key on the Geometry object containing random seed values
134+
(between 0 and 1) to use in the statistical distribution. If none is
135+
specified, then an array of random numbers will be automatically
136+
generated and stored on teh Geometry object.
137+
138+
Examples
139+
--------
140+
The following code illustrates the inner workings of this function,
141+
which uses the 'norm' method of the scipy.stats module. This can
142+
be used to find suitable values of 'scale' and 'loc'.
143+
144+
.. code-block::
145+
146+
import scipy.stats as spst
147+
import matplotlib.pyplot as plt
148+
x = spst.norm.ppf(q=sp.rand(10000), scale=.0001, loc=0.001)
149+
plt.hist(x, bins=50)
150+
151+
"""
152+
seeds = geometry[seeds]
153+
value = _spts.norm.ppf(q=seeds, scale=scale, loc=loc)
154+
return value
155+
156+
157+
def generic(geometry, func, seeds, **kwargs):
158+
r"""
159+
Accepts an 'rv_frozen' object from the Scipy.stats submodule and returns
160+
values from the distribution for the given seeds using the ``ppf`` method.
161+
162+
Parameters
163+
----------
164+
func : object
165+
An 'rv_frozen' object from the Scipy.stats library with all of the
166+
parameters pre-specified.
167+
168+
seeds : string, optional
169+
The dictionary key on the Geometry object containing random seed values
170+
(between 0 and 1) to use in the statistical distribution. If none is
171+
specified, then an array of random numbers will be automatically
172+
generated and stored on teh Geometry object.
173+
174+
Examples
175+
--------
176+
The following code illustrates the process of obtaining a 'frozen' Scipy
177+
stats object, and visualizes the corresponding distribution using a
178+
Matplotlib histogram:
179+
180+
.. code-block:: python
181+
182+
import scipy
183+
func = scipy.stats.weibull_min(c=2, scale=.0001, loc=0)
184+
import matplotlib.pylot as plt
185+
plt.hist(func.ppf(q=scipy.rand(1000), bins=50))
186+
187+
"""
188+
seeds = geometry[seeds]
189+
value = func.ppf(seeds)
190+
return value

0 commit comments

Comments
 (0)