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,600 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ ===============================================================================
4
+ Delaunay: Generate random networks based on Delaunay Tessellations
5
+ ===============================================================================
6
+
7
+ """
8
+ import sys
9
+ import scipy as sp
10
+ import numpy as np
11
+ import scipy.sparse as sprs
12
+ import scipy.spatial as sptl
13
+ import scipy.ndimage as spim
14
+ from OpenPNM.Network import GenericNetwork
15
+ from OpenPNM.Base import logging
16
+ logger = logging.getLogger(__name__)
17
+ from scipy.spatial import Voronoi
18
+ from scipy import stats as st
19
+ from scipy.special import cbrt
20
+ import OpenPNM.Utilities.vertexops as vo
21
+
22
+ class Delaunay(GenericNetwork):
23
+ r"""
24
+ This class contains the methods for creating a *Delaunay* network topology
25
+ based connecting pores with a Delaunay tessellation.
26
+
27
+ To invoke the actual generation it is necessary to run the `generate` method.
28
+
29
+ Parameters
30
+ ----------
31
+ name : string
32
+ A unique name for the network
33
+
34
+ Examples
35
+ --------
36
+ >>> import OpenPNM
37
+ >>> pn = OpenPNM.Network.Delaunay(num_pores=100, domain_size=[0.0001,0.0001,0.0001])
38
+ >>> pn.num_pores()
39
+ 100
40
+
41
+ """
42
+
43
+ def __init__(self,num_pores=None,domain_size=None,**kwargs):
44
+ '''
45
+ Create Delauny network object
46
+ '''
47
+ super(Delaunay,self).__init__(**kwargs)
48
+ if (num_pores and domain_size) is None:
49
+ num_pores = 1
50
+ domain_size = [1.0,1.0,1.0]
51
+ else:
52
+ self.generate(num_pores,domain_size)
53
+
54
+ def generate(self,num_pores,domain_size):
55
+ r'''
56
+ Method to trigger the generation of the network
57
+
58
+ Parameters
59
+ ----------
60
+ domain_size : list of floats, [Lx,Ly,Lz]
61
+ Bounding cube for internal pore positions
62
+ num_pores : int
63
+ Number of pores to place randomly within domain
64
+
65
+ '''
66
+ logger.info("Start of network topology generation")
67
+ self._generate_setup(num_pores,domain_size)
68
+ self._generate_pores()
69
+ self._generate_throats()
70
+ logger.debug("Network generation complete")
71
+
72
+ def _generate_setup(self,num_pores,domain_size):
73
+ r"""
74
+ Perform applicable preliminary checks and calculations required for generation
75
+ """
76
+ logger.debug("generate_setup: Perform preliminary calculations")
77
+ if domain_size is not None and num_pores is not None:
78
+ self._Lx = domain_size[0]
79
+ self._Ly = domain_size[1]
80
+ self._Lz = domain_size[2]
81
+ self._Np = num_pores
82
+ r'''
83
+ TODO: Fix this, btype should be received as an argument
84
+ '''
85
+ self._btype = [0,0,0]
86
+ else:
87
+ logger.error("domain_size and num_pores must be specified")
88
+ raise Exception('domain_size and num_pores must be specified')
89
+
90
+
91
+ def _generate_pores(self):
92
+ r"""
93
+ Generate the pores with numbering scheme.
94
+ """
95
+ logger.info("Place randomly located pores in the domain")
96
+ #Original Random Point Generator
97
+ #coords = sp.rand(self._Np,3)*[self._Lx,self._Ly,self._Lz]
98
+ #Seeding Code
99
+ coords = np.zeros([self._Np,3])
100
+ #reject points close to boundaries - if False there will be slightly more
101
+ rejection = [False,False,True]
102
+ for j in range(3):
103
+ i = 0
104
+ while i < self._Np:
105
+ coord = np.random.uniform(0,1,1)
106
+ if self._reject(coord) == rejection[j]:
107
+ coords[i][j]=coord
108
+ i += 1
109
+ coords*=np.array([self._Lx,self._Ly,self._Lz])
110
+ #Seeding Code
111
+ #Uniform Random Generator
112
+ #coords = np.array([np.random.uniform(0,self._Lx,self._Np),np.random.uniform(0,self._Ly,self._Np),np.random.uniform(0,self._Lz,self._Np)]).T
113
+
114
+ self['pore.coords'] = coords
115
+ logger.debug("End of method")
116
+
117
+ def _prob_func(self,m):
118
+ a = 35
119
+ b = 0.2
120
+ p = ((m**a) + ((1-m)**a) + (2*b))/(1 + (2*b))
121
+ #p = ((m**a) + b)/(1 + b)
122
+ return p
123
+
124
+ def _reject(self,point):
125
+
126
+ P = self._prob_func(point)
127
+ nrand = np.random.uniform(0,1,1)
128
+ #place more points at the sides of the domain and fewer at the top and bottom
129
+ if P < nrand:
130
+ rejection = True
131
+ else:
132
+ rejection = False
133
+
134
+ return rejection
135
+
136
+ def _generate_throats(self):
137
+ r"""
138
+ Generate the throats connections
139
+ """
140
+ logger.info("Define connections between pores")
141
+ #Np = self._Np
142
+ pts = self['pore.coords']
143
+ Np = len(pts)
144
+ #Generate 6 dummy domains to pad onto each face of real domain
145
+ #This prevents surface pores from making long range connections to each other
146
+
147
+ x,y,z = self["pore.coords"].T
148
+ if x.max() > self._Lx:
149
+ Lx = x.max()*1.05
150
+ else:
151
+ Lx = self._Lx
152
+ if y.max() > self._Ly:
153
+ Ly = y.max()*1.05
154
+ else:
155
+ Ly = self._Ly
156
+ if z.max() > self._Lz:
157
+ Lz = z.max()*1.05
158
+ else:
159
+ Lz = self._Lz
160
+
161
+ #Reflect in X = Lx and 0
162
+ Pxp = pts.copy()
163
+ Pxp[:,0]=(2*Lx-Pxp[:,0])
164
+ Pxm= pts.copy()
165
+ Pxm[:,0] = Pxm[:,0]*(-1)
166
+ #Reflect in Y = Ly and 0
167
+ Pyp = pts.copy()
168
+ Pyp[:,1]=(2*Ly-Pxp[:,1])
169
+ Pym = pts.copy()
170
+ Pym[:,1] = Pxm[:,1]*(-1)
171
+ #Reflect in Z = Lz and 0
172
+ Pzp = pts.copy()
173
+ Pzp[:,2]=(2*Lz-Pxp[:,2])
174
+ Pzm = pts.copy()
175
+ Pzm[:,2] = Pxm[:,2]*(-1)
176
+ #Add dummy domains to real domain
177
+ pts = np.vstack((pts,Pxp,Pxm,Pyp,Pym,Pzp,Pzm)) #Order important for boundary logic
178
+ #Perform tessellation
179
+ logger.debug("Beginning tessellation")
180
+ Tri = sptl.Delaunay(pts)
181
+ logger.debug("Converting tessellation to adjacency matrix")
182
+ adjmat = sprs.lil_matrix((Np,Np),dtype=int)
183
+ for i in sp.arange(0,sp.shape(Tri.simplices)[0]):
184
+ #Keep only simplices that are fully in real domain
185
+ #this used to be vectorize, but it stopped working...change in scipy?
186
+ for j in Tri.simplices[i]:
187
+ if j < Np:
188
+ adjmat[j,Tri.simplices[i][Tri.simplices[i]<Np]] = 1
189
+ #Remove duplicate (lower triangle) and self connections (diagonal)
190
+ #and convert to coo
191
+ adjmat = sprs.triu(adjmat,k=1,format="coo")
192
+ logger.debug("Conversion to adjacency matrix complete")
193
+ self['throat.conns']=sp.vstack((adjmat.row, adjmat.col)).T
194
+ self['pore.all'] = np.ones(len(self['pore.coords']), dtype=bool)
195
+ self['throat.all'] = np.ones(len(self['throat.conns']), dtype=bool)
196
+
197
+ # Do Voronoi diagram - creating voronoi polyhedra around each pore and save vertex information
198
+ self._vor = Voronoi(pts)
199
+ all_vert_index = sp.ndarray(Np,dtype=object)
200
+ for i,polygon in enumerate(self._vor.point_region[0:Np]):
201
+ if -1 not in self._vor.regions[polygon]:
202
+ all_vert_index[i]=dict(zip(self._vor.regions[polygon],self._vor.vertices[self._vor.regions[polygon]]))
203
+
204
+ " Add throat vertices by looking up vor.ridge_dict "
205
+ throat_verts = sp.ndarray(len(self["throat.conns"]),dtype=object)
206
+ for i,(p1,p2) in enumerate(self["throat.conns"]):
207
+ try:
208
+ throat_verts[i]=dict(zip(self._vor.ridge_dict[(p1,p2)],self._vor.vertices[self._vor.ridge_dict[(p1,p2)]]))
209
+ except KeyError:
210
+ try:
211
+ throat_verts[i]=dict(zip(self._vor.ridge_dict[(p2,p1)],self._vor.vertices[self._vor.ridge_dict[(p2,p1)]]))
212
+ except KeyError:
213
+ print("Throat Pair Not Found in Voronoi Ridge Dictionary")
214
+
215
+ self['pore.vert_index']=all_vert_index
216
+ self['throat.vert_index']=throat_verts
217
+ logger.debug(sys._getframe().f_code.co_name+": End of method")
218
+
219
+ def _add_labels(self):
220
+ r'''
221
+ Deprecated if using add_boundaries()
222
+ This finds surface pores simply by proximity to the domain boundaries.
223
+ A better approach is necessary
224
+ '''
225
+ coords = self['pore.coords']
226
+ self['pore.front'] = coords[:,0]<(0.1*self._Lx)
227
+ self['pore.back'] = coords[:,0]>(0.9*self._Lx)
228
+ self['pore.left'] = coords[:,1]<(0.1*self._Ly)
229
+ self['pore.right'] = coords[:,1]>(0.9*self._Ly)
230
+ self['pore.bottom'] = coords[:,2]<(0.1*self._Lz)
231
+ self['pore.top'] = coords[:,2]>(0.9*self._Lz)
232
+ bnds = self.pores(labels=['front','back','left','right','bottom','top'])
233
+ self['pore.boundary'] = False
234
+ self['pore.boundary'] = bnds
235
+
236
+ def _add_boundaries(self):
237
+ r"""
238
+ This is an alternative means of adding boundaries
239
+ """
240
+ logger.info("add_boundaries: start of method")
241
+
242
+ import scipy.spatial as sptl
243
+ import scipy.sparse as sprs
244
+ Lx = self._Lx
245
+ Ly = self._Ly
246
+ Lz = self._Lz
247
+ Np = self.num_pores()
248
+ btype = self._btype
249
+ boffset = 0.05
250
+
251
+ #Translate internal pores to each face of domain
252
+ poffset = np.zeros((7,3))
253
+ poffset[[2,5],0] = [-Lx, Lx]
254
+ poffset[[3,4],1] = [-Ly, Ly]
255
+ poffset[[1,6],2] = [-Lz, Lz]
256
+ pcoords = pcoords0 = self['pore.coords']
257
+ for i in np.r_[1:7]:
258
+ pcoords = np.concatenate((pcoords,pcoords0 + poffset[i,:]),axis=0)
259
+
260
+ #Use some twisted logic to get bval list of + for boundary and - for periodic faces
261
+ bval = [0, 1, 2, 3, 4, 5, 6]*(np.array([0, btype[2], btype[0], btype[1], btype[1], btype[0], btype[2]])*-2+1)
262
+ ptype = np.zeros((Np,),dtype=int)
263
+ for i in np.r_[1:7]:
264
+ ptype = np.concatenate((ptype,np.ones((Np,),dtype=int)*bval[i]),axis=0)
265
+
266
+ #pnum contains the internal ID number of the boundary pores (for connecting periodic points)
267
+ pnum = self.pores()
268
+ pnum = np.tile(pnum,7)
269
+
270
+ Tri = sptl.Delaunay(pcoords)
271
+ adjmat = sprs.lil_matrix((np.shape(pcoords)[0],np.shape(pcoords)[0]),dtype=int)
272
+ for i in np.arange(0,np.shape(Tri.simplices)[0]):
273
+ #Keep only simplices that are fully in real domain
274
+ adjmat[Tri.simplices[i],Tri.simplices[i]] = 1
275
+ adjmat = sprs.triu(adjmat,k=1,format="lil")
276
+ for i in np.arange(0,Np):
277
+ #Add periodic throats to the netowrk (if any)
278
+ tpore2 = pnum[adjmat.rows[i]][ptype[adjmat.rows[i]]<0]
279
+ tpore1 = np.ones_like(tpore2,dtype=int)*i
280
+ conns = self['throat.conns']
281
+ conns = np.concatenate((conns,np.vstack((tpore1,tpore2)).T),axis=0)
282
+ #Add boundary pores and throats to the network
283
+ newporetyps = np.unique(ptype[adjmat.rows[i]][ptype[adjmat.rows[i]]>0])
284
+ newporenums = np.r_[self.num_pores():self.num_pores()+np.size(newporetyps)]
285
+ tpore2 = newporenums
286
+ tpore1 = np.ones_like(tpore2,dtype=int)*i
287
+ conns = np.concatenate((conns,np.vstack((tpore1,tpore2)).T),axis=0)
288
+ self['throat.conns'] = conns
289
+ bcoords = np.zeros((7,3),dtype=float)
290
+ coords = self['pore.coords']
291
+ bcoords[1,:] = [coords[i,0], coords[i,1], 0-Lz*boffset]
292
+ bcoords[2,:] = [0-Lx*boffset, coords[i,1], coords[i,2]]
293
+ bcoords[3,:] = [coords[i,0], -Ly*boffset, coords[i,2]]
294
+ bcoords[4,:] = [coords[i,0], Ly+Ly*boffset, coords[i,2]]
295
+ bcoords[5,:] = [Lx+Lx*boffset, coords[i,1], coords[i,2]]
296
+ bcoords[6,:] = [coords[i,0], coords[i,1], Lz+Lz*boffset]
297
+ newporecoords = bcoords[newporetyps,:]
298
+ coords = np.concatenate((coords,newporecoords),axis=0)
299
+ self['pore.coords'] = coords
300
+ #Reset number of pores and throats (easier than tracking it)
301
+ nums = np.r_[0:np.shape(coords)[0]]
302
+ self['pore.numbering'] = nums
303
+ self['pore.numbering'] = np.ones((nums[-1]+1,),dtype=bool)
304
+ nums = np.r_[0:np.shape(conns)[0]]
305
+ self['throat.numbering'] = nums
306
+ self['throat.numbering'] = np.ones((nums[-1]+1,),dtype=bool)
307
+ logger.debug("add_boundaries: end of method")
308
+
309
+ def _add_boundaries_old(self):
310
+ logger.info("add_boundaries_old: Start of method")
311
+
312
+ self.add_opposing_boundaries(btype=[2,5])
313
+ self.add_opposing_boundaries(btype=[3,4])
314
+ self.add_opposing_boundaries(btype=[1,6])
315
+
316
+ def _add_opposing_boundaries(self,btype=[1,6]):
317
+ r"""
318
+ btype indicates which two boundaries are being added by type
319
+ """
320
+ logger.info("add_opposing_boundaries: start of method")
321
+
322
+ if btype==[2,5]:
323
+ D=0
324
+ W=1
325
+ H=2
326
+ elif btype==[3,4]:
327
+ D=1
328
+ W=0
329
+ H=2
330
+ elif btype==[1,6]:
331
+ D=2
332
+ W=1
333
+ H=0
334
+
335
+ Lx = self.domain_size[D]
336
+ Ly = self.domain_size[W]
337
+ Lz = self.domain_size[H]
338
+ #Rotate pore coordinates (use only internal pores)
339
+ pnum = self._net.pore_data['numbering'][self._net.pore_data['type']==0]
340
+ pcoords = np.zeros_like(self._net.pore_data['coords'][pnum,:])
341
+ pcoords[:,0] = self._net.pore_data['coords'][pnum,D]
342
+ pcoords[:,1] = self._net.pore_data['coords'][pnum,W]
343
+ pcoords[:,2] = self._net.pore_data['coords'][pnum,H]
344
+
345
+ #Determine dimensions of image from dimensions of domain
346
+ f = 100 #minimum image dimension
347
+ im_dim = [0,0,0]
348
+ im_dim[0] = np.floor(f*Lx/np.min([Lx,Ly,Lz]))
349
+ im_dim[1] = np.floor(f*Ly/np.min([Lx,Ly,Lz]))
350
+ im_dim[2] = np.floor(f*Lz/np.min([Lx,Ly,Lz]))
351
+ im_dim = np.array(im_dim,dtype=int)
352
+
353
+ #Convert pore coordinates into image subscripts
354
+ im_subs = np.zeros_like(pcoords,dtype=int)
355
+ im_subs[:,0] = pcoords[:,0]*im_dim[0]/Lx
356
+ im_subs[:,1] = pcoords[:,1]*im_dim[1]/Ly
357
+ im_subs[:,2] = pcoords[:,2]*im_dim[2]/Lz
358
+ #Find linear indices of each pore in the new image
359
+ im_inds = np.ravel_multi_index((im_subs[:,0], im_subs[:,1], im_subs[:,2]), dims=(im_dim), order='F')
360
+
361
+ #Generate 3D image of points (place pore numbers at each site for use later)
362
+ img = np.zeros(im_dim,dtype=int)
363
+ img[im_subs[:,0],im_subs[:,1],im_subs[:,2]] = pnum
364
+
365
+ #Perform distance transform on points and also get 'indicies' of each point
366
+ img_dt, ind_dt = spim.distance_transform_edt(img==0)
367
+
368
+ #Project all* internal points to x face
369
+ #*Note that it's possible/likely that mutliple internal points map to the same boundary point
370
+ img_bd0 = np.zeros([im_dim[1],im_dim[2]],dtype=int)
371
+ img_bd1 = np.zeros([im_dim[1],im_dim[2]],dtype=int)
372
+ img_bd0[im_subs[:,1],im_subs[:,2]] = im_inds
373
+ img_bd1[im_subs[:,1],im_subs[:,2]] = im_inds
374
+
375
+ #Create 2D array of distance transform indices for 0 and end faces
376
+ dt_D0 = ind_dt[0,0,:,:]*(img_bd0>0) #0 face
377
+ dt_D1 = ind_dt[0,-1,:,:]*(img_bd1>0) #end face
378
+
379
+ #Create a 2D mask containing x coordinates of internal points and -1 elsewhere
380
+ img_D0 = -np.ones([im_dim[1],im_dim[2]],dtype=int)
381
+ img_D1 = -np.ones([im_dim[1],im_dim[2]],dtype=int)
382
+ img_D0[im_subs[:,1],im_subs[:,2]] = im_subs[:,0]
383
+ img_D1[im_subs[:,1],im_subs[:,2]] = im_subs[:,0]
384
+
385
+ #Find where x value of internal points corresponds to x value of distance transform indices
386
+ img_bd0 = (img_D0 == dt_D0)*img_bd0
387
+ img_bd1 = (img_D1 == dt_D1)*img_bd1
388
+
389
+ #Convert boundary sites to linear indices
390
+ inds_bd0 = img_bd0[np.nonzero(img_bd0)]
391
+ inds_bd1 = img_bd1[np.nonzero(img_bd1)]
392
+
393
+ #Use linear indices to find pore ID nums
394
+ nums_bd0 = img[np.unravel_index(inds_bd0, dims=(im_dim), order='F')]
395
+ nums_bd1 = img[np.unravel_index(inds_bd1, dims=(im_dim), order='F')]
396
+ nums_bd = np.append(nums_bd0,nums_bd1)
397
+ types_bd = np.append(np.zeros_like(nums_bd0),np.ones_like(nums_bd1))
398
+
399
+ #Add new boundary pores and throats to the network
400
+ Np = self._net.num_pores() #Get all pores including previously added boundaries
401
+ bp_numbering = np.r_[Np:Np+np.size(nums_bd)]
402
+ bp_type = (types_bd==0)*btype[0] + (types_bd==1)*btype[1]
403
+ bp_coords = np.zeros([np.size(nums_bd),3])
404
+ bp_coords[types_bd==0,D] = np.zeros_like(nums_bd0)-.0001
405
+ bp_coords[types_bd==0,W] = pcoords[nums_bd0,1]
406
+ bp_coords[types_bd==0,H] = pcoords[nums_bd0,2]
407
+ bp_coords[types_bd==1,D] = np.ones_like(nums_bd1)*Lx+0.0001
408
+ bp_coords[types_bd==1,W] = pcoords[nums_bd1,1]
409
+ bp_coords[types_bd==1,H] = pcoords[nums_bd1,2]
410
+ self._net.pore_data['numbering'] = np.append(self._net.pore_data['numbering'],bp_numbering)
411
+ self._net.pore_data['type'] = np.append(self._net.pore_data['type'],bp_type)
412
+ self._net.pore_data['coords'] = np.concatenate((self._net.pore_data['coords'],bp_coords))
413
+ Nt = self._net.num_throats()
414
+ bt_numbering = np.r_[Nt:Nt+np.size(nums_bd)]
415
+ bt_type = np.ones(np.size(nums_bd),dtype=int)*2
416
+ bt_connections = np.zeros([np.size(nums_bd),2],dtype=int)
417
+ bt_connections[:,0] = nums_bd
418
+ bt_connections[:,1] = bp_numbering
419
+ self._net.throat_data['numbering'] = np.append(self._net.throat_data['numbering'],bt_numbering)
420
+ self._net.throat_data['type'] = np.append(self._net.throat_data['type'],bt_type)
421
+ self._net.throat_data['conns'] = np.concatenate((self._net.throat_data['conns'],bt_connections))
422
+
423
+ def domain_size(self,dimension=''):
424
+ r"""
425
+ This is a simple way to find the domain sizes.
426
+ N.B
427
+ Will not work with saved and loaded networks
428
+ """
429
+ if dimension == 'front' or dimension == 'back':
430
+ return self._Ly*self._Lz
431
+ if dimension == 'left' or dimension == 'right':
432
+ return self._Lx*self._Lz
433
+ if dimension == 'top' or dimension == 'bottom':
434
+ return self._Lx*self._Ly
435
+ if dimension == 'volume':
436
+ return self._Lx*self._Ly*self._Lz
437
+ if dimension == 'height':
438
+ return self._Lz
439
+ if dimension == 'width':
440
+ return self._Lx
441
+ if dimension == 'depth':
442
+ return self._Ly
443
+
444
+ def add_boundaries(self):
445
+
446
+ r"""
447
+ This method identifies pores in the original Voronoi object that straddle a boundary imposed by the reflection
448
+ The pore inside the original set of pores (with index 0 - Np) is identified and the coordinates are saved
449
+ The vertices making up the boundary throat are retrieved from the ridge_dict values and these are used to identify
450
+ which boundary the throat sits at.
451
+ A new pore and new connection is created with coordinates lying on the boundary plane
452
+ N.B This method will only work properly if the original network remains unaltered i.e. not trimmed or extended
453
+ This preserves the connection between pore index on the network object and the Voronoi object
454
+ The point of using this method is so that the throat vertices created by the Voronoi object are preserved
455
+
456
+ This method will create boundary pores at the centre of the voronoi faces that
457
+ align with the outer planes of the domain.
458
+ The original pores in the domain are labelled internal and the boundary pores
459
+ are labelled external
460
+
461
+ Examples
462
+ --------
463
+ >>> import OpenPNM
464
+ >>> pn = OpenPNM.Network.Delaunay(num_pores=100, domain_size=[0.0001,0.0001,0.0001])
465
+ >>> pn.add_boundaries()
466
+ >>> pn.num_pores("boundary")>0
467
+ True
468
+ """
469
+
470
+ bound_conns=[]
471
+ bound_coords=[]
472
+ bound_vert_index=[]
473
+ throat_vert_index=[]
474
+ #Find boundary extent
475
+ [x_min,x_max,y_min,y_max,z_min,z_max]=vo.vertex_dimension(self,self.pores(),parm='minmax')
476
+ min_point = np.around(np.array([x_min,y_min,z_min]),10)
477
+ max_point = np.around(np.array([x_max,y_max,z_max]),10)
478
+ Np = self.num_pores()
479
+ Nt = self.num_throats()
480
+ new_throat_count = 0
481
+ # ridge_dict contains a dictionary where the key is a set of 2 neighbouring pores and the value is the vertex indices
482
+ # that form the throat or ridge between them
483
+ for p,v in self._vor.ridge_dict.items():
484
+ # if the vertex with index -1 is contained in list then the ridge is unbounded - ignore these
485
+ if np.all(np.asarray(v) >=0):
486
+ #boundary throats will be those connecting one pore inside the original set and one out
487
+ if (p[0] in range(Np) and p[1] not in range(Np)) or\
488
+ (p[0] not in range(Np) and p[1] in range(Np)):
489
+ # the dictionary key is not in numerical order so find the pore index inside
490
+ if p[0] in range(Np):
491
+ my_pore=p[0]
492
+ else:
493
+ my_pore=p[1]
494
+ my_pore_coord = self["pore.coords"][my_pore]
495
+ new_pore_coord = my_pore_coord.copy()
496
+ #rounding necessary here to identify the plane as Voronoi can have 1e-17 and smaller errors
497
+ throat_verts = np.around(self._vor.vertices[v],10)
498
+ #find which plane we are aligned with (if any) and align new_pore with throat plane
499
+ if len(np.unique(throat_verts[:,0])) == 1:
500
+ new_pore_coord[0]=np.unique(throat_verts[:,0])
501
+ elif len(np.unique(throat_verts[:,1])) == 1:
502
+ new_pore_coord[1]=np.unique(throat_verts[:,1])
503
+ elif len(np.unique(throat_verts[:,2])) == 1:
504
+ new_pore_coord[2]=np.unique(throat_verts[:,2])
505
+ else:
506
+ new_pore_coord = throat_verts.mean()
507
+ bound_coords.append(new_pore_coord)
508
+ bound_conns.append(np.array([my_pore,new_throat_count+Np]))
509
+ bound_vert_index.append(dict(zip(v,throat_verts)))
510
+ throat_vert_index.append(dict(zip(v,throat_verts)))
511
+ new_throat_count += 1
512
+
513
+ #Add new pores and connections
514
+ self.extend(pore_coords=bound_coords, throat_conns=bound_conns)
515
+ #Record new number of pores
516
+ Mp = self.num_pores()
517
+ Mt = self.num_throats()
518
+ new_pore_ids = np.arange(Np,Mp)
519
+ new_throat_ids = np.arange(Nt,Mt)
520
+ #Identify which boundary the pore sits on
521
+ front = self.pores()[self['pore.coords'][:,0]==min_point[0]]
522
+ back = self.pores()[self['pore.coords'][:,0]==max_point[0]]
523
+ left = self.pores()[self['pore.coords'][:,1]==min_point[1]]
524
+ right = self.pores()[self['pore.coords'][:,1]==max_point[1]]
525
+ bottom = self.pores()[self['pore.coords'][:,2]==min_point[2]]
526
+ top = self.pores()[self['pore.coords'][:,2]==max_point[2]]
527
+ #Assign labels
528
+ self['pore.boundary'] = False
529
+ self['pore.boundary'][new_pore_ids] = True
530
+ self['pore.right_boundary'] = False
531
+ self['pore.left_boundary'] = False
532
+ self['pore.front_boundary'] = False
533
+ self['pore.back_boundary'] = False
534
+ self['pore.top_boundary'] = False
535
+ self['pore.bottom_boundary'] = False
536
+ self['pore.right_boundary'][right] = True
537
+ self['pore.left_boundary'][left] = True
538
+ self['pore.front_boundary'][front] = True
539
+ self['pore.back_boundary'][back] = True
540
+ self['pore.top_boundary'][top] = True
541
+ self['pore.bottom_boundary'][bottom] = True
542
+ #Save the throat verts
543
+ self["pore.vert_index"][new_pore_ids] = bound_vert_index
544
+ self["throat.vert_index"][new_throat_ids] = throat_vert_index
545
+
546
+
547
+ def domain_length(self,face_1,face_2):
548
+ r"""
549
+ Returns the distance between two faces
550
+ No coplanar checking this is done in vertex_dimension
551
+
552
+ Example
553
+ --------
554
+ >>> import OpenPNM
555
+ >>> pn = OpenPNM.Network.Delaunay(num_pores=100, domain_size=[3,2,1])
556
+ >>> pn.add_boundaries()
557
+ >>> B1 = pn.pores("left_boundary")
558
+ >>> B2 = pn.pores("right_boundary")
559
+ >>> pn.domain_length(B1,B2)
560
+ 2.0
561
+ """
562
+ L = vo.vertex_dimension(self,face_1,face_2,parm='length')
563
+ return L
564
+
565
+ def domain_area(self,face):
566
+ r"""
567
+ Returns the area of a face
568
+ No coplanar checking this is done in vertex_dimension
569
+ Example
570
+ --------
571
+ >>> import OpenPNM
572
+ >>> pn = OpenPNM.Network.Delaunay(num_pores=100, domain_size=[3,2,1])
573
+ >>> pn.add_boundaries()
574
+ >>> B1 = pn.pores("left_boundary")
575
+ >>> B2 = pn.pores("right_boundary")
576
+ >>> pn.domain_area(B1)
577
+ 3.0
578
+ """
579
+ A = vo.vertex_dimension(self,face,parm='area')
580
+
581
+ return A
582
+
583
+ def trim_occluded_throats(self):
584
+ r"""
585
+ After the offsetting routine throats with zero area have been fully occluded.
586
+ Remove these from the network and also remove pores that are isolated
587
+ """
588
+ occluded_ts = list(self.throats()[self["throat.area"]==0])
589
+ #occluded_throats = np.asarray(occluded_throats)
590
+ if len(occluded_ts) > 0:
591
+ self.trim(throats=occluded_ts)
592
+ "Also get rid of isolated pores"
593
+ isolated_ps = self.check_network_health()['isolated_pores']
594
+ if len(isolated_ps) > 0:
595
+ self.trim(isolated_ps)
596
+
597
+ if __name__ == '__main__':
598
+ #Run doc tests
599
+ import doctest
600
+ doctest.testmod(verbose=True)