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,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
|