openpnm 1.0.0__zip
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- OpenPNM-1.1/MANIFEST.in +2 -0
- OpenPNM-1.1/OpenPNM/Algorithms/__FickianDiffusion__.py +67 -0
- OpenPNM-1.1/OpenPNM/Algorithms/__FourierConduction__.py +63 -0
- OpenPNM-1.1/OpenPNM/Algorithms/__GenericAlgorithm__.py +235 -0
- OpenPNM-1.1/OpenPNM/Algorithms/__GenericLinearTransport__.py +641 -0
- OpenPNM-1.1/OpenPNM/Algorithms/__InvasionPercolationForImbibition__.py +703 -0
- OpenPNM-1.1/OpenPNM/Algorithms/__InvasionPercolationTimed__.py +702 -0
- OpenPNM-1.1/OpenPNM/Algorithms/__InvasionPercolation__.py +156 -0
- OpenPNM-1.1/OpenPNM/Algorithms/__OhmicConduction__.py +64 -0
- OpenPNM-1.1/OpenPNM/Algorithms/__OrdinaryPercolation__.py +402 -0
- OpenPNM-1.1/OpenPNM/Algorithms/__StokesFlow__.py +64 -0
- OpenPNM-1.1/OpenPNM/Algorithms/__Tortuosity__.py +91 -0
- OpenPNM-1.1/OpenPNM/Algorithms/__init__.py +48 -0
- OpenPNM-1.1/OpenPNM/Base/__Controller__.py +480 -0
- OpenPNM-1.1/OpenPNM/Base/__Core__.py +1522 -0
- OpenPNM-1.1/OpenPNM/Base/__ModelsDict__.py +345 -0
- OpenPNM-1.1/OpenPNM/Base/__Tools__.py +72 -0
- OpenPNM-1.1/OpenPNM/Base/__init__.py +32 -0
- OpenPNM-1.1/OpenPNM/Geometry/__Boundary__.py +80 -0
- OpenPNM-1.1/OpenPNM/Geometry/__Cube_and_Cuboid__.py +64 -0
- OpenPNM-1.1/OpenPNM/Geometry/__GenericGeometry__.py +106 -0
- OpenPNM-1.1/OpenPNM/Geometry/__SGL10__.py +67 -0
- OpenPNM-1.1/OpenPNM/Geometry/__Stick_and_Ball__.py +68 -0
- OpenPNM-1.1/OpenPNM/Geometry/__TestGeometry__.py +51 -0
- OpenPNM-1.1/OpenPNM/Geometry/__Toray090__.py +68 -0
- OpenPNM-1.1/OpenPNM/Geometry/__Voronoi__.py +98 -0
- OpenPNM-1.1/OpenPNM/Geometry/__init__.py +47 -0
- OpenPNM-1.1/OpenPNM/Geometry/models/__init__.py +33 -0
- OpenPNM-1.1/OpenPNM/Geometry/models/pore_area.py +27 -0
- OpenPNM-1.1/OpenPNM/Geometry/models/pore_centroid.py +35 -0
- OpenPNM-1.1/OpenPNM/Geometry/models/pore_diameter.py +127 -0
- OpenPNM-1.1/OpenPNM/Geometry/models/pore_misc.py +55 -0
- OpenPNM-1.1/OpenPNM/Geometry/models/pore_seed.py +212 -0
- OpenPNM-1.1/OpenPNM/Geometry/models/pore_surface_area.py +28 -0
- OpenPNM-1.1/OpenPNM/Geometry/models/pore_vertices.py +19 -0
- OpenPNM-1.1/OpenPNM/Geometry/models/pore_volume.py +133 -0
- OpenPNM-1.1/OpenPNM/Geometry/models/throat_area.py +47 -0
- OpenPNM-1.1/OpenPNM/Geometry/models/throat_centroid.py +80 -0
- OpenPNM-1.1/OpenPNM/Geometry/models/throat_diameter.py +106 -0
- OpenPNM-1.1/OpenPNM/Geometry/models/throat_length.py +95 -0
- OpenPNM-1.1/OpenPNM/Geometry/models/throat_misc.py +42 -0
- OpenPNM-1.1/OpenPNM/Geometry/models/throat_normal.py +31 -0
- OpenPNM-1.1/OpenPNM/Geometry/models/throat_offset_vertices.py +191 -0
- OpenPNM-1.1/OpenPNM/Geometry/models/throat_perimeter.py +26 -0
- OpenPNM-1.1/OpenPNM/Geometry/models/throat_seed.py +12 -0
- OpenPNM-1.1/OpenPNM/Geometry/models/throat_shape_factor.py +37 -0
- OpenPNM-1.1/OpenPNM/Geometry/models/throat_surface_area.py +44 -0
- OpenPNM-1.1/OpenPNM/Geometry/models/throat_vector.py +27 -0
- OpenPNM-1.1/OpenPNM/Geometry/models/throat_vertices.py +19 -0
- OpenPNM-1.1/OpenPNM/Geometry/models/throat_volume.py +45 -0
- OpenPNM-1.1/OpenPNM/Network/__Cubic__.py +316 -0
- OpenPNM-1.1/OpenPNM/Network/__DelaunayCubic__.py +127 -0
- OpenPNM-1.1/OpenPNM/Network/__Delaunay__.py +600 -0
- OpenPNM-1.1/OpenPNM/Network/__GenericNetwork__.py +1184 -0
- OpenPNM-1.1/OpenPNM/Network/__MatFile__.py +331 -0
- OpenPNM-1.1/OpenPNM/Network/__TestNet__.py +109 -0
- OpenPNM-1.1/OpenPNM/Network/__init__.py +40 -0
- OpenPNM-1.1/OpenPNM/Network/models/__init__.py +12 -0
- OpenPNM-1.1/OpenPNM/Network/models/pore_topology.py +106 -0
- OpenPNM-1.1/OpenPNM/Phases/__Air__.py +63 -0
- OpenPNM-1.1/OpenPNM/Phases/__GenericPhase__.py +146 -0
- OpenPNM-1.1/OpenPNM/Phases/__Mercury__.py +71 -0
- OpenPNM-1.1/OpenPNM/Phases/__TestPhase__.py +46 -0
- OpenPNM-1.1/OpenPNM/Phases/__Water__.py +56 -0
- OpenPNM-1.1/OpenPNM/Phases/__init__.py +38 -0
- OpenPNM-1.1/OpenPNM/Phases/models/__init__.py +22 -0
- OpenPNM-1.1/OpenPNM/Phases/models/contact_angle.py +34 -0
- OpenPNM-1.1/OpenPNM/Phases/models/density.py +81 -0
- OpenPNM-1.1/OpenPNM/Phases/models/diffusivity.py +95 -0
- OpenPNM-1.1/OpenPNM/Phases/models/electrical_conductivity.py +10 -0
- OpenPNM-1.1/OpenPNM/Phases/models/misc.py +125 -0
- OpenPNM-1.1/OpenPNM/Phases/models/molar_density.py +69 -0
- OpenPNM-1.1/OpenPNM/Phases/models/molar_mass.py +31 -0
- OpenPNM-1.1/OpenPNM/Phases/models/surface_tension.py +104 -0
- OpenPNM-1.1/OpenPNM/Phases/models/thermal_conductivity.py +98 -0
- OpenPNM-1.1/OpenPNM/Phases/models/vapor_pressure.py +69 -0
- OpenPNM-1.1/OpenPNM/Phases/models/viscosity.py +103 -0
- OpenPNM-1.1/OpenPNM/Physics/__GenericPhysics__.py +111 -0
- OpenPNM-1.1/OpenPNM/Physics/__Standard__.py +51 -0
- OpenPNM-1.1/OpenPNM/Physics/__TestPhysics__.py +50 -0
- OpenPNM-1.1/OpenPNM/Physics/__init__.py +30 -0
- OpenPNM-1.1/OpenPNM/Physics/models/__init__.py +18 -0
- OpenPNM-1.1/OpenPNM/Physics/models/capillary_pressure.py +122 -0
- OpenPNM-1.1/OpenPNM/Physics/models/diffusive_conductance.py +82 -0
- OpenPNM-1.1/OpenPNM/Physics/models/electrical_conductance.py +59 -0
- OpenPNM-1.1/OpenPNM/Physics/models/generic_source_term.py +564 -0
- OpenPNM-1.1/OpenPNM/Physics/models/hydraulic_conductance.py +76 -0
- OpenPNM-1.1/OpenPNM/Physics/models/multiphase.py +133 -0
- OpenPNM-1.1/OpenPNM/Physics/models/thermal_conductance.py +67 -0
- OpenPNM-1.1/OpenPNM/Postprocessing/Graphics.py +251 -0
- OpenPNM-1.1/OpenPNM/Postprocessing/Plots.py +369 -0
- OpenPNM-1.1/OpenPNM/Postprocessing/__init__.py +10 -0
- OpenPNM-1.1/OpenPNM/Utilities/IO.py +277 -0
- OpenPNM-1.1/OpenPNM/Utilities/Shortcuts.py +17 -0
- OpenPNM-1.1/OpenPNM/Utilities/__init__.py +16 -0
- OpenPNM-1.1/OpenPNM/Utilities/misc.py +226 -0
- OpenPNM-1.1/OpenPNM/Utilities/transformations.py +1923 -0
- OpenPNM-1.1/OpenPNM/Utilities/vertexops.py +824 -0
- OpenPNM-1.1/OpenPNM/__init__.py +56 -0
- OpenPNM-1.1/OpenPNM.egg-info/PKG-INFO +11 -0
- OpenPNM-1.1/OpenPNM.egg-info/SOURCES.txt +107 -0
- OpenPNM-1.1/OpenPNM.egg-info/dependency_links.txt +1 -0
- OpenPNM-1.1/OpenPNM.egg-info/requires.txt +1 -0
- OpenPNM-1.1/OpenPNM.egg-info/top_level.txt +1 -0
- OpenPNM-1.1/PKG-INFO +11 -0
- OpenPNM-1.1/README.txt +88 -0
- OpenPNM-1.1/setup.cfg +7 -0
- OpenPNM-1.1/setup.py +39 -0
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
r"""
|
|
2
|
+
===============================================================================
|
|
3
|
+
pore_diameter --
|
|
4
|
+
===============================================================================
|
|
5
|
+
|
|
6
|
+
"""
|
|
7
|
+
import scipy as _sp
|
|
8
|
+
import numpy as np
|
|
9
|
+
|
|
10
|
+
from scipy.spatial import ConvexHull
|
|
11
|
+
|
|
12
|
+
def sphere(geometry,
|
|
13
|
+
psd_name,psd_shape,psd_loc,psd_scale,
|
|
14
|
+
pore_seed='pore.seed',
|
|
15
|
+
psd_offset=0,
|
|
16
|
+
**kwargs):
|
|
17
|
+
r"""
|
|
18
|
+
Calculate pore diameter from seed values for a spherical pore body
|
|
19
|
+
"""
|
|
20
|
+
import scipy.stats as spst
|
|
21
|
+
prob_fn = getattr(spst,psd_name)
|
|
22
|
+
P = prob_fn(psd_shape,loc=psd_loc,scale=psd_scale)
|
|
23
|
+
value = P.ppf(geometry[pore_seed])+psd_offset
|
|
24
|
+
return value
|
|
25
|
+
|
|
26
|
+
def voronoi(geometry,
|
|
27
|
+
pore_volume='pore.volume',
|
|
28
|
+
**kwargs):
|
|
29
|
+
r"""
|
|
30
|
+
Calculate pore diameter from equivalent sphere - volumes must be calculated first
|
|
31
|
+
"""
|
|
32
|
+
from scipy.special import cbrt
|
|
33
|
+
pore_vols = geometry[pore_volume]
|
|
34
|
+
value = cbrt(6*pore_vols/_sp.pi)
|
|
35
|
+
return value
|
|
36
|
+
|
|
37
|
+
def centroids(network,
|
|
38
|
+
geometry,
|
|
39
|
+
**kwargs):
|
|
40
|
+
r"""
|
|
41
|
+
Calculate the diameter representing an inclosed sphere. The maximum is very difficult to caluclate for irregular polygons with more than 4 faces
|
|
42
|
+
so an average distance from the pore centroid to the throat centroid is an approximation
|
|
43
|
+
"""
|
|
44
|
+
Np = geometry.num_pores()
|
|
45
|
+
value = _sp.zeros(Np)
|
|
46
|
+
pore_map = geometry.map_pores(geometry.pores(),geometry._net)
|
|
47
|
+
for geom_pore,net_pore in pore_map:
|
|
48
|
+
net_throats = geometry._net.find_neighbor_throats(net_pore)
|
|
49
|
+
geom_throats = geometry._net.map_throats(net_throats,geometry)[:,1]
|
|
50
|
+
tcs = geometry["throat.centroid"][geom_throats]
|
|
51
|
+
pc = geometry["pore.centroid"][geom_pore]
|
|
52
|
+
value[geom_pore]=_sp.mean(_sp.sqrt(((tcs-pc)*(tcs-pc))[:,0]+((tcs-pc)*(tcs-pc))[:,1]+((tcs-pc)*(tcs-pc))[:,2]))*2
|
|
53
|
+
return value
|
|
54
|
+
|
|
55
|
+
def insphere(network,
|
|
56
|
+
geometry,
|
|
57
|
+
**kwargs):
|
|
58
|
+
r"""
|
|
59
|
+
Calculate the diameter of the insphere using linear programming
|
|
60
|
+
"""
|
|
61
|
+
import warnings
|
|
62
|
+
try:
|
|
63
|
+
import pulp as pu
|
|
64
|
+
Np = geometry.num_pores()
|
|
65
|
+
value = _sp.zeros(Np)
|
|
66
|
+
pore_map = geometry.map_pores(geometry.pores(),geometry._net)
|
|
67
|
+
for geom_pore,net_pore in pore_map:
|
|
68
|
+
net_throats = geometry._net.find_neighbor_throats(net_pore)
|
|
69
|
+
geom_throats = geometry._net.map_throats(net_throats,geometry)[:,1]
|
|
70
|
+
verts = geometry['throat.offset_vertices'][geom_throats]
|
|
71
|
+
if len(verts) > 1:
|
|
72
|
+
try:
|
|
73
|
+
pts = np.vstack((i for i in verts if len(i)>0))
|
|
74
|
+
except ValueError:
|
|
75
|
+
pts = []
|
|
76
|
+
if len(pts) > 4:
|
|
77
|
+
"Work out central point to use as initial guess"
|
|
78
|
+
c0 = np.mean(pts,axis=0)
|
|
79
|
+
"Compute convex hull to find points lying on the hull in order"
|
|
80
|
+
hull = ConvexHull(pts, qhull_options='QJ Pp')
|
|
81
|
+
"For each simplex making up the hull collect the end points"
|
|
82
|
+
A = pts[hull.simplices[:,0]]
|
|
83
|
+
B = pts[hull.simplices[:,1]]
|
|
84
|
+
C = pts[hull.simplices[:,2]]
|
|
85
|
+
#I = np.array([[0,1],[-1,0]])
|
|
86
|
+
"Normal of the simplices"
|
|
87
|
+
#N = np.dot((B-A),I)
|
|
88
|
+
N = np.cross((B-A),(C-A),axis=1)
|
|
89
|
+
#L = np.sqrt(np.sum(np.square(N),axis=1))
|
|
90
|
+
"Normalize the normal vector"
|
|
91
|
+
L = np.linalg.norm(N,axis=1)
|
|
92
|
+
F = np.vstack((L,L,L)).T
|
|
93
|
+
N /= F
|
|
94
|
+
"If normals point out of hull change sign to point in"
|
|
95
|
+
pointing_out = (np.sum((A-c0)*N,axis=1)>0)
|
|
96
|
+
N[pointing_out]*= -1
|
|
97
|
+
"Define Linear Program Variables"
|
|
98
|
+
"The centre of the incircle adjustment"
|
|
99
|
+
cx = pu.LpVariable("cx",None,None,pu.LpContinuous)
|
|
100
|
+
cy = pu.LpVariable("cy",None,None,pu.LpContinuous)
|
|
101
|
+
cz = pu.LpVariable("cz",None,None,pu.LpContinuous)
|
|
102
|
+
"Radius of the incircle"
|
|
103
|
+
R = pu.LpVariable("R",0,None,pu.LpContinuous)
|
|
104
|
+
"Slack variables for shortest distance between centre and simplices"
|
|
105
|
+
S = pu.LpVariable.dict("SlackVariable",range(len(A)),0,None,pu.LpContinuous)
|
|
106
|
+
"Set up LP problem"
|
|
107
|
+
prob = pu.LpProblem("FindInRadius",pu.LpMaximize)
|
|
108
|
+
"Objective Function"
|
|
109
|
+
prob += R
|
|
110
|
+
for i in range(len(A)):
|
|
111
|
+
" Ni.(C-Ai)-Si = 0"
|
|
112
|
+
prob += N[i][0]*(c0[0]+cx) + N[i][1]*(c0[1]+cy) + N[i][2]*(c0[2]+cz)- N[i][0]*A[i][0] - N[i][1]*A[i][1] - N[i][2]*A[i][2]- S[i] == 0
|
|
113
|
+
"Si >= R"
|
|
114
|
+
prob += S[i] >= R
|
|
115
|
+
"Solve the LP"
|
|
116
|
+
with warnings.catch_warnings():
|
|
117
|
+
warnings.simplefilter("ignore")
|
|
118
|
+
prob.solve()
|
|
119
|
+
"As the radius is the objective function we can get it from the objective or as R.value()"
|
|
120
|
+
rad = prob.objective.value()
|
|
121
|
+
#cen = c0 + np.array([cx.value(),cy.value(),cz.value()])
|
|
122
|
+
value[geom_pore]=rad*2
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
return value
|
|
126
|
+
except ImportError:
|
|
127
|
+
print("Cannot use insphere method without installing pulp package")
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
r"""
|
|
2
|
+
===============================================================================
|
|
3
|
+
pore_misc -- miscillaneous and generic functions to apply to pores
|
|
4
|
+
===============================================================================
|
|
5
|
+
|
|
6
|
+
"""
|
|
7
|
+
import scipy as _sp
|
|
8
|
+
|
|
9
|
+
def constant(geometry,
|
|
10
|
+
value,
|
|
11
|
+
**kwargs):
|
|
12
|
+
r"""
|
|
13
|
+
Assign specified constant value. This function is redundant and could be
|
|
14
|
+
accomplished with geometry['pore.prop'] = value.
|
|
15
|
+
"""
|
|
16
|
+
value = _sp.ones(geometry.num_pores(),)*value
|
|
17
|
+
return value
|
|
18
|
+
|
|
19
|
+
def random(geometry,
|
|
20
|
+
seed=None,
|
|
21
|
+
num_range=[0,1],
|
|
22
|
+
**kwargs):
|
|
23
|
+
r"""
|
|
24
|
+
Assign random number to pore bodies
|
|
25
|
+
note: should this be called 'poisson'?
|
|
26
|
+
"""
|
|
27
|
+
range_size = num_range[1]-num_range[0]
|
|
28
|
+
range_min = num_range[0]
|
|
29
|
+
_sp.random.seed(seed)
|
|
30
|
+
value = _sp.random.rand(geometry.num_pores(),)
|
|
31
|
+
value = value*range_size + range_min
|
|
32
|
+
return value
|
|
33
|
+
|
|
34
|
+
def neighbor(network,
|
|
35
|
+
geometry,
|
|
36
|
+
throat_prop='',
|
|
37
|
+
mode='min',
|
|
38
|
+
**kwargs):
|
|
39
|
+
r"""
|
|
40
|
+
Adopt the minimum seed value from the neighboring throats
|
|
41
|
+
"""
|
|
42
|
+
Ps = geometry.pores()
|
|
43
|
+
data = geometry[throat_prop]
|
|
44
|
+
neighborTs = network.find_neighbor_throats(pores=Ps,flatten=False,mode='intersection')
|
|
45
|
+
values = _sp.ones((_sp.shape(Ps)[0],))*_sp.nan
|
|
46
|
+
if mode == 'min':
|
|
47
|
+
for pore in Ps:
|
|
48
|
+
values[pore] = _sp.amin(data[neighborTs[pore]])
|
|
49
|
+
if mode == 'max':
|
|
50
|
+
for pore in Ps:
|
|
51
|
+
values[pore] = _sp.amax(data[neighborTs[pore]])
|
|
52
|
+
if mode == 'mean':
|
|
53
|
+
for pore in Ps:
|
|
54
|
+
values[pore] = _sp.mean(data[neighborTs[pore]])
|
|
55
|
+
return values
|
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
r"""
|
|
3
|
+
===============================================================================
|
|
4
|
+
pore_seeds -- Methods for generating fields of values for use as seeds in
|
|
5
|
+
statistical pore size distributions
|
|
6
|
+
===============================================================================
|
|
7
|
+
|
|
8
|
+
"""
|
|
9
|
+
import scipy as _sp
|
|
10
|
+
|
|
11
|
+
def perlin_noise(geometry,freq=1,octaves=4,mode='classic',**kwargs):
|
|
12
|
+
r'''
|
|
13
|
+
Generate pore seed values using the Perlin noise algorithm. This approach
|
|
14
|
+
imparts some spatial clumpiness to the pore seeds.
|
|
15
|
+
|
|
16
|
+
Parameters
|
|
17
|
+
----------
|
|
18
|
+
freq, octaves : int
|
|
19
|
+
Parameters that control the qualities of the noise. Lower frequency
|
|
20
|
+
results in more smaller clumps. Higher octaves gives a more textured
|
|
21
|
+
noise.
|
|
22
|
+
mode : {'classic','simplex'}
|
|
23
|
+
The algorithm to use when generating the noise.
|
|
24
|
+
|
|
25
|
+
* 'classic' : (default) The original algorithm developed by Perlin
|
|
26
|
+
* 'simplex' : A newer algorithm that is supposedly faster and results
|
|
27
|
+
in a more natural texture.
|
|
28
|
+
|
|
29
|
+
Returns
|
|
30
|
+
-------
|
|
31
|
+
A list of pore seeds values between 0:1 that are spatially correlated (i.e.
|
|
32
|
+
similar values are clumped together)
|
|
33
|
+
|
|
34
|
+
Notes
|
|
35
|
+
-----
|
|
36
|
+
- This method uses image analysis type tools, so only works on Cubic
|
|
37
|
+
networks
|
|
38
|
+
- This method requires the 'noise' module is installed
|
|
39
|
+
|
|
40
|
+
Examples
|
|
41
|
+
--------
|
|
42
|
+
>>> import OpenPNM
|
|
43
|
+
>>> pn = OpenPNM.Network.Cubic(shape=[50,50,50])
|
|
44
|
+
>>> geom = OpenPNM.Geometry.GenericGeometry(network=pn,pores=pn.Ps,throats=pn.Ts)
|
|
45
|
+
>>> geom.add_model(propname='pore.seed',model=OpenPNM.Geometry.models.pore_seed.perlin_noise)
|
|
46
|
+
>>> im = pn.asarray(geom['pore.seed'])
|
|
47
|
+
|
|
48
|
+
Visualizing the end result can be done with:
|
|
49
|
+
|
|
50
|
+
``matplotlib.pyplot.imshow(im[:,25,:],interpolation='none')``
|
|
51
|
+
|
|
52
|
+
'''
|
|
53
|
+
from noise import pnoise3, snoise3
|
|
54
|
+
import scipy.stats as spst
|
|
55
|
+
|
|
56
|
+
net = geometry._net
|
|
57
|
+
if mode == 'classic':
|
|
58
|
+
model = pnoise3
|
|
59
|
+
elif mode == 'simplex':
|
|
60
|
+
model = snoise3
|
|
61
|
+
freq = freq * octaves
|
|
62
|
+
#The following will only work on Cubic networks
|
|
63
|
+
x = net._shape[0]
|
|
64
|
+
y = net._shape[1]
|
|
65
|
+
z = net._shape[2]
|
|
66
|
+
temp = _sp.ndarray((x,y,z))
|
|
67
|
+
for k in range(z):
|
|
68
|
+
for j in range(y):
|
|
69
|
+
for i in range(x):
|
|
70
|
+
temp[i,j,k] = model(i / freq, j / freq, k / freq, octaves) + 0.5
|
|
71
|
+
#Assuming that the noise is normally distributed, find seeds of that dist
|
|
72
|
+
temp = _sp.reshape(temp,(temp.size,))
|
|
73
|
+
x_mean = _sp.mean(temp)
|
|
74
|
+
x_sigma = _sp.sqrt(1/(temp.size-1)*_sp.sum((temp - x_mean)**2))
|
|
75
|
+
fn1 = spst.norm(loc=x_mean,scale=x_sigma)
|
|
76
|
+
values = fn1.cdf(temp)
|
|
77
|
+
values = values[geometry.map_pores(target=net,pores=geometry.Ps)]
|
|
78
|
+
return values.flatten()
|
|
79
|
+
|
|
80
|
+
def distance_from_inclusion(geometry,p,**kwargs):
|
|
81
|
+
r'''
|
|
82
|
+
Genrate spatially correlated pore seeds by calculating distance from random
|
|
83
|
+
locations (inclusions) in the domain
|
|
84
|
+
|
|
85
|
+
Parameters
|
|
86
|
+
----------
|
|
87
|
+
p : float
|
|
88
|
+
The fraction of pores in the domain that are set as 'seeds' for the
|
|
89
|
+
distance calculation
|
|
90
|
+
|
|
91
|
+
Returns
|
|
92
|
+
-------
|
|
93
|
+
A list of distance values (in voxels) between each pore and it nearest
|
|
94
|
+
seed pore. A list of voxel distances is returned rather than normalized
|
|
95
|
+
seeds between 0:1 so that the user can manipulate the map as desired, by
|
|
96
|
+
applying desired thresholds and/or scaling to get 0:1 seeds.
|
|
97
|
+
|
|
98
|
+
Notes
|
|
99
|
+
-----
|
|
100
|
+
- This method uses image analysis tools, so only works on Cubic networks
|
|
101
|
+
- At present the result contains edge artifacts since no inclusions are present beyond the image boundary
|
|
102
|
+
|
|
103
|
+
Examples
|
|
104
|
+
--------
|
|
105
|
+
>>> import OpenPNM
|
|
106
|
+
>>> pn = OpenPNM.Network.Cubic(shape=[50,50,50])
|
|
107
|
+
>>> geom = OpenPNM.Geometry.GenericGeometry(network=pn,pores=pn.Ps,throats=pn.Ts)
|
|
108
|
+
>>> geom.add_model(propname='pore.seed',model=OpenPNM.Geometry.models.pore_seed.distance_from_inclusion,p = 0.001)
|
|
109
|
+
>>> im = pn.asarray(geom['pore.seed'])
|
|
110
|
+
|
|
111
|
+
Visualizing the end result can be done with:
|
|
112
|
+
|
|
113
|
+
``matplotlib.pyplot.imshow(im[:,25,:],interpolation='none')``
|
|
114
|
+
|
|
115
|
+
'''
|
|
116
|
+
import scipy.ndimage as _spim
|
|
117
|
+
net = geometry._net
|
|
118
|
+
#The following will only work on Cubic networks
|
|
119
|
+
x = net._shape[0]
|
|
120
|
+
y = net._shape[1]
|
|
121
|
+
z = net._shape[2]
|
|
122
|
+
img = _sp.rand(x,y,z)>p
|
|
123
|
+
#Pad image by tiling
|
|
124
|
+
a = _sp.tile(img,[3,3,3])
|
|
125
|
+
b = a[x:-x,y:-y,z:-z]
|
|
126
|
+
#Perform distance transform
|
|
127
|
+
img = _spim.distance_transform_bf(b)
|
|
128
|
+
#Convert back to pore-list
|
|
129
|
+
values = img.flatten()
|
|
130
|
+
values = values[geometry.map_pores(target=net,pores=geometry.Ps)]
|
|
131
|
+
return values
|
|
132
|
+
|
|
133
|
+
def spatially_correlated(geometry,network,weights=None,strel=None,**kwargs):
|
|
134
|
+
r'''
|
|
135
|
+
Generates pore seeds that are spatailly correlated with their neighbors.
|
|
136
|
+
|
|
137
|
+
Parameters
|
|
138
|
+
----------
|
|
139
|
+
weights : list of ints, optional
|
|
140
|
+
The [Nx,Ny,Nz] distances (in number of pores) in each direction that
|
|
141
|
+
should be correlated.
|
|
142
|
+
|
|
143
|
+
strel : array_like, optional (in place of weights)
|
|
144
|
+
The option allows full control over the spatial correlation pattern by
|
|
145
|
+
specifying the structuring element to be used in the convolution.
|
|
146
|
+
|
|
147
|
+
The array should be a 3D array containing the strength of correlations
|
|
148
|
+
in each direction. Nonzero values indicate the strength, direction
|
|
149
|
+
and extent of correlations. The following would achieve a basic
|
|
150
|
+
correlation in the z-direction:
|
|
151
|
+
|
|
152
|
+
strel = sp.array([[[0, 0, 0], [0, 0, 0], [0, 0, 0]],\
|
|
153
|
+
[[0, 0, 0], [1, 1, 1], [0, 0, 0]],\
|
|
154
|
+
[[0, 0, 0], [0, 0, 0], [0, 0, 0]]])
|
|
155
|
+
|
|
156
|
+
Notes
|
|
157
|
+
-----
|
|
158
|
+
This approach uses image convolution to replace each pore seed in the
|
|
159
|
+
geoemtry with a weighted average of those around it. It then converts the
|
|
160
|
+
new seeds back to a random distribution by assuming they new seeds are
|
|
161
|
+
normally distributed.
|
|
162
|
+
|
|
163
|
+
Because is uses image analysis tools, it only works on Cubic networks.
|
|
164
|
+
|
|
165
|
+
This is the appproached used by Gostick et al [2]_ to create an anistropic
|
|
166
|
+
gas diffusion layer for fuel cell electrodes.
|
|
167
|
+
|
|
168
|
+
References
|
|
169
|
+
----------
|
|
170
|
+
.. [2] J. Gostick et al, Pore network modeling of fibrous gas diffusion layers for polymer electrolyte membrane fuel cells. J Power Sources 173 (2007) 277–290
|
|
171
|
+
|
|
172
|
+
Examples
|
|
173
|
+
--------
|
|
174
|
+
>>> import OpenPNM
|
|
175
|
+
>>> pn = OpenPNM.Network.Cubic(shape=[50,50,50])
|
|
176
|
+
>>> geom = OpenPNM.Geometry.GenericGeometry(network=pn,pores=pn.Ps,throats=pn.Ts)
|
|
177
|
+
>>> geom.add_model(propname='pore.seed',model=OpenPNM.Geometry.models.pore_seed.spatially_correlated,weights=[2,2,2])
|
|
178
|
+
>>> im = pn.asarray(geom['pore.seed'])
|
|
179
|
+
|
|
180
|
+
Visualizing the end result can be done with:
|
|
181
|
+
|
|
182
|
+
``matplotlib.pyplot.imshow(im[:,25,:],interpolation='none')``
|
|
183
|
+
|
|
184
|
+
'''
|
|
185
|
+
import scipy.ndimage as spim
|
|
186
|
+
import scipy.stats as spst
|
|
187
|
+
#The following will only work on Cubic networks
|
|
188
|
+
x = network._shape[0]
|
|
189
|
+
y = network._shape[1]
|
|
190
|
+
z = network._shape[2]
|
|
191
|
+
im = _sp.rand(x,y,z)
|
|
192
|
+
if strel is None: # Then generate a strel
|
|
193
|
+
if sum(weights) == 0:
|
|
194
|
+
# If weights of 0 are sent, then skip everything and return rands.
|
|
195
|
+
return im.flatten()
|
|
196
|
+
w = _sp.array(weights)
|
|
197
|
+
strel = _sp.zeros(w*2+1)
|
|
198
|
+
strel[:,w[1],w[2]] = 1
|
|
199
|
+
strel[w[0],:,w[2]] = 1
|
|
200
|
+
strel[w[0],w[1],:] = 1
|
|
201
|
+
im = spim.convolve(im,strel)
|
|
202
|
+
#Convolution is no longer randomly distributed, so fit a gaussian and find it's seeds
|
|
203
|
+
temp = im.flatten()
|
|
204
|
+
x_mean = _sp.mean(temp)
|
|
205
|
+
x_sigma = _sp.sqrt(1/(temp.size-1)*_sp.sum((temp - x_mean)**2))
|
|
206
|
+
fn1 = spst.norm(loc=x_mean,scale=x_sigma)
|
|
207
|
+
values = fn1.cdf(temp)
|
|
208
|
+
values = values[geometry.map_pores(target=network,pores=geometry.Ps)]
|
|
209
|
+
return values
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
r"""
|
|
2
|
+
===============================================================================
|
|
3
|
+
Submodule -- throat_surface_area
|
|
4
|
+
===============================================================================
|
|
5
|
+
|
|
6
|
+
"""
|
|
7
|
+
import scipy as _sp
|
|
8
|
+
|
|
9
|
+
def sphere(geometry,
|
|
10
|
+
pore_diameter='pore.diameter',
|
|
11
|
+
**kwargs):
|
|
12
|
+
r"""
|
|
13
|
+
Calculate internal surface area for a spherical pore
|
|
14
|
+
"""
|
|
15
|
+
R = geometry[pore_diameter]/2
|
|
16
|
+
value = 4*_sp.constants.pi*R**2
|
|
17
|
+
return value
|
|
18
|
+
|
|
19
|
+
def cube(geometry,
|
|
20
|
+
pore_diameter='pore.diameter',
|
|
21
|
+
**kwargs):
|
|
22
|
+
r"""
|
|
23
|
+
Calculate internal surface area for a cubic pore
|
|
24
|
+
"""
|
|
25
|
+
D = geometry[pore_diameter]
|
|
26
|
+
value = 6*D**2
|
|
27
|
+
return value
|
|
28
|
+
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
r"""
|
|
2
|
+
===============================================================================
|
|
3
|
+
pore_vertices -- update pore vertices from Vornoi object
|
|
4
|
+
===============================================================================
|
|
5
|
+
|
|
6
|
+
"""
|
|
7
|
+
import scipy as _sp
|
|
8
|
+
|
|
9
|
+
def voronoi(network,
|
|
10
|
+
geometry,
|
|
11
|
+
**kwargs):
|
|
12
|
+
r"""
|
|
13
|
+
Update the pore vertices from the voronoi vertices
|
|
14
|
+
"""
|
|
15
|
+
pores = geometry.map_pores(network,geometry.pores())
|
|
16
|
+
value = _sp.ndarray(len(pores),dtype=object)
|
|
17
|
+
for i in range(len(pores)):
|
|
18
|
+
value[i]=_sp.asarray(list(network["pore.vert_index"][pores[i]].values()))
|
|
19
|
+
return value
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
r"""
|
|
2
|
+
===============================================================================
|
|
3
|
+
pore_volume --
|
|
4
|
+
===============================================================================
|
|
5
|
+
|
|
6
|
+
"""
|
|
7
|
+
import scipy as _sp
|
|
8
|
+
import numpy as np
|
|
9
|
+
from scipy.spatial import Delaunay
|
|
10
|
+
import OpenPNM.Utilities.misc as misc
|
|
11
|
+
|
|
12
|
+
def _get_hull_volume(points):
|
|
13
|
+
r"""
|
|
14
|
+
Calculate the volume of a set of points by dividing the bounding surface into triangles and working out the volume of all the pyramid elements
|
|
15
|
+
connected to the volume centroid
|
|
16
|
+
"""
|
|
17
|
+
" remove any duplicate points - this messes up the triangulation "
|
|
18
|
+
points = _sp.asarray(misc.unique_list(np.around(points,10)))
|
|
19
|
+
try:
|
|
20
|
+
tri = Delaunay(points,qhull_options='QJ Pp')
|
|
21
|
+
except _sp.spatial.qhull.QhullError:
|
|
22
|
+
print(points)
|
|
23
|
+
" We only want points included in the convex hull to calculate the centroid "
|
|
24
|
+
hull_centroid = _sp.array([points[:,0].mean(),points[:,1].mean(),points[:,2].mean()])
|
|
25
|
+
hull_volume = 0.0
|
|
26
|
+
pyramid_COMs = []
|
|
27
|
+
for ia, ib, ic in tri.convex_hull:
|
|
28
|
+
" Points making each triangular face "
|
|
29
|
+
" Collection of co-ordinates of each point in this face "
|
|
30
|
+
face_x = points[[ia,ib,ic]][:,0]
|
|
31
|
+
face_y = points[[ia,ib,ic]][:,1]
|
|
32
|
+
face_z = points[[ia,ib,ic]][:,2]
|
|
33
|
+
" Average of each co-ordinate is the centroid of the face "
|
|
34
|
+
face_centroid = [face_x.mean(),face_y.mean(),face_z.mean()]
|
|
35
|
+
face_centroid_vector = face_centroid - hull_centroid
|
|
36
|
+
" Vectors of the sides of the face used to find normal vector and area "
|
|
37
|
+
vab = points[ib] - points[ia]
|
|
38
|
+
vac = points[ic] - points[ia]
|
|
39
|
+
vbc = points[ic] - points[ib] # used later for area
|
|
40
|
+
#face_COM = (vab+vac)/3
|
|
41
|
+
" As vectors are co-planar the cross-product will produce the normal vector of the face "
|
|
42
|
+
face_normal = _sp.cross(vab,vac)
|
|
43
|
+
try:
|
|
44
|
+
face_unit_normal = face_normal/_sp.linalg.norm(face_normal)
|
|
45
|
+
except RuntimeWarning:
|
|
46
|
+
print("Pore Volume Error:" +str(vab)+" "+str(vac))
|
|
47
|
+
" As triangles are orientated randomly in 3D we could either transform co-ordinates to align with a plane and perform 2D operations "
|
|
48
|
+
" to work out the area or we could work out the lengths of each side and use Heron's formula which is easier"
|
|
49
|
+
" Using Delaunay traingulation will always produce triangular faces but if dealing with other polygons co-ordinate transfer may be necessary "
|
|
50
|
+
a = _sp.linalg.norm(vab)
|
|
51
|
+
b = _sp.linalg.norm(vbc)
|
|
52
|
+
c = _sp.linalg.norm(vac)
|
|
53
|
+
" Semiperimeter "
|
|
54
|
+
s = 0.5*(a+b+c)
|
|
55
|
+
face_area = _sp.sqrt(s*(s-a)*(s-b)*(s-c))
|
|
56
|
+
" Now the volume of the pyramid section defined by the 3 face points and the hull centroid can be calculated "
|
|
57
|
+
pyramid_volume = _sp.absolute(_sp.dot(face_centroid_vector,face_unit_normal)*face_area/3)
|
|
58
|
+
" Each pyramid is summed together to calculate the total volume "
|
|
59
|
+
hull_volume += pyramid_volume
|
|
60
|
+
" The Centre of Mass will not be the same as the geometrical centroid "
|
|
61
|
+
" A weighted adjustment can be calculated from the pyramid centroid and volume "
|
|
62
|
+
vha = points[ia]-hull_centroid
|
|
63
|
+
vhb = points[ib]-hull_centroid
|
|
64
|
+
vhc = points[ic]-hull_centroid
|
|
65
|
+
pCOM = ((vha+vhb+vhc)/4)*pyramid_volume
|
|
66
|
+
pyramid_COMs.append(pCOM)
|
|
67
|
+
if _sp.isnan(hull_volume):
|
|
68
|
+
hull_volume = 0.0
|
|
69
|
+
if hull_volume>0:
|
|
70
|
+
hull_COM = hull_centroid + _sp.mean(_sp.asarray(pyramid_COMs),axis=0)/hull_volume
|
|
71
|
+
else:
|
|
72
|
+
hull_COM = hull_centroid
|
|
73
|
+
|
|
74
|
+
return hull_volume, hull_COM
|
|
75
|
+
|
|
76
|
+
def sphere(geometry,
|
|
77
|
+
pore_diameter='pore.diameter',
|
|
78
|
+
**kwargs):
|
|
79
|
+
r"""
|
|
80
|
+
Calculate pore volume from diameter for a spherical pore body
|
|
81
|
+
"""
|
|
82
|
+
diams = geometry[pore_diameter]
|
|
83
|
+
value=_sp.pi/6*diams**3
|
|
84
|
+
return value
|
|
85
|
+
|
|
86
|
+
def cube(geometry,
|
|
87
|
+
pore_diameter='pore.diameter',
|
|
88
|
+
**kwargs):
|
|
89
|
+
r"""
|
|
90
|
+
Calculate pore volume from diameter for a cubic pore body
|
|
91
|
+
"""
|
|
92
|
+
diams = geometry[pore_diameter]
|
|
93
|
+
value = diams**3
|
|
94
|
+
return value
|
|
95
|
+
|
|
96
|
+
def voronoi(network,
|
|
97
|
+
geometry,
|
|
98
|
+
**kwargs):
|
|
99
|
+
r"""
|
|
100
|
+
Calculate volume from the convex hull of the offset vertices making the throats surrounding the pore
|
|
101
|
+
Also calculate the centre of mass for the volume
|
|
102
|
+
"""
|
|
103
|
+
pores = geometry.map_pores(network,geometry.pores())
|
|
104
|
+
Np = len(pores)
|
|
105
|
+
volume = _sp.zeros(Np)
|
|
106
|
+
com = _sp.zeros([Np,3])
|
|
107
|
+
for i in range(Np):
|
|
108
|
+
throat_vert_list = []
|
|
109
|
+
net_throats=network.find_neighbor_throats([pores[i]])
|
|
110
|
+
geom_throats = network.map_throats(target=geometry,throats=net_throats,return_mapping=True)['target']
|
|
111
|
+
if len(geom_throats) > 1:
|
|
112
|
+
for throat in geom_throats:
|
|
113
|
+
geom_throat_verts = geometry["throat.offset_vertices"][throat]
|
|
114
|
+
if geom_throat_verts is not None:
|
|
115
|
+
for j in range(len(geom_throat_verts)):
|
|
116
|
+
throat_vert_list.append(geom_throat_verts[j])
|
|
117
|
+
throat_array=_sp.asarray(throat_vert_list)
|
|
118
|
+
if len(throat_array)>4:
|
|
119
|
+
volume[i],com[i] = _get_hull_volume(throat_array)
|
|
120
|
+
else:
|
|
121
|
+
volume[i]=0
|
|
122
|
+
elif len(geom_throats) == 1 and 'throat.centroid' in geometry.props():
|
|
123
|
+
com[i]=geometry['throat.centroid'][geom_throats]
|
|
124
|
+
volume[i]=0
|
|
125
|
+
"Find any pores with centroids at origin and use the mean of the pore vertices instead"
|
|
126
|
+
"Not doing this messes up hydraulic conductances using centre to centre"
|
|
127
|
+
ps = np.where(~com.any(axis=1))[0]
|
|
128
|
+
if len(ps) >0:
|
|
129
|
+
for pore in ps:
|
|
130
|
+
com[pore]=np.mean(geometry["pore.vertices"][pore],axis=0)
|
|
131
|
+
geometry["pore.centroid"]=com
|
|
132
|
+
|
|
133
|
+
return volume
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
r"""
|
|
2
|
+
===============================================================================
|
|
3
|
+
Submodule -- throat_area
|
|
4
|
+
===============================================================================
|
|
5
|
+
|
|
6
|
+
"""
|
|
7
|
+
import scipy as _sp
|
|
8
|
+
import OpenPNM.Utilities.transformations as tr
|
|
9
|
+
import OpenPNM.Utilities.vertexops as vo
|
|
10
|
+
|
|
11
|
+
def cylinder(geometry,
|
|
12
|
+
throat_diameter='throat.diameter',
|
|
13
|
+
**kwargs):
|
|
14
|
+
r"""
|
|
15
|
+
Calculate throat area for a cylindrical throat
|
|
16
|
+
"""
|
|
17
|
+
diams = geometry[throat_diameter]
|
|
18
|
+
value = _sp.constants.pi/4*(diams)**2
|
|
19
|
+
return value
|
|
20
|
+
|
|
21
|
+
def cuboid(geometry,
|
|
22
|
+
throat_diameter='throat.diameter',
|
|
23
|
+
**kwargs):
|
|
24
|
+
r"""
|
|
25
|
+
Calculate throat area for a cuboid throat
|
|
26
|
+
"""
|
|
27
|
+
diams = geometry[throat_diameter]
|
|
28
|
+
value = (diams)**2
|
|
29
|
+
return value
|
|
30
|
+
|
|
31
|
+
def voronoi(geometry,
|
|
32
|
+
**kwargs):
|
|
33
|
+
r"""
|
|
34
|
+
Use the Voronoi verts and throat normals to work out the area
|
|
35
|
+
"""
|
|
36
|
+
Nt = geometry.num_throats()
|
|
37
|
+
verts = geometry['throat.offset_vertices']
|
|
38
|
+
normals = geometry['throat.normal']
|
|
39
|
+
area = _sp.ndarray(Nt)
|
|
40
|
+
for i in range(Nt):
|
|
41
|
+
if len(verts[i]) > 2:
|
|
42
|
+
verts_2D = tr.rotate_and_chop(verts[i],normals[i],[0,0,1])
|
|
43
|
+
area[i] = vo.PolyArea2D(verts_2D)
|
|
44
|
+
else:
|
|
45
|
+
area[i] = 0.0
|
|
46
|
+
|
|
47
|
+
return area
|