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,824 @@
1
+ import numpy as np
2
+ from scipy.spatial import ConvexHull
3
+ from OpenPNM.Utilities import transformations as tr
4
+ from OpenPNM.Utilities import misc as misc
5
+ from math import atan2
6
+
7
+ def get_throat_geom(verts,normal,fibre_rad):
8
+ r"""
9
+ For one set of vertices defining a throat return the key properties
10
+ This is the main loop for calling other sub-routines.
11
+ General Method:
12
+ For each connection or throat defined by the shared vertices
13
+ Rotate the vertices to align with the xy-plane and get rid of z-coordinate
14
+ Compute the convex hull of the 2D points giving a set of simplices which define neighbouring vertices in a clockwise fashion
15
+ For each triplet calculate the offset position given the fibre radius
16
+ Check for overlapping vertices and ones that lie outside the original hull - recalculate position to offset from or ignore if all overlapping
17
+ Calculate Area and Perimeter if successfully generated offset vertices to replicate eroded throat
18
+ Translate back into 3D
19
+ Any Errors encountered result in the throat area being zero and no vertices being passed back
20
+ These Errors are not coding mistakes but failures to obtain an eroded facet with non-zero area:
21
+ Error 1: Less than 3 vertices in the convex hull - Should never happen (unless 2 points are incredibly close together)
22
+ Error 2: The largest span of points is less than twice the fibre radius (i.e. throat will definitley be occluded)
23
+ Error 3: All the offset vertices overlap with at least one other vertex - Throat fully occluded
24
+ Error 4: Not enough offset vertices to continue - Throat fully occluded
25
+ Error 5: An offset vertex is outside the original set of points - Throat fully occluded
26
+ """
27
+ z_axis = [0,0,1]
28
+ throat_area = 0.0
29
+ throat_perimeter = 0.0
30
+ throat_COM = np.zeros([1,3])
31
+ output_offset = []
32
+ Error = 0
33
+ " For boundaries some facets will already be aligned with the axis - if this is the case a rotation is unnecessary and could also cause problems "
34
+ angle = tr.angle_between_vectors(normal,z_axis)
35
+ if (angle==0.0)or(angle==np.pi):
36
+ "We are already aligned"
37
+ rotate_input = False
38
+ facet = verts
39
+ else:
40
+ rotate_input = True
41
+ M = tr.rotation_matrix(tr.angle_between_vectors(normal,z_axis),tr.vector_product(normal,z_axis))
42
+ facet = np.dot(verts,M[:3,:3].T)
43
+ x = facet[:,0]
44
+ y = facet[:,1]
45
+ z = facet[:,2]
46
+ " Work out span of points and set axes scales to cover this and be equal in both dimensions "
47
+ x_range = x.max() - x.min()
48
+ y_range = y.max() - y.min()
49
+ if (x_range > y_range):
50
+ my_range = x_range
51
+ else:
52
+ my_range = y_range
53
+ if (np.around(z.std(),3)!=0.000):
54
+ print("Rotation failed")
55
+ facet_coords_2D = np.column_stack((x,y))
56
+ hull = ConvexHull(facet_coords_2D,qhull_options='QJ Pp')
57
+ verts_2D = facet_coords_2D[hull.vertices]
58
+ offset = outer_offset(verts_2D,fibre_rad)
59
+ " At this point we may have overlapping areas for which we need to offset from a new point "
60
+ overlap_array,sweep_radius,line_points = set_overlap(verts_2D,offset)
61
+ #first_array = overlap_array
62
+ temp_vert_list=[]
63
+ #new_vert_list=[]
64
+ if (len(verts_2D) <3):
65
+ "Error: Fused Too Many Verts"
66
+ Error = 1
67
+ elif(my_range < fibre_rad*2):
68
+ "Error: Facet Too small to Erode"
69
+ Error = 2
70
+ else:
71
+ if overlap_array.any()==False:
72
+ " If no overlaps don't worry"
73
+ "no overlaps"
74
+ elif all_overlap(overlap_array)==True:
75
+ " If all overlaps then throat is fully occluded"
76
+ "Error: Throat fully occluded"
77
+ Error = 3
78
+ else:
79
+ " If one or two sets of overlaps exist and at least one vertex is not overlapped then we need to do a bit more work "
80
+ " Do some linalg to find a new point to offset from saving un-overlapped verts and newly created verts in a temporary list "
81
+ count = 0
82
+ temp_verts = verts_2D
83
+ while True:
84
+ temp_vert_list=[]
85
+ for i in range(np.shape(line_points)[0]):
86
+ if np.sum(overlap_array[i])==0.0:
87
+ temp_vert_list.append(temp_verts[i])
88
+ else:
89
+ my_lines=[]
90
+ for j in range(np.shape(line_points)[0]):
91
+
92
+ if overlap_array[i][j] ==1 and overlap_array[j][i]==1:
93
+ list_a = line_points[i][j]
94
+ list_b = line_points[j][i]
95
+ my_lines = symmetric_difference(list_a,list_b)
96
+
97
+ my_lines=np.asarray(my_lines)
98
+
99
+ if len(my_lines)==2:
100
+ try:
101
+ quad_points=temp_verts[my_lines]
102
+ my_new_point = new_point(quad_points)
103
+ temp_vert_list.append(my_new_point)
104
+ except IndexError:
105
+ print("IndexError: "+str(my_lines))
106
+ except TypeError:
107
+ print("TypeError: "+str(my_lines))
108
+
109
+ #new_vert_list.append(my_new_point)
110
+
111
+ temp_verts=np.asarray(misc.unique_list(temp_vert_list))
112
+ #new_vert_list=np.asarray(self._unique_list(new_vert_list))
113
+ #if len(verts_2D) >=3:
114
+ offset = outer_offset(temp_verts,fibre_rad)
115
+ overlap_array,sweep_radius,line_points = set_overlap(temp_verts,offset)
116
+ #else:
117
+ #Error = 4
118
+ if overlap_array.any()==False:
119
+ break
120
+ elif all_overlap(overlap_array)==True:
121
+ Error = 3
122
+ break
123
+ elif len(temp_verts) <3:
124
+ Error = 4
125
+ break
126
+ else:
127
+ count+=1
128
+ temp_verts = np.asarray(fuse_verts(verts=temp_verts,percentage=0.05*count))
129
+ offset = outer_offset(temp_verts,fibre_rad)
130
+ overlap_array,sweep_radius,line_points = set_overlap(temp_verts,offset)
131
+ " Continue Looping until one of the above conditions is true or counter reaches 10"
132
+ if count >= 10:
133
+ break
134
+
135
+ if len(offset) >= 3 and Error == 0:
136
+ " Now also check whether any of the offset points lie outside the original convex hull "
137
+ original_area = np.around(PolyArea2D(verts_2D),10)
138
+ all_points = np.concatenate((verts_2D,offset),axis=0)
139
+ try:
140
+ total_hull = ConvexHull(all_points,qhull_options='QJ Pp') #ignores very small angles
141
+ total_area = np.around(PolyArea2D(all_points[total_hull.vertices]),10)
142
+ except np.spatial.qhull.QhullError:
143
+ print(all_points)
144
+ total_area =999
145
+ Error = 5
146
+ #total_area=0
147
+ offset_hull = ConvexHull(offset,qhull_options='QJ Pp')
148
+ offset_verts_2D = offset[offset_hull.vertices]
149
+ if (total_area>original_area): # Throat is fully occluded
150
+ " Don't do anything "
151
+ if Error != 5:
152
+ Error = 6
153
+ #print("First Array")
154
+ #print(first_array)
155
+ #print("Second Array")
156
+ #print(overlap_array)
157
+ else:
158
+ throat_area = PolyArea2D(offset_verts_2D)
159
+ throat_perimeter = PolyPerimeter2D(offset_verts_2D)
160
+ throat_COM_2D = PolyWeightedCentroid2D(offset_verts_2D)
161
+ throat_COM = np.hstack((throat_COM_2D,z[0]))
162
+ " Make 3D again in rotated plane "
163
+ offset_verts_3D = np.column_stack((offset_verts_2D,z[0:len(offset_verts_2D)]))
164
+ " Get matrix to un-rotate the co-ordinates back to the original orientation if we rotated in the first place"
165
+ if (rotate_input):
166
+ M1 = tr.inverse_matrix(M)
167
+ " Unrotate the offset coordinates "
168
+ output_offset = np.dot(offset_verts_3D,M1[:3,:3].T)
169
+ throat_COM = np.dot(throat_COM,M1[:3,:3].T)
170
+ else:
171
+ output_offset = offset_verts_3D
172
+
173
+ return throat_area, throat_perimeter, output_offset, throat_COM, Error
174
+
175
+ def outer_offset(verts,fibre_rad):
176
+ r"""
177
+ Routine to loop through all verts and calculate offset position based on neighbours either side. Verts must be in hull order
178
+ """
179
+ offset = []
180
+ for i,vert in enumerate(verts):
181
+ " Collect three adjacent points and compute the offset of the first "
182
+ triplet = (vert, np.roll(verts,-1,axis=0)[i],np.roll(verts,1,axis=0)[i])
183
+ offset.append(offset_vertex(triplet,fibre_rad))
184
+ offset = np.asarray(offset)
185
+
186
+ return offset
187
+
188
+ def offset_vertex(points,rad = 0.01):
189
+ " We are passed in a set of 3 points forming vertices of two adjoining simplexes of the convex hull of a voronoi facet "
190
+ " We need to offset the vertices normal to the fibre direction (or adjoining vectors) by the fibre radius "
191
+ " This is achieved by finding the half angle between the two adjoining vectors and a direction "
192
+ " Mid-point must be the first in the array "
193
+ p0 = np.array(points[0])
194
+ p1 = np.array(points[1])
195
+ p2 = np.array(points[2])
196
+ " Now make the midpoint the origin "
197
+ vector1 = p1-p0
198
+ vector2 = p2-p0
199
+
200
+ " Find what quadrant the vector is pointing in - atan2 function takes account of signs "
201
+ " 0 means aligned with x-axis, pi is aligned with -xaxis, positive numbers are positive y and negative numbers are negative y "
202
+ " The angle between the vectors should always be within 180 degrees of one another in a convex hull "
203
+
204
+ q1 = atan2(vector1[1],vector1[0])
205
+ q2 = atan2(vector2[1],vector2[0])
206
+ alpha = 0.5*tr.angle_between_vectors(vector1,vector2)
207
+
208
+ " We always want to offset from the first vertex we get to - going anti-clockwise from the x-axis "
209
+ " Check if both vectors point up or both point down - if so the first one we get to will have smaller q value "
210
+ if q1*q2 >=0.0:
211
+ if q1<q2:
212
+ theta = q1
213
+ else:
214
+ theta = q2
215
+ else:
216
+ "if vector 1 is more rotated away from positive xaxis than vector 2 is rotated away from negative xaxis - use it"
217
+ " and vice-versa "
218
+ if (abs(q1)+abs(q2)>np.pi):
219
+ "vectors are pointing negative x so take whichever has positive q-value - like a pacman facing left"
220
+ if q1>=0:
221
+ theta = q1
222
+ else:
223
+ theta = q2
224
+ else:
225
+ "vectors are pointing positive x so take whichever is negative"
226
+ if q1<=0:
227
+ theta = q1
228
+ else:
229
+ theta = q2
230
+
231
+ if alpha == 0: # this may cause problems in terms of which way to offset!!!
232
+ #x = rad*np.cos(theta)
233
+ #y = rad*np.sin(theta)
234
+ x=0
235
+ y=0
236
+ else:
237
+ x = rad*np.cos(alpha+theta)/np.sin(alpha)
238
+ y = rad*np.sin(alpha+theta)/np.sin(alpha)
239
+
240
+ "Add the midpoint back in"
241
+ output = [x+p0[0],y+p0[1]]
242
+
243
+ return output
244
+
245
+ def dist2(p1, p2):
246
+ r"""
247
+ Pythagoras to compute the square of the distance between two points (in 2D)
248
+ """
249
+ return (p1[0]-p2[0])**2 + (p1[1]-p2[1])**2
250
+
251
+ def fuse(points, d):
252
+ r"""
253
+ Fuse points together wihin a certain range
254
+ """
255
+ ret = []
256
+ d2 = d * d
257
+ n = len(points)
258
+ taken = [False] * n
259
+ for i in range(n):
260
+ if not taken[i]:
261
+ count = 1
262
+ point = [points[i][0], points[i][1]]
263
+ taken[i] = True
264
+ for j in range(i+1, n):
265
+ if dist2(points[i], points[j]) < d2:
266
+ point[0] += points[j][0]
267
+ point[1] += points[j][1]
268
+ count+=1
269
+ taken[j] = True
270
+ point[0] /= count
271
+ point[1] /= count
272
+ ret.append((point[0], point[1]))
273
+ return ret
274
+
275
+ def fuse_verts(verts,percentage=0.05):
276
+ r"""
277
+ Work out the span of the points and therefore the range for fusing them together then call fuse
278
+ """
279
+ #Work out largest span
280
+ x_span = max(verts[:,0])- min(verts[:,0])
281
+ y_span = max(verts[:,1])- min(verts[:,1])
282
+ if x_span > y_span:
283
+ tolerance = x_span*percentage
284
+ else:
285
+ tolerance = y_span*percentage
286
+ #fuse vertices lying within 5% of the largest span
287
+ return fuse(verts,tolerance)
288
+
289
+ def PolyArea2D(pts):
290
+ r"""
291
+ returns the area of a 2D polygon given the set of points defining the convex hull in correct order
292
+ """
293
+ lines = np.hstack([pts,np.roll(pts,-1,axis=0)])
294
+ area = 0.5*abs(sum(x1*y2-x2*y1 for x1,y1,x2,y2 in lines))
295
+ return area
296
+
297
+ def PolyPerimeter2D(pts):
298
+ r"""
299
+ returns the perimeter of a 2D polygon given the set of points defining the convex hull in correct order
300
+ """
301
+ lines = np.hstack([pts,np.roll(pts,-1,axis=0)])
302
+ perimeter = sum(np.sqrt((x2-x1)**2+(y2-y1)**2) for x1,y1,x2,y2 in lines)
303
+ return perimeter
304
+
305
+ def PolyWeightedCentroid2D(pts):
306
+ r"""
307
+ returns the perimeter of a 2D polygon given the set of points defining the convex hull in correct order
308
+ """
309
+ lines = np.hstack([pts,np.roll(pts,-1,axis=0)])
310
+ twice_area = 0.0
311
+ cx = 0.0
312
+ cy = 0.0
313
+ #perimeter_weighting = np.zeros(len(pts))
314
+ #average_point = np.zeros([len(pts),2])
315
+ for x1,y1,x2,y2 in lines:
316
+ f = x1*y2 - x2*y1
317
+ cx += (x1+x2)*f
318
+ cy += (y1+y2)*f
319
+ twice_area += f
320
+ A = 0.5*(twice_area)
321
+ #A2 = PolyArea2D(pts)
322
+ Cx = cx/(6*A)
323
+ Cy = cy/(6*A)
324
+ #cx = sum(average_point[:,0]*perimeter_weighting)/sum(perimeter_weighting)
325
+ #cy = sum(average_point[:,1]*perimeter_weighting)/sum(perimeter_weighting)
326
+
327
+ return [Cx,Cy]
328
+
329
+ def symmetric_difference(list_a,list_b):
330
+ r"""
331
+ Return the combination of two lists without common elements (necessary as sets cannot contain mutable objects)
332
+ """
333
+ sym_diff=[]
334
+ sorted_list_a = np.sort(list_a)
335
+ sorted_list_b = np.sort(list_b)
336
+ " Add elements in list a if not in list b "
337
+ for element_a in sorted_list_a:
338
+ match = False
339
+ for element_b in sorted_list_b:
340
+ if all(element_a==element_b):
341
+ match = True
342
+ if match==False:
343
+ sym_diff.append(element_a)
344
+ " Add elements in list b if not in list a "
345
+ for element_b in sorted_list_b:
346
+ match = False
347
+ for element_a in sorted_list_a:
348
+ if all(element_a==element_b):
349
+ match = True
350
+ if match==False:
351
+ sym_diff.append(element_b)
352
+ return sym_diff
353
+
354
+ def new_point(pairs):
355
+ r"""
356
+ Passed 2 pairs of points defining lines either side of overlapped offset vertices
357
+ need to calculate the new point to offset from given the orientation of the outer fibres
358
+ """
359
+ m1,c1 = line_equation(pairs[0])
360
+ m2,c2 = line_equation(pairs[1])
361
+
362
+ if (m1 == np.inf):
363
+ "line1 is a straight line x=c1"
364
+ x=c1
365
+ y=(m2*c1)+c2
366
+ elif (m2 == np.inf):
367
+ "line2 is a straight line x=c2"
368
+ x=c2
369
+ y=(m1*c2)+c1
370
+ else:
371
+ try:
372
+ x=(c2-c1)/(m1-m2)
373
+ y=(m1*c2 - m2*c1)/(m1-m2)
374
+ except RuntimeWarning:
375
+ x=0
376
+ y=0
377
+ return x,y
378
+
379
+ def line_equation(points):
380
+ r"""
381
+ Return the gradient and y intercept of a straight line given 2 points
382
+ """
383
+ x_coords, y_coords = zip(*points)
384
+ dy = y_coords[1]-y_coords[0]
385
+ dx = x_coords[1]-x_coords[0]
386
+ if dx==0:
387
+ m=np.inf
388
+ c=x_coords[1]
389
+ else:
390
+ m=dy/dx
391
+ c=y_coords[1] - m*x_coords[1]
392
+
393
+ return m,c
394
+
395
+ def set_overlap(verts,offset):
396
+ r"""
397
+ Given a set of vertices and a set of offset vertices, evaluate whether any of the offset vertices overlap
398
+ This is then used to recalculate points from which to offset
399
+ """
400
+ dim = len(verts)
401
+ overlap_array = np.zeros(shape=(dim,dim))
402
+ sweep_radius = np.zeros(len(verts))
403
+ for i,zone_centre in enumerate(verts):
404
+ sweep_radius[i] = np.sqrt(dist2(zone_centre,offset[i]))
405
+ for j,test_point in enumerate(offset):
406
+ test_radius = np.sqrt(dist2(zone_centre,test_point))
407
+ if (test_radius < sweep_radius[i]):
408
+ overlap_array[i][j]=1
409
+ overlap_array[j][i]=1 # Fill in both so that they are both recalculated later i overlapping j doesn't necessarily mean j overlaps i
410
+ " Join up overlapping regions of points "
411
+ for i in range(dim):
412
+ for j in range(dim): #loop through each element
413
+ if overlap_array[i][j]==1: #if an overlap exist look at what others exist for that vertex
414
+ for k in range(dim):
415
+ if overlap_array[j][k]==1 and k!=i:
416
+ overlap_array[i][k]=1
417
+
418
+ line_points_out=line_points(overlap_array)
419
+
420
+ return overlap_array,sweep_radius,line_points_out
421
+
422
+ def line_points(array):
423
+ r"""
424
+ We are passed a square array containing a list of overlap results. rows represent vertices and columns represent offset vertices
425
+ If an overlap occurs in the span between offset j and offset i then a 1 will result in [i][j]
426
+ As the vertices are in hull order and our aim is to create a new point from which to offset using connected fibres we want to
427
+ identify the correct points to use to define our lines
428
+
429
+ e__________ d
430
+ | | c
431
+ | /
432
+ | /
433
+ | /
434
+ |_/
435
+ a b
436
+ if we have 5 points in a hull a,b,c,d,e where a overlaps with b and c overlaps with d (and visa versa) the array will look like
437
+ [0,1,0,0,0]
438
+ [1,0,0,0,0]
439
+ [0,0,0,1,0]
440
+ [0,0,1,0,0]
441
+ [0,0,0,0,0]
442
+
443
+ This means that c and d are within a fibre's width of each other and the line between them does not represent the fibre
444
+ Instead we want to extend the outer lines (bc and de) to see where they would meet and offset from this point.
445
+ Roll up and down to find the first unoverlapped index from which to start each line from then go back one to get the two
446
+ points to form a line.
447
+ """
448
+ dim=np.shape(array)[0]
449
+ index=range(dim)
450
+ line_points=np.ndarray(shape=[dim,dim],dtype=object)
451
+ for i in range(dim):
452
+ for j in range(dim):
453
+ if array[i][j]==1 and array[j][i]==1:
454
+ " Roll forwards to find the first unoverlapped index"
455
+ k=1
456
+ while k < dim:
457
+ if np.roll(array[i],-k,axis=0)[j]==0:
458
+ break
459
+ else:
460
+ k+=1
461
+ " Save the indices of the first unoverlapped index and the one before to create the line "
462
+ forward_line = [np.roll(index,-k,axis=0)[j],np.roll(index,-(k-1),axis=0)[j]]
463
+ forward_line.sort()
464
+ " Roll backwards to find the first unoverlapped index "
465
+ k=1
466
+ while k < dim:
467
+ if np.roll(array[i],k,axis=0)[j]==0:
468
+ break
469
+ else:
470
+ k+=1
471
+ " Save the indices of the first unoverlapped index and the one before to create the line "
472
+ backward_line = [np.roll(index,k,axis=0)[j],np.roll(index,(k-1),axis=0)[j]]
473
+ backward_line.sort()
474
+ line_points[i][j]=(forward_line,backward_line)
475
+
476
+ return line_points
477
+
478
+ def all_overlap(array):
479
+ r"""
480
+ Find out whether all offset vertices (columns) are overlapped by at least one other
481
+ If so then throat is fully occluded
482
+ """
483
+ dim=np.shape(array)[0]
484
+ overlap=[False]*dim
485
+ all_overlap=False
486
+ for i in range(dim):
487
+ for j in range(dim):
488
+ if array[j][i]==1:
489
+ overlap[i]=True
490
+ if sum(overlap)==dim:
491
+ all_overlap = True
492
+
493
+ return all_overlap
494
+
495
+ def scale(network,scale_factor=[1,1,1],preserve_vol=True):
496
+ r"""
497
+ A method for scaling the coordinates and vertices to create anisotropic networks
498
+ The original domain volume can be preserved by setting preserve_vol = True
499
+
500
+ Example
501
+ ---------
502
+ >>> import OpenPNM
503
+ >>> import OpenPNM.Utilities.vertexops as vo
504
+ >>> import numpy as np
505
+ >>> pn = OpenPNM.Network.Delaunay(num_pores=100, domain_size=[3,2,1])
506
+ >>> pn.add_boundaries()
507
+ >>> B1 = pn.pores("left_boundary")
508
+ >>> B2 = pn.pores("right_boundary")
509
+ >>> Vol = vo.vertex_dimension(pn,B1,B2)
510
+ >>> vo.scale(network=pn,scale_factor=[2,1,1])
511
+ >>> Vol2 = vo.vertex_dimension(pn,B1,B2)
512
+ >>> np.around(Vol-Vol2,5)
513
+ 0.0
514
+ >>> vo.scale(network=pn,scale_factor=[2,1,1],preserve_vol=False)
515
+ >>> Vol3 = vo.vertex_dimension(pn,B1,B2)
516
+ >>> np.around(Vol3/Vol,5)
517
+ 2.0
518
+
519
+ """
520
+ from scipy.special import cbrt
521
+ import scipy as sp
522
+ scale_factor = np.asarray(scale_factor)
523
+ if preserve_vol == True:
524
+ scale_factor = scale_factor/(cbrt(sp.prod(scale_factor)))
525
+ network["pore.coords"]=network["pore.coords"]*scale_factor
526
+ #Cycle through all vertices of all pores updating vertex values
527
+ for pore in network.pores():
528
+ for i,vert in network["pore.vert_index"][pore].items():
529
+ network["pore.vert_index"][pore][i] = network["pore.vert_index"][pore][i]*scale_factor
530
+ #Cycle through all vertices of all throats updating vertex values
531
+ for throat in network.throats():
532
+ for i,vert in network["throat.vert_index"][throat].items():
533
+ network["throat.vert_index"][throat][i] = network["throat.vert_index"][throat][i]*scale_factor
534
+
535
+ def vertex_dimension(network,face1=[],face2=[],parm='volume'):
536
+ r"""
537
+ Return the domain extent based on the vertices
538
+
539
+ This function is better than using the pore coords as they may be far
540
+ away from the original domain size. And will alter the effective
541
+ properties which should be based on the original domain sizes. Takes
542
+ one or two sets of pores and works out different geometric properties
543
+ if "length" is specified and two lists are given the planarity is
544
+ determined and the appropriate length (x,y,z) is returned. It should
545
+ work the same as domain length and area if vertices are not in network
546
+ by using coordinates.
547
+
548
+ Example
549
+ ----------
550
+ >>> import OpenPNM
551
+ >>> import OpenPNM.Utilities.vertexops as vo
552
+ >>> pn = OpenPNM.Network.Delaunay(num_pores=100, domain_size=[3,2,1])
553
+ >>> pn.add_boundaries()
554
+ >>> B1 = pn.pores("left_boundary")
555
+ >>> B2 = pn.pores("right_boundary")
556
+ >>> vo.vertex_dimension(pn,B1,B2,'volume')
557
+ 6.0
558
+ >>> vo.vertex_dimension(pn,B1,B2,'area')
559
+ 3.0
560
+ >>> vo.vertex_dimension(pn,B1,B2,'length')
561
+ 2.0
562
+ >>> vo.vertex_dimension(pn,B1,B2,'area_xy')
563
+ 6.0
564
+ >>> vo.vertex_dimension(pn,B1,B2,'area_yz')
565
+ 2.0
566
+ >>> vo.vertex_dimension(pn,B1,B2,'area_xz')
567
+ 3.0
568
+ >>> vo.vertex_dimension(pn,B1,B2,'minmax')
569
+ [0.0, 3.0, 0.0, 2.0, 0.0, 1.0]
570
+ """
571
+ pores=np.array([],dtype=int)
572
+ if len(face1)>0:
573
+ pores=np.hstack((pores,face1))
574
+ if len(face2)>0:
575
+ pores=np.hstack((pores,face2))
576
+
577
+ face1_coords = network["pore.coords"][face1]
578
+ face2_coords = network["pore.coords"][face2]
579
+ face1_planar = np.zeros(3)
580
+ face2_planar = np.zeros(3)
581
+ planar = np.zeros(3)
582
+ for i in range(3):
583
+ if len(np.unique(face1_coords[:,i]))==1:
584
+ face1_planar[i]=1
585
+ if len(np.unique(face2_coords[:,i]))==1:
586
+ face2_planar[i]=1
587
+ if len(face1)>0 and len(face2)>0:
588
+ planar = face1_planar*face2_planar
589
+ elif len(face1)>0:
590
+ planar = face1_planar
591
+ elif len(face2)>0:
592
+ planar = face2_planar
593
+ else:
594
+ return 0
595
+
596
+ if "pore.vert_index" in network.props():
597
+ verts = []
598
+ for pore in pores:
599
+ for vert in np.asarray(list(network["pore.vert_index"][pore].values())):
600
+ verts.append(vert)
601
+ verts = np.asarray(verts)
602
+ else:
603
+ verts = network["pore.coords"][pores]
604
+
605
+ vx_min = verts[:,0].min()
606
+ vx_max = verts[:,0].max()
607
+ vy_min = verts[:,1].min()
608
+ vy_max = verts[:,1].max()
609
+ vz_min = verts[:,2].min()
610
+ vz_max = verts[:,2].max()
611
+ output = 0
612
+ width = np.around(vx_max-vx_min,10)
613
+ depth = np.around(vy_max-vy_min,10)
614
+ height = np.around(vz_max-vz_min,10)
615
+
616
+ if parm == 'volume':
617
+ output = width*depth*height
618
+ elif parm == 'area_xy' or (parm == 'area' and planar[2]==1):
619
+ output = width*depth
620
+ elif parm == 'area_xz' or (parm == 'area' and planar[1]==1):
621
+ output = width*height
622
+ elif parm == 'area_yz' or (parm == 'area' and planar[0]==1):
623
+ output = depth*height
624
+ elif parm == 'length_x' or (parm == 'length' and planar[0]==1):
625
+ output = width
626
+ elif parm == 'length_y'or (parm == 'length' and planar[1]==1):
627
+ output = depth
628
+ elif parm == 'length_z'or (parm == 'length' and planar[2]==1):
629
+ output = height
630
+ elif parm == 'minmax':
631
+ output = [vx_min,vx_max,vy_min,vy_max,vz_min,vz_max]
632
+
633
+ return output
634
+
635
+ def porosity(network):
636
+ r"""
637
+ Return the porosity of the domain - sum of the pore volumes divided by domain volume
638
+ """
639
+ domain_vol=vertex_dimension(network,network.pores(),parm='volume')
640
+ try:
641
+ pore_vol=sum(network["pore.volume"])
642
+ except KeyError:
643
+ print("Geometries must be assigned first")
644
+ pore_vol=0
645
+ porosity = pore_vol/domain_vol
646
+ return porosity
647
+
648
+ def pore2centroid(network):
649
+ r"""
650
+ Move the pore coordinate to the centroid of the pore vertices
651
+ """
652
+ for geom_name in network.geometries():
653
+ geometry = network.geometries(geom_name)[0]
654
+ if "pore.centroid" in geometry.props():
655
+ net_pores,geom_pores = geometry.map_pores(network,geometry.pores(),True).values()
656
+ for i in range(len(geom_pores)):
657
+ network["pore.coords"][net_pores[i]]=geometry["pore.centroid"][geom_pores[i]]
658
+
659
+ def tortuosity(network=None):
660
+ r"""
661
+ Calculate the tortuosity from the angle between throat vectors and principle axes
662
+ """
663
+ conns = network["throat.conns"]
664
+ va = network["throat.centroid"] - network["pore.centroid"][conns[:,0]]
665
+ vb = network["throat.centroid"] - network["pore.centroid"][conns[:,1]]
666
+ x = [1,0,0]
667
+ y = [0,1,0]
668
+ z = [0,0,1]
669
+ f = 180/np.pi
670
+ theta_x_a = tr.angle_between_vectors(va,x,directed=False,axis=1)
671
+ theta_x_b = tr.angle_between_vectors(vb,x,directed=False,axis=1)
672
+ theta_x = (np.mean(theta_x_a[~np.isnan(theta_x_a)])+np.mean(theta_x_b[~np.isnan(theta_x_b)]))/2
673
+ theta_y_a = tr.angle_between_vectors(va,y,directed=False,axis=1)
674
+ theta_y_b = tr.angle_between_vectors(vb,y,directed=False,axis=1)
675
+ theta_y = (np.mean(theta_y_a[~np.isnan(theta_y_a)])+np.mean(theta_y_b[~np.isnan(theta_y_b)]))/2
676
+ theta_z_a = tr.angle_between_vectors(va,z,directed=False,axis=1)
677
+ theta_z_b = tr.angle_between_vectors(vb,z,directed=False,axis=1)
678
+ theta_z = (np.mean(theta_z_a[~np.isnan(theta_z_a)])+np.mean(theta_z_b[~np.isnan(theta_z_b)]))/2
679
+ tot_angle = (theta_x+theta_y+theta_z)*f
680
+ if tot_angle>180:
681
+ print("something is wrong: " +str(tot_angle))
682
+
683
+ return 1/np.cos(np.array([theta_x,theta_y,theta_z]))
684
+
685
+ def print_throat(geom,throats_in):
686
+ r"""
687
+ Print a given throat or list of throats accepted as [1,2,3,...,n]
688
+ e.g geom.print_throat([34,65,99])
689
+ Original vertices plus offset vertices are rotated to align with
690
+ the z-axis and then printed in 2D
691
+ """
692
+ import matplotlib.pyplot as plt
693
+ throats = []
694
+ for throat in throats_in:
695
+ if throat in range(geom.num_throats()):
696
+ throats.append(throat)
697
+ else:
698
+ print("Throat: "+str(throat)+ " not part of geometry")
699
+ if len(throats) > 0:
700
+ verts = geom['throat.vertices'][throats]
701
+ offsets = geom['throat.offset_vertices'][throats]
702
+ #image_offsets = geom['throat.image_analysis'][throats]
703
+ normals = geom['throat.normal'][throats]
704
+ coms = geom['throat.centroid'][throats]
705
+ incentre = geom['throat.incentre'][throats]
706
+ inradius = 0.5*geom['throat.indiameter'][throats]
707
+ for i in range(len(throats)):
708
+ fig = plt.figure()
709
+ vert_2D = tr.rotate_and_chop(verts[i],normals[i],[0,0,1])
710
+ hull = ConvexHull(vert_2D,qhull_options='QJ Pp')
711
+ for simplex in hull.simplices:
712
+ plt.plot(vert_2D[simplex,0], vert_2D[simplex,1], 'k-',linewidth=2)
713
+ plt.scatter(vert_2D[:,0], vert_2D[:,1])
714
+ #centroid = vo.PolyWeightedCentroid2D(vert_2D[hull.vertices])
715
+ offset_2D = tr.rotate_and_chop(offsets[i],normals[i],[0,0,1])
716
+ offset_hull = ConvexHull(offset_2D,qhull_options='QJ Pp')
717
+ for simplex in offset_hull.simplices:
718
+ plt.plot(offset_2D[simplex,0], offset_2D[simplex,1], 'g-',linewidth=2)
719
+ plt.scatter(offset_2D[:,0], offset_2D[:,1])
720
+ " Make sure the plot looks nice by finding the greatest range of points and setting the plot to look square"
721
+ xmax = vert_2D[:,0].max()
722
+ xmin = vert_2D[:,0].min()
723
+ ymax = vert_2D[:,1].max()
724
+ ymin = vert_2D[:,1].min()
725
+ x_range = xmax - xmin
726
+ y_range = ymax - ymin
727
+ if (x_range > y_range):
728
+ my_range = x_range
729
+ else:
730
+ my_range = y_range
731
+ lower_bound_x = xmin - my_range*0.5
732
+ upper_bound_x = xmin + my_range*1.5
733
+ lower_bound_y = ymin - my_range*0.5
734
+ upper_bound_y = ymin + my_range*1.5
735
+ plt.axis((lower_bound_x,upper_bound_x,lower_bound_y,upper_bound_y))
736
+ plt.grid(b=True, which='major', color='b', linestyle='-')
737
+ centroid = tr.rotate_and_chop(coms[i],normals[i],[0,0,1])
738
+ incent = tr.rotate_and_chop(incentre[i],normals[i],[0,0,1])
739
+ plt.scatter(centroid[0][0],centroid[0][1])
740
+ #plt.scatter(centroid2[0],centroid2[1],c='r')
741
+ "Plot incircle"
742
+ t = np.linspace(0,2*np.pi,200)
743
+ u = inradius[i]*np.cos(t)+incent[0][0]
744
+ v = inradius[i]*np.sin(t)+incent[0][1]
745
+ plt.plot(u,v,'r-')
746
+ fig.show()
747
+ else:
748
+ print("Please provide throat indices")
749
+
750
+ def print_pore(geom,pores,fig=None,axis_bounds=None):
751
+ r"""
752
+ Print all throats around a given pore or list of pores accepted as [1,2,3,...,n]
753
+ e.g geom.print_pore([34,65,99])
754
+ Original vertices plus offset vertices used to create faces and
755
+ then printed in 3D
756
+ To print all pores (n)
757
+ pore_range = np.arange(0,n-1,1)
758
+ geom.print_pore(pore_range)
759
+ """
760
+ import matplotlib.pyplot as plt
761
+ from mpl_toolkits.mplot3d import Axes3D
762
+ from mpl_toolkits.mplot3d.art3d import Poly3DCollection
763
+ return_fig=False
764
+ if len(pores) > 0:
765
+ net_pores = geom.map_pores(geom._net,pores)
766
+ centroids = geom["pore.centroid"][pores]
767
+ #centroids2 = self["pore.com"][pores]
768
+ #for i,pore in enumerate(pores):
769
+ # centroids[i]=self["pore.centroid"][pore]
770
+ #coords = self._net["pore.coords"][net_pores]
771
+ net_throats = geom._net.find_neighbor_throats(pores=net_pores)
772
+ #for net_throat in net_throats:
773
+ # try:
774
+ # throats.append(geom['throat.map'].tolist().index(net_throat))
775
+ # except ValueError:
776
+ # " Throat not in this geometry "
777
+ throats = geom._net.map_throats(geom,net_throats,return_mapping=True)["target"]
778
+ "Can't create volume from one throat"
779
+ if len(throats)>=1:
780
+ verts = geom['throat.vertices'][throats]
781
+ normals = geom['throat.normal'][throats]
782
+ " Get verts in hull order "
783
+ ordered_verts=[]
784
+ for i in range(len(verts)):
785
+ vert_2D = tr.rotate_and_chop(verts[i],normals[i],[0,0,1])
786
+ hull = ConvexHull(vert_2D,qhull_options='QJ Pp')
787
+ ordered_verts.append(verts[i][hull.vertices])
788
+ offsets = geom['throat.offset_vertices'][throats]
789
+ ordered_offs=[]
790
+ for i in range(len(offsets)):
791
+ offs_2D = tr.rotate_and_chop(offsets[i],normals[i],[0,0,1])
792
+ offs_hull = ConvexHull(offs_2D,qhull_options='QJ Pp')
793
+ ordered_offs.append(offsets[i][offs_hull.vertices])
794
+ "Get domain extents for setting axis "
795
+ if axis_bounds is None:
796
+ [xmin,xmax,ymin,ymax,zmin,zmax]= vertex_dimension(geom._net,pores,parm='minmax')
797
+ else:
798
+ [xmin,xmax,ymin,ymax,zmin,zmax]=axis_bounds
799
+ if fig is None:
800
+ fig = plt.figure()
801
+ else:
802
+ return_fig==True
803
+ ax = fig.gca(projection='3d')
804
+ outer_items = Poly3DCollection(ordered_verts,linewidths=1, alpha=0.2, zsort='min')
805
+ outer_face_colours=[(1, 0, 0, 0.01)]
806
+ outer_items.set_facecolor(outer_face_colours)
807
+ ax.add_collection(outer_items)
808
+ inner_items = Poly3DCollection(ordered_offs,linewidths=1, alpha=0.2, zsort='min')
809
+ inner_face_colours=[(0, 0, 1, 0.01)]
810
+ inner_items.set_facecolor(inner_face_colours)
811
+ ax.add_collection(inner_items)
812
+ ax.set_xlim(xmin,xmax)
813
+ ax.set_ylim(ymin,ymax)
814
+ ax.set_zlim(zmin,zmax)
815
+ #ax.scatter(coords[:,0],coords[:,1],coords[:,2])
816
+ ax.scatter(centroids[:,0],centroids[:,1],centroids[:,2],c='y')
817
+ #ax.scatter(centroids2[:,0],centroids2[:,1],centroids2[:,2],c='g')
818
+ plt.show()
819
+ else:
820
+ print_throat(throats)
821
+ else:
822
+ print("Please provide pore indices")
823
+ if return_fig == True:
824
+ return fig