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.
Files changed (108) hide show
  1. OpenPNM-1.1/MANIFEST.in +2 -0
  2. OpenPNM-1.1/OpenPNM/Algorithms/__FickianDiffusion__.py +67 -0
  3. OpenPNM-1.1/OpenPNM/Algorithms/__FourierConduction__.py +63 -0
  4. OpenPNM-1.1/OpenPNM/Algorithms/__GenericAlgorithm__.py +235 -0
  5. OpenPNM-1.1/OpenPNM/Algorithms/__GenericLinearTransport__.py +641 -0
  6. OpenPNM-1.1/OpenPNM/Algorithms/__InvasionPercolationForImbibition__.py +703 -0
  7. OpenPNM-1.1/OpenPNM/Algorithms/__InvasionPercolationTimed__.py +702 -0
  8. OpenPNM-1.1/OpenPNM/Algorithms/__InvasionPercolation__.py +156 -0
  9. OpenPNM-1.1/OpenPNM/Algorithms/__OhmicConduction__.py +64 -0
  10. OpenPNM-1.1/OpenPNM/Algorithms/__OrdinaryPercolation__.py +402 -0
  11. OpenPNM-1.1/OpenPNM/Algorithms/__StokesFlow__.py +64 -0
  12. OpenPNM-1.1/OpenPNM/Algorithms/__Tortuosity__.py +91 -0
  13. OpenPNM-1.1/OpenPNM/Algorithms/__init__.py +48 -0
  14. OpenPNM-1.1/OpenPNM/Base/__Controller__.py +480 -0
  15. OpenPNM-1.1/OpenPNM/Base/__Core__.py +1522 -0
  16. OpenPNM-1.1/OpenPNM/Base/__ModelsDict__.py +345 -0
  17. OpenPNM-1.1/OpenPNM/Base/__Tools__.py +72 -0
  18. OpenPNM-1.1/OpenPNM/Base/__init__.py +32 -0
  19. OpenPNM-1.1/OpenPNM/Geometry/__Boundary__.py +80 -0
  20. OpenPNM-1.1/OpenPNM/Geometry/__Cube_and_Cuboid__.py +64 -0
  21. OpenPNM-1.1/OpenPNM/Geometry/__GenericGeometry__.py +106 -0
  22. OpenPNM-1.1/OpenPNM/Geometry/__SGL10__.py +67 -0
  23. OpenPNM-1.1/OpenPNM/Geometry/__Stick_and_Ball__.py +68 -0
  24. OpenPNM-1.1/OpenPNM/Geometry/__TestGeometry__.py +51 -0
  25. OpenPNM-1.1/OpenPNM/Geometry/__Toray090__.py +68 -0
  26. OpenPNM-1.1/OpenPNM/Geometry/__Voronoi__.py +98 -0
  27. OpenPNM-1.1/OpenPNM/Geometry/__init__.py +47 -0
  28. OpenPNM-1.1/OpenPNM/Geometry/models/__init__.py +33 -0
  29. OpenPNM-1.1/OpenPNM/Geometry/models/pore_area.py +27 -0
  30. OpenPNM-1.1/OpenPNM/Geometry/models/pore_centroid.py +35 -0
  31. OpenPNM-1.1/OpenPNM/Geometry/models/pore_diameter.py +127 -0
  32. OpenPNM-1.1/OpenPNM/Geometry/models/pore_misc.py +55 -0
  33. OpenPNM-1.1/OpenPNM/Geometry/models/pore_seed.py +212 -0
  34. OpenPNM-1.1/OpenPNM/Geometry/models/pore_surface_area.py +28 -0
  35. OpenPNM-1.1/OpenPNM/Geometry/models/pore_vertices.py +19 -0
  36. OpenPNM-1.1/OpenPNM/Geometry/models/pore_volume.py +133 -0
  37. OpenPNM-1.1/OpenPNM/Geometry/models/throat_area.py +47 -0
  38. OpenPNM-1.1/OpenPNM/Geometry/models/throat_centroid.py +80 -0
  39. OpenPNM-1.1/OpenPNM/Geometry/models/throat_diameter.py +106 -0
  40. OpenPNM-1.1/OpenPNM/Geometry/models/throat_length.py +95 -0
  41. OpenPNM-1.1/OpenPNM/Geometry/models/throat_misc.py +42 -0
  42. OpenPNM-1.1/OpenPNM/Geometry/models/throat_normal.py +31 -0
  43. OpenPNM-1.1/OpenPNM/Geometry/models/throat_offset_vertices.py +191 -0
  44. OpenPNM-1.1/OpenPNM/Geometry/models/throat_perimeter.py +26 -0
  45. OpenPNM-1.1/OpenPNM/Geometry/models/throat_seed.py +12 -0
  46. OpenPNM-1.1/OpenPNM/Geometry/models/throat_shape_factor.py +37 -0
  47. OpenPNM-1.1/OpenPNM/Geometry/models/throat_surface_area.py +44 -0
  48. OpenPNM-1.1/OpenPNM/Geometry/models/throat_vector.py +27 -0
  49. OpenPNM-1.1/OpenPNM/Geometry/models/throat_vertices.py +19 -0
  50. OpenPNM-1.1/OpenPNM/Geometry/models/throat_volume.py +45 -0
  51. OpenPNM-1.1/OpenPNM/Network/__Cubic__.py +316 -0
  52. OpenPNM-1.1/OpenPNM/Network/__DelaunayCubic__.py +127 -0
  53. OpenPNM-1.1/OpenPNM/Network/__Delaunay__.py +600 -0
  54. OpenPNM-1.1/OpenPNM/Network/__GenericNetwork__.py +1184 -0
  55. OpenPNM-1.1/OpenPNM/Network/__MatFile__.py +331 -0
  56. OpenPNM-1.1/OpenPNM/Network/__TestNet__.py +109 -0
  57. OpenPNM-1.1/OpenPNM/Network/__init__.py +40 -0
  58. OpenPNM-1.1/OpenPNM/Network/models/__init__.py +12 -0
  59. OpenPNM-1.1/OpenPNM/Network/models/pore_topology.py +106 -0
  60. OpenPNM-1.1/OpenPNM/Phases/__Air__.py +63 -0
  61. OpenPNM-1.1/OpenPNM/Phases/__GenericPhase__.py +146 -0
  62. OpenPNM-1.1/OpenPNM/Phases/__Mercury__.py +71 -0
  63. OpenPNM-1.1/OpenPNM/Phases/__TestPhase__.py +46 -0
  64. OpenPNM-1.1/OpenPNM/Phases/__Water__.py +56 -0
  65. OpenPNM-1.1/OpenPNM/Phases/__init__.py +38 -0
  66. OpenPNM-1.1/OpenPNM/Phases/models/__init__.py +22 -0
  67. OpenPNM-1.1/OpenPNM/Phases/models/contact_angle.py +34 -0
  68. OpenPNM-1.1/OpenPNM/Phases/models/density.py +81 -0
  69. OpenPNM-1.1/OpenPNM/Phases/models/diffusivity.py +95 -0
  70. OpenPNM-1.1/OpenPNM/Phases/models/electrical_conductivity.py +10 -0
  71. OpenPNM-1.1/OpenPNM/Phases/models/misc.py +125 -0
  72. OpenPNM-1.1/OpenPNM/Phases/models/molar_density.py +69 -0
  73. OpenPNM-1.1/OpenPNM/Phases/models/molar_mass.py +31 -0
  74. OpenPNM-1.1/OpenPNM/Phases/models/surface_tension.py +104 -0
  75. OpenPNM-1.1/OpenPNM/Phases/models/thermal_conductivity.py +98 -0
  76. OpenPNM-1.1/OpenPNM/Phases/models/vapor_pressure.py +69 -0
  77. OpenPNM-1.1/OpenPNM/Phases/models/viscosity.py +103 -0
  78. OpenPNM-1.1/OpenPNM/Physics/__GenericPhysics__.py +111 -0
  79. OpenPNM-1.1/OpenPNM/Physics/__Standard__.py +51 -0
  80. OpenPNM-1.1/OpenPNM/Physics/__TestPhysics__.py +50 -0
  81. OpenPNM-1.1/OpenPNM/Physics/__init__.py +30 -0
  82. OpenPNM-1.1/OpenPNM/Physics/models/__init__.py +18 -0
  83. OpenPNM-1.1/OpenPNM/Physics/models/capillary_pressure.py +122 -0
  84. OpenPNM-1.1/OpenPNM/Physics/models/diffusive_conductance.py +82 -0
  85. OpenPNM-1.1/OpenPNM/Physics/models/electrical_conductance.py +59 -0
  86. OpenPNM-1.1/OpenPNM/Physics/models/generic_source_term.py +564 -0
  87. OpenPNM-1.1/OpenPNM/Physics/models/hydraulic_conductance.py +76 -0
  88. OpenPNM-1.1/OpenPNM/Physics/models/multiphase.py +133 -0
  89. OpenPNM-1.1/OpenPNM/Physics/models/thermal_conductance.py +67 -0
  90. OpenPNM-1.1/OpenPNM/Postprocessing/Graphics.py +251 -0
  91. OpenPNM-1.1/OpenPNM/Postprocessing/Plots.py +369 -0
  92. OpenPNM-1.1/OpenPNM/Postprocessing/__init__.py +10 -0
  93. OpenPNM-1.1/OpenPNM/Utilities/IO.py +277 -0
  94. OpenPNM-1.1/OpenPNM/Utilities/Shortcuts.py +17 -0
  95. OpenPNM-1.1/OpenPNM/Utilities/__init__.py +16 -0
  96. OpenPNM-1.1/OpenPNM/Utilities/misc.py +226 -0
  97. OpenPNM-1.1/OpenPNM/Utilities/transformations.py +1923 -0
  98. OpenPNM-1.1/OpenPNM/Utilities/vertexops.py +824 -0
  99. OpenPNM-1.1/OpenPNM/__init__.py +56 -0
  100. OpenPNM-1.1/OpenPNM.egg-info/PKG-INFO +11 -0
  101. OpenPNM-1.1/OpenPNM.egg-info/SOURCES.txt +107 -0
  102. OpenPNM-1.1/OpenPNM.egg-info/dependency_links.txt +1 -0
  103. OpenPNM-1.1/OpenPNM.egg-info/requires.txt +1 -0
  104. OpenPNM-1.1/OpenPNM.egg-info/top_level.txt +1 -0
  105. OpenPNM-1.1/PKG-INFO +11 -0
  106. OpenPNM-1.1/README.txt +88 -0
  107. OpenPNM-1.1/setup.cfg +7 -0
  108. 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