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,1184 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
===============================================================================
|
|
4
|
+
GenericNetwork: Abstract class to construct pore networks
|
|
5
|
+
===============================================================================
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
import scipy as sp
|
|
9
|
+
import scipy.sparse as sprs
|
|
10
|
+
import scipy.spatial as sptl
|
|
11
|
+
import OpenPNM.Utilities.misc as misc
|
|
12
|
+
from OpenPNM.Base import Core, Controller, Tools, logging
|
|
13
|
+
logger = logging.getLogger(__name__)
|
|
14
|
+
ctrl = Controller()
|
|
15
|
+
|
|
16
|
+
class GenericNetwork(Core):
|
|
17
|
+
r"""
|
|
18
|
+
GenericNetwork - Base class to construct pore networks
|
|
19
|
+
|
|
20
|
+
Parameters
|
|
21
|
+
----------
|
|
22
|
+
name : string
|
|
23
|
+
Unique name for Network object
|
|
24
|
+
|
|
25
|
+
"""
|
|
26
|
+
def __init__(self,**kwargs):
|
|
27
|
+
r"""
|
|
28
|
+
Initialize Network
|
|
29
|
+
"""
|
|
30
|
+
super(GenericNetwork,self).__init__(**kwargs)
|
|
31
|
+
logger.name = self.name
|
|
32
|
+
|
|
33
|
+
#Initialize adjacency and incidence matrix dictionaries
|
|
34
|
+
self._incidence_matrix = {}
|
|
35
|
+
self._adjacency_matrix = {}
|
|
36
|
+
|
|
37
|
+
def __setitem__(self,prop,value):
|
|
38
|
+
for geom in self._geometries:
|
|
39
|
+
if (prop in geom.keys()) and ('all' not in prop.split('.')):
|
|
40
|
+
logger.error(prop+' is already defined in at least one associated Geometry object')
|
|
41
|
+
return
|
|
42
|
+
super(GenericNetwork,self).__setitem__(prop,value)
|
|
43
|
+
|
|
44
|
+
def __getitem__(self,key):
|
|
45
|
+
if key.split('.')[-1] == self.name:
|
|
46
|
+
element = key.split('.')[0]
|
|
47
|
+
return self[element+'.all']
|
|
48
|
+
if key not in self.keys():
|
|
49
|
+
logger.debug(key+' not on Network, constructing data from Geometries')
|
|
50
|
+
return self._interleave_data(key,self.geometries())
|
|
51
|
+
else:
|
|
52
|
+
return super(GenericNetwork,self).__getitem__(key)
|
|
53
|
+
|
|
54
|
+
def _set_net(self,network):
|
|
55
|
+
pass
|
|
56
|
+
def _get_net(self):
|
|
57
|
+
return self
|
|
58
|
+
_net = property(fset=_set_net,fget=_get_net)
|
|
59
|
+
|
|
60
|
+
#--------------------------------------------------------------------------
|
|
61
|
+
'''Graph Theory and Network Query Methods'''
|
|
62
|
+
#--------------------------------------------------------------------------
|
|
63
|
+
def create_adjacency_matrix(self,data=None,sprsfmt='coo',dropzeros=True,sym=True):
|
|
64
|
+
r"""
|
|
65
|
+
Generates a weighted adjacency matrix in the desired sparse format
|
|
66
|
+
|
|
67
|
+
Parameters
|
|
68
|
+
----------
|
|
69
|
+
data : array_like, optional
|
|
70
|
+
An array containing the throat values to enter into the matrix (in
|
|
71
|
+
graph theory these are known as the 'weights'). If omitted, ones
|
|
72
|
+
are used to create a standard adjacency matrix representing
|
|
73
|
+
connectivity only.
|
|
74
|
+
|
|
75
|
+
sprsfmt : string, optional
|
|
76
|
+
The sparse storage format to return. Options are:
|
|
77
|
+
|
|
78
|
+
* 'coo' : (default) This is the native format of OpenPNM data
|
|
79
|
+
|
|
80
|
+
* 'lil' : Enables row-wise slice of data
|
|
81
|
+
|
|
82
|
+
* 'csr' : Favored by most linear algebra routines
|
|
83
|
+
|
|
84
|
+
dropzeros : boolean, optional
|
|
85
|
+
Remove 0 elements from the values, instead of creating 0-weighted
|
|
86
|
+
links, the default is True.
|
|
87
|
+
|
|
88
|
+
sym : Boolean, optional
|
|
89
|
+
Makes the matrix symmetric about the diagonal, the default is true.
|
|
90
|
+
|
|
91
|
+
Returns
|
|
92
|
+
-------
|
|
93
|
+
Returns an adjacency matrix in the specified Scipy sparse format
|
|
94
|
+
|
|
95
|
+
Examples
|
|
96
|
+
--------
|
|
97
|
+
>>> import OpenPNM
|
|
98
|
+
>>> pn = OpenPNM.Network.TestNet()
|
|
99
|
+
>>> vals = sp.rand(pn.num_throats(),) < 0.5
|
|
100
|
+
>>> temp = pn.create_adjacency_matrix(data=vals,sprsfmt='csr')
|
|
101
|
+
|
|
102
|
+
"""
|
|
103
|
+
logger.debug('create_adjacency_matrix: Start of method')
|
|
104
|
+
Np = self.num_pores()
|
|
105
|
+
Nt = self.num_throats()
|
|
106
|
+
|
|
107
|
+
#Check if provided data is valid
|
|
108
|
+
if data is None:
|
|
109
|
+
data = sp.ones((self.num_throats(),))
|
|
110
|
+
elif sp.shape(data)[0] != Nt:
|
|
111
|
+
raise Exception('Received dataset of incorrect length')
|
|
112
|
+
|
|
113
|
+
#Clear any zero-weighted connections
|
|
114
|
+
if dropzeros:
|
|
115
|
+
ind = data>0
|
|
116
|
+
else:
|
|
117
|
+
ind = sp.ones_like(data,dtype=bool)
|
|
118
|
+
|
|
119
|
+
#Get connectivity info from network
|
|
120
|
+
conn = self['throat.conns'][ind]
|
|
121
|
+
row = conn[:,0]
|
|
122
|
+
col = conn[:,1]
|
|
123
|
+
data = data[ind]
|
|
124
|
+
|
|
125
|
+
if sym: #Append row & col to each other, and data to itself
|
|
126
|
+
row = sp.append(row,conn[:,1])
|
|
127
|
+
col = sp.append(col,conn[:,0])
|
|
128
|
+
data = sp.append(data,data)
|
|
129
|
+
|
|
130
|
+
#Generate sparse adjacency matrix in 'coo' format
|
|
131
|
+
temp = sprs.coo_matrix((data,(row,col)),(Np,Np))
|
|
132
|
+
|
|
133
|
+
#Convert to requested format
|
|
134
|
+
if sprsfmt == 'coo':
|
|
135
|
+
pass #temp is already in coo format
|
|
136
|
+
if sprsfmt == 'csr':
|
|
137
|
+
temp = temp.tocsr()
|
|
138
|
+
if sprsfmt == 'lil':
|
|
139
|
+
temp = temp.tolil()
|
|
140
|
+
logger.debug('create_adjacency_matrix: End of method')
|
|
141
|
+
return temp
|
|
142
|
+
|
|
143
|
+
def create_incidence_matrix(self,data=None,sprsfmt='coo',dropzeros=True):
|
|
144
|
+
r"""
|
|
145
|
+
Creates an incidence matrix filled with supplied throat values
|
|
146
|
+
|
|
147
|
+
Parameters
|
|
148
|
+
----------
|
|
149
|
+
data : array_like, optional
|
|
150
|
+
An array containing the throat values to enter into the matrix (In
|
|
151
|
+
graph theory these are known as the 'weights'). If omitted, ones
|
|
152
|
+
are used to create a standard incidence matrix representing
|
|
153
|
+
connectivity only.
|
|
154
|
+
|
|
155
|
+
sprsfmt : string, optional
|
|
156
|
+
The sparse storage format to return. Options are:
|
|
157
|
+
|
|
158
|
+
* 'coo' : (default) This is the native format of OpenPNMs data
|
|
159
|
+
|
|
160
|
+
* 'lil' : Enables row-wise slice of data
|
|
161
|
+
|
|
162
|
+
* 'csr' : Favored by most linear algebra routines
|
|
163
|
+
|
|
164
|
+
dropzeros : Boolean, optional
|
|
165
|
+
Remove 0 elements from values, instead of creating 0-weighted
|
|
166
|
+
links, the default is True.
|
|
167
|
+
|
|
168
|
+
Returns
|
|
169
|
+
-------
|
|
170
|
+
An incidence matrix (a cousin to the adjacency matrix, useful for
|
|
171
|
+
finding throats of given a pore)
|
|
172
|
+
|
|
173
|
+
Examples
|
|
174
|
+
--------
|
|
175
|
+
>>> import OpenPNM
|
|
176
|
+
>>> pn = OpenPNM.Network.TestNet()
|
|
177
|
+
>>> vals = sp.rand(pn.num_throats(),) < 0.5
|
|
178
|
+
>>> temp = pn.create_incidence_matrix(data=vals,sprsfmt='csr')
|
|
179
|
+
"""
|
|
180
|
+
logger.debug('create_incidence_matrix: Start of method')
|
|
181
|
+
|
|
182
|
+
Nt = self.num_throats()
|
|
183
|
+
Np = self.num_pores()
|
|
184
|
+
|
|
185
|
+
#Check if provided data is valid
|
|
186
|
+
if data is None:
|
|
187
|
+
data = sp.ones((self.num_throats(),))
|
|
188
|
+
elif sp.shape(data)[0] != Nt:
|
|
189
|
+
raise Exception('Received dataset of incorrect length')
|
|
190
|
+
|
|
191
|
+
if dropzeros:
|
|
192
|
+
ind = data > 0
|
|
193
|
+
else:
|
|
194
|
+
ind = sp.ones_like(data, dtype=bool)
|
|
195
|
+
|
|
196
|
+
conn = self['throat.conns'][ind]
|
|
197
|
+
row = conn[:,0]
|
|
198
|
+
row = sp.append(row,conn[:,1])
|
|
199
|
+
col = self.throats('all')[ind]
|
|
200
|
+
col = sp.append(col,col)
|
|
201
|
+
data = sp.append(data[ind],data[ind])
|
|
202
|
+
|
|
203
|
+
temp = sprs.coo.coo_matrix((data,(row,col)),(Np,Nt))
|
|
204
|
+
|
|
205
|
+
#Convert to requested format
|
|
206
|
+
if sprsfmt == 'coo':
|
|
207
|
+
pass #temp is already in coo format
|
|
208
|
+
if sprsfmt == 'csr':
|
|
209
|
+
temp = temp.tocsr()
|
|
210
|
+
if sprsfmt == 'lil':
|
|
211
|
+
temp = temp.tolil()
|
|
212
|
+
logger.debug('create_incidence_matrix: End of method')
|
|
213
|
+
return temp
|
|
214
|
+
|
|
215
|
+
def find_connected_pores(self,throats=[],flatten=False):
|
|
216
|
+
r"""
|
|
217
|
+
Return a list of pores connected to the given list of throats
|
|
218
|
+
|
|
219
|
+
Parameters
|
|
220
|
+
----------
|
|
221
|
+
throats : array_like
|
|
222
|
+
List of throats numbers
|
|
223
|
+
|
|
224
|
+
flatten : boolean, optional
|
|
225
|
+
If flatten is True (default) a 1D array of unique pore numbers
|
|
226
|
+
is returned. If flatten is False each location in the the returned
|
|
227
|
+
array contains a sub-arras of neighboring pores for each input
|
|
228
|
+
throat, in the order they were sent.
|
|
229
|
+
|
|
230
|
+
Returns
|
|
231
|
+
-------
|
|
232
|
+
1D array (if flatten is True) or ndarray of arrays (if flatten is False)
|
|
233
|
+
|
|
234
|
+
Examples
|
|
235
|
+
--------
|
|
236
|
+
>>> import OpenPNM
|
|
237
|
+
>>> pn = OpenPNM.Network.TestNet()
|
|
238
|
+
>>> pn.find_connected_pores(throats=[0,1])
|
|
239
|
+
array([[0, 1],
|
|
240
|
+
[0, 5]])
|
|
241
|
+
>>> pn.find_connected_pores(throats=[0,1],flatten=True)
|
|
242
|
+
array([0, 1, 5])
|
|
243
|
+
"""
|
|
244
|
+
Ps = self['throat.conns'][throats]
|
|
245
|
+
#Ps = [sp.asarray(x) for x in Ps if x]
|
|
246
|
+
if flatten:
|
|
247
|
+
Ps = sp.unique(sp.hstack(Ps))
|
|
248
|
+
return Ps
|
|
249
|
+
|
|
250
|
+
def find_connecting_throat(self,P1,P2):
|
|
251
|
+
r"""
|
|
252
|
+
Return the throat number connecting pairs of pores
|
|
253
|
+
|
|
254
|
+
Parameters
|
|
255
|
+
----------
|
|
256
|
+
P1 , P2 : array_like
|
|
257
|
+
The pore numbers whose throats are sought. These can be vectors
|
|
258
|
+
of pore numbers, but must be the same length
|
|
259
|
+
|
|
260
|
+
Returns
|
|
261
|
+
-------
|
|
262
|
+
Tnum : list of list of int
|
|
263
|
+
Returns throat number(s), or empty array if pores are not connected
|
|
264
|
+
|
|
265
|
+
Examples
|
|
266
|
+
--------
|
|
267
|
+
>>> import OpenPNM
|
|
268
|
+
>>> pn = OpenPNM.Network.TestNet()
|
|
269
|
+
>>> pn.find_connecting_throat([0,1,2],[2,2,2])
|
|
270
|
+
[[], [3], []]
|
|
271
|
+
|
|
272
|
+
TODO: This now works on 'vector' inputs, but is not actually vectorized
|
|
273
|
+
in the Numpy sense, so could be slow with large P1,P2 inputs
|
|
274
|
+
"""
|
|
275
|
+
Ts1 = self.find_neighbor_throats(P1,flatten=False)
|
|
276
|
+
Ts2 = self.find_neighbor_throats(P2,flatten=False)
|
|
277
|
+
Ts = []
|
|
278
|
+
if sp.shape(P1) == ():
|
|
279
|
+
P1 = [P1]
|
|
280
|
+
P2 = [P2]
|
|
281
|
+
for row in range(0,len(P1)):
|
|
282
|
+
if P1[row] == P2[row]:
|
|
283
|
+
throat = []
|
|
284
|
+
else:
|
|
285
|
+
throat = sp.intersect1d(Ts1[row],Ts2[row]).tolist()
|
|
286
|
+
Ts.insert(0,throat)
|
|
287
|
+
Ts.reverse()
|
|
288
|
+
return Ts
|
|
289
|
+
|
|
290
|
+
def find_neighbor_pores(self,pores,mode='union',flatten=True,excl_self=True):
|
|
291
|
+
r"""
|
|
292
|
+
Returns a list of pores neighboring the given pore(s)
|
|
293
|
+
|
|
294
|
+
Parameters
|
|
295
|
+
----------
|
|
296
|
+
pores : array_like
|
|
297
|
+
ID numbers of pores whose neighbors are sought.
|
|
298
|
+
flatten : boolean, optional
|
|
299
|
+
If flatten is True a 1D array of unique pore ID numbers is
|
|
300
|
+
returned. If flatten is False the returned array contains arrays
|
|
301
|
+
of neighboring pores for each input pore, in the order they were
|
|
302
|
+
sent.
|
|
303
|
+
excl_self : bool, optional (Default is False)
|
|
304
|
+
If this is True then the input pores are not included in the
|
|
305
|
+
returned list. This option only applies when input pores
|
|
306
|
+
are in fact neighbors to each other, otherwise they are not
|
|
307
|
+
part of the returned list anyway.
|
|
308
|
+
mode : string, optional
|
|
309
|
+
Specifies which neighbors should be returned. The options are:
|
|
310
|
+
|
|
311
|
+
* 'union' : All neighbors of the input pores
|
|
312
|
+
|
|
313
|
+
* 'intersection' : Only neighbors shared by all input pores
|
|
314
|
+
|
|
315
|
+
* 'not_intersection' : Only neighbors not shared by any input pores
|
|
316
|
+
|
|
317
|
+
Returns
|
|
318
|
+
-------
|
|
319
|
+
neighborPs : 1D array (if flatten is True) or ndarray of ndarrays (if
|
|
320
|
+
flatten if False)
|
|
321
|
+
|
|
322
|
+
Examples
|
|
323
|
+
--------
|
|
324
|
+
>>> import OpenPNM
|
|
325
|
+
>>> pn = OpenPNM.Network.TestNet()
|
|
326
|
+
>>> pn.find_neighbor_pores(pores=[0,2])
|
|
327
|
+
array([ 1, 3, 5, 7, 25, 27])
|
|
328
|
+
>>> pn.find_neighbor_pores(pores=[0,1]) #Find all neighbors, excluding selves
|
|
329
|
+
array([ 2, 5, 6, 25, 26])
|
|
330
|
+
>>> pn.find_neighbor_pores(pores=[0,1],mode='union',excl_self=False) #Find all neighbors, including selves
|
|
331
|
+
array([ 0, 1, 2, 5, 6, 25, 26])
|
|
332
|
+
>>> pn.find_neighbor_pores(pores=[0,2],flatten=False)
|
|
333
|
+
array([array([ 1, 5, 25]), array([ 1, 3, 7, 27])], dtype=object)
|
|
334
|
+
>>> pn.find_neighbor_pores(pores=[0,2],mode='intersection') #Find only common neighbors
|
|
335
|
+
array([1])
|
|
336
|
+
>>> pn.find_neighbor_pores(pores=[0,2],mode='not_intersection') #Exclude common neighbors
|
|
337
|
+
array([ 3, 5, 7, 25, 27])
|
|
338
|
+
"""
|
|
339
|
+
pores = sp.array(pores,ndmin=1)
|
|
340
|
+
try:
|
|
341
|
+
neighborPs = self._adjacency_matrix['lil'].rows[[pores]]
|
|
342
|
+
except:
|
|
343
|
+
temp = self.create_adjacency_matrix(sprsfmt='lil')
|
|
344
|
+
self._adjacency_matrix['lil'] = temp
|
|
345
|
+
neighborPs = self._adjacency_matrix['lil'].rows[[pores]]
|
|
346
|
+
if [sp.asarray(x) for x in neighborPs if x] == []:
|
|
347
|
+
return sp.array([],ndmin=1)
|
|
348
|
+
if flatten:
|
|
349
|
+
#All the empty lists must be removed to maintain data type after hstack (numpy bug?)
|
|
350
|
+
neighborPs = [sp.asarray(x) for x in neighborPs if x]
|
|
351
|
+
neighborPs = sp.hstack(neighborPs)
|
|
352
|
+
neighborPs = sp.concatenate((neighborPs,pores))
|
|
353
|
+
#Remove references to input pores and duplicates
|
|
354
|
+
if mode == 'not_intersection':
|
|
355
|
+
neighborPs = sp.array(sp.unique(sp.where(sp.bincount(neighborPs)==1)[0]),dtype=int)
|
|
356
|
+
elif mode == 'union':
|
|
357
|
+
neighborPs = sp.array(sp.unique(neighborPs),int)
|
|
358
|
+
elif mode == 'intersection':
|
|
359
|
+
neighborPs = sp.array(sp.unique(sp.where(sp.bincount(neighborPs)>1)[0]),dtype=int)
|
|
360
|
+
if excl_self:
|
|
361
|
+
neighborPs = neighborPs[~sp.in1d(neighborPs,pores)]
|
|
362
|
+
else:
|
|
363
|
+
for i in range(0,sp.size(pores)):
|
|
364
|
+
neighborPs[i] = sp.array(neighborPs[i],dtype=int)
|
|
365
|
+
return sp.array(neighborPs,ndmin=1)
|
|
366
|
+
|
|
367
|
+
def find_neighbor_throats(self,pores,mode='union',flatten=True):
|
|
368
|
+
r"""
|
|
369
|
+
Returns a list of throats neighboring the given pore(s)
|
|
370
|
+
|
|
371
|
+
Parameters
|
|
372
|
+
----------
|
|
373
|
+
pores : array_like
|
|
374
|
+
Indices of pores whose neighbors are sought
|
|
375
|
+
flatten : boolean, optional
|
|
376
|
+
If flatten is True (default) a 1D array of unique throat ID numbers
|
|
377
|
+
is returned. If flatten is False the returned array contains arrays
|
|
378
|
+
of neighboring throat ID numbers for each input pore, in the order
|
|
379
|
+
they were sent.
|
|
380
|
+
mode : string, optional
|
|
381
|
+
Specifies which neighbors should be returned. The options are:
|
|
382
|
+
|
|
383
|
+
* 'union' : All neighbors of the input pores
|
|
384
|
+
|
|
385
|
+
* 'intersection' : Only neighbors shared by all input pores
|
|
386
|
+
|
|
387
|
+
* 'not_intersection' : Only neighbors not shared by any input pores
|
|
388
|
+
|
|
389
|
+
Returns
|
|
390
|
+
-------
|
|
391
|
+
neighborTs : 1D array (if flatten is True) or ndarray of arrays (if
|
|
392
|
+
flatten if False)
|
|
393
|
+
|
|
394
|
+
Examples
|
|
395
|
+
--------
|
|
396
|
+
>>> import OpenPNM
|
|
397
|
+
>>> pn = OpenPNM.Network.TestNet()
|
|
398
|
+
>>> pn.find_neighbor_throats(pores=[0,1])
|
|
399
|
+
array([0, 1, 2, 3, 4, 5])
|
|
400
|
+
>>> pn.find_neighbor_throats(pores=[0,1],flatten=False)
|
|
401
|
+
array([array([0, 1, 2]), array([0, 3, 4, 5])], dtype=object)
|
|
402
|
+
"""
|
|
403
|
+
#Test for existence of incidence matrix
|
|
404
|
+
try:
|
|
405
|
+
neighborTs = self._incidence_matrix['lil'].rows[[pores]]
|
|
406
|
+
except:
|
|
407
|
+
temp = self.create_incidence_matrix(sprsfmt='lil')
|
|
408
|
+
self._incidence_matrix['lil'] = temp
|
|
409
|
+
neighborTs = self._incidence_matrix['lil'].rows[[pores]]
|
|
410
|
+
if [sp.asarray(x) for x in neighborTs if x] == []:
|
|
411
|
+
return sp.array([],ndmin=1)
|
|
412
|
+
if flatten:
|
|
413
|
+
#All the empty lists must be removed to maintain data type after hstack (numpy bug?)
|
|
414
|
+
neighborTs = [sp.asarray(x) for x in neighborTs if x]
|
|
415
|
+
neighborTs = sp.hstack(neighborTs)
|
|
416
|
+
#Remove references to input pores and duplicates
|
|
417
|
+
if mode == 'not_intersection':
|
|
418
|
+
neighborTs = sp.unique(sp.where(sp.bincount(neighborTs)==1)[0])
|
|
419
|
+
elif mode == 'union':
|
|
420
|
+
neighborTs = sp.unique(neighborTs)
|
|
421
|
+
elif mode == 'intersection':
|
|
422
|
+
neighborTs = sp.unique(sp.where(sp.bincount(neighborTs)>1)[0])
|
|
423
|
+
else:
|
|
424
|
+
for i in range(0,sp.size(pores)):
|
|
425
|
+
neighborTs[i] = sp.array(neighborTs[i])
|
|
426
|
+
return sp.array(neighborTs,ndmin=1)
|
|
427
|
+
|
|
428
|
+
def num_neighbors(self,pores,flatten=False):
|
|
429
|
+
r"""
|
|
430
|
+
Returns an ndarray containing the number of neigbhor pores for each
|
|
431
|
+
element in pores
|
|
432
|
+
|
|
433
|
+
Parameters
|
|
434
|
+
----------
|
|
435
|
+
pores : array_like
|
|
436
|
+
Pores whose neighbors are to be counted
|
|
437
|
+
flatten : boolean (optional)
|
|
438
|
+
If False (default) the number pore neighbors for each input are
|
|
439
|
+
returned as an array. If True the sum total number of unique
|
|
440
|
+
neighbors is counted, not including the input pores even if they
|
|
441
|
+
neighbor each other.
|
|
442
|
+
|
|
443
|
+
Returns
|
|
444
|
+
-------
|
|
445
|
+
num_neighbors : 1D array with number of neighbors in each element
|
|
446
|
+
|
|
447
|
+
Examples
|
|
448
|
+
--------
|
|
449
|
+
>>> import OpenPNM
|
|
450
|
+
>>> pn = OpenPNM.Network.TestNet()
|
|
451
|
+
>>> pn.num_neighbors(pores=[0,1],flatten=False)
|
|
452
|
+
array([3, 4])
|
|
453
|
+
>>> pn.num_neighbors(pores=[0,1],flatten=True) # Sum excludes pores 0 & 1
|
|
454
|
+
5
|
|
455
|
+
>>> pn.num_neighbors(pores=[0,2],flatten=True) # Sum includes pore 1, but not 0 & 2
|
|
456
|
+
6
|
|
457
|
+
"""
|
|
458
|
+
|
|
459
|
+
#Count number of neighbors
|
|
460
|
+
if flatten:
|
|
461
|
+
neighborPs = self.find_neighbor_pores(pores,flatten=True,mode='union',excl_self=True)
|
|
462
|
+
num = sp.shape(neighborPs)[0]
|
|
463
|
+
else:
|
|
464
|
+
neighborPs = self.find_neighbor_pores(pores,flatten=False)
|
|
465
|
+
num = sp.zeros(sp.shape(neighborPs),dtype=int)
|
|
466
|
+
for i in range(0,sp.shape(num)[0]):
|
|
467
|
+
num[i] = sp.size(neighborPs[i])
|
|
468
|
+
return num
|
|
469
|
+
|
|
470
|
+
def find_interface_throats(self,labels=[]):
|
|
471
|
+
r'''
|
|
472
|
+
Finds the throats that join two pore labels.
|
|
473
|
+
|
|
474
|
+
Parameters
|
|
475
|
+
----------
|
|
476
|
+
labels : list of strings
|
|
477
|
+
The labels of the two pore groups whose interface is sought
|
|
478
|
+
|
|
479
|
+
Returns
|
|
480
|
+
-------
|
|
481
|
+
An array of throat numbers that connect the given pore groups
|
|
482
|
+
|
|
483
|
+
Notes
|
|
484
|
+
-----
|
|
485
|
+
This method is meant to find interfaces between TWO groups, regions or
|
|
486
|
+
clusters of pores (as defined by their label). If the input labels
|
|
487
|
+
overlap or are not adjacent, an empty array is returned.
|
|
488
|
+
|
|
489
|
+
Examples
|
|
490
|
+
--------
|
|
491
|
+
>>> import OpenPNM
|
|
492
|
+
>>> pn = OpenPNM.Network.TestNet()
|
|
493
|
+
>>> pn['pore.domain1'] = False
|
|
494
|
+
>>> pn['pore.domain2'] = False
|
|
495
|
+
>>> pn['pore.domain1'][[0,1,2]] = True
|
|
496
|
+
>>> pn['pore.domain2'][[5,6,7]] = True
|
|
497
|
+
>>> pn.find_interface_throats(labels=['domain1','domain2'])
|
|
498
|
+
array([1, 4, 7])
|
|
499
|
+
'''
|
|
500
|
+
Tind = sp.array([],ndmin=1)
|
|
501
|
+
if sp.shape(labels)[0] != 2:
|
|
502
|
+
logger.error('Exactly two labels must be given')
|
|
503
|
+
pass
|
|
504
|
+
else:
|
|
505
|
+
P1 = self.pores(labels=labels[0])
|
|
506
|
+
P2 = self.pores(labels=labels[1])
|
|
507
|
+
#Check if labels overlap
|
|
508
|
+
if sp.sum(sp.in1d(P1,P2)) > 0:
|
|
509
|
+
logger.error('Some labels overlap, iterface cannot be found')
|
|
510
|
+
pass
|
|
511
|
+
else:
|
|
512
|
+
T1 = self.find_neighbor_throats(P1)
|
|
513
|
+
T2 = self.find_neighbor_throats(P2)
|
|
514
|
+
Tmask = sp.in1d(T1,T2)
|
|
515
|
+
Tind = T1[Tmask]
|
|
516
|
+
return Tind
|
|
517
|
+
|
|
518
|
+
def find_clusters(self,mask=[]):
|
|
519
|
+
r'''
|
|
520
|
+
Identify connected clusters of pores in the network.
|
|
521
|
+
|
|
522
|
+
Parameters
|
|
523
|
+
----------
|
|
524
|
+
mask : array_like, boolean
|
|
525
|
+
A list of active nodes. This method will automatically search
|
|
526
|
+
for clusters based on site or bond connectivity depending on
|
|
527
|
+
wheather the received mask is Np or Nt long.
|
|
528
|
+
|
|
529
|
+
Returns
|
|
530
|
+
-------
|
|
531
|
+
clusters : array_like
|
|
532
|
+
An Np long list of clusters numbers
|
|
533
|
+
|
|
534
|
+
'''
|
|
535
|
+
if sp.shape(mask)[0] == self.num_throats():
|
|
536
|
+
#Convert to boolean mask if not already
|
|
537
|
+
temp = sp.zeros((self.num_throats(),),dtype=bool)
|
|
538
|
+
temp[mask] = True
|
|
539
|
+
elif sp.shape(mask)[0] == self.num_pores():
|
|
540
|
+
conns = self.find_connected_pores(throats=self.throats())
|
|
541
|
+
conns[:,0] = mask[conns[:,0]]
|
|
542
|
+
conns[:,1] = mask[conns[:,1]]
|
|
543
|
+
temp = sp.array(conns[:,0]*conns[:,1],dtype=bool)
|
|
544
|
+
else:
|
|
545
|
+
raise Exception('Mask received was neither Nt nor Np long')
|
|
546
|
+
temp = self.create_adjacency_matrix(data=temp, sprsfmt='csr', dropzeros=True)
|
|
547
|
+
clusters = sprs.csgraph.connected_components(csgraph=temp,directed=False)[1]
|
|
548
|
+
return clusters
|
|
549
|
+
|
|
550
|
+
def _find_nearest_pores(self,pores,distance=0):
|
|
551
|
+
r'''
|
|
552
|
+
Still a work in progress, but will be useful for finding spatially
|
|
553
|
+
near, but not topologically connected pores.
|
|
554
|
+
'''
|
|
555
|
+
kd = sptl.cKDTree(self['pore.coords'])
|
|
556
|
+
if distance == 0:
|
|
557
|
+
pass
|
|
558
|
+
elif distance > 0:
|
|
559
|
+
Pn = kd.query_ball_point(self['pore.coords'][pores])[1]
|
|
560
|
+
return Pn
|
|
561
|
+
|
|
562
|
+
#--------------------------------------------------------------------------
|
|
563
|
+
'''Network Manipulation Methods'''
|
|
564
|
+
#--------------------------------------------------------------------------
|
|
565
|
+
def clone_pores(self,pores,apply_label=['clone'],mode='parents'):
|
|
566
|
+
r'''
|
|
567
|
+
Clones the specified pores and adds them to the network
|
|
568
|
+
|
|
569
|
+
Parameters
|
|
570
|
+
----------
|
|
571
|
+
pores : array_like
|
|
572
|
+
List of pores to clone
|
|
573
|
+
apply_labels : string, or list of strings
|
|
574
|
+
The labels to apply to the clones, default is 'clone'
|
|
575
|
+
mode : string
|
|
576
|
+
Controls the connections between parents and clones. Options are:
|
|
577
|
+
|
|
578
|
+
- 'parents': (Default) Each clone is connected only to its parent
|
|
579
|
+
- 'siblings': Clones are only connected to each other in the same manner as parents were connected
|
|
580
|
+
- 'isolated': No connections between parents or siblings
|
|
581
|
+
'''
|
|
582
|
+
if (self._geometries != []):
|
|
583
|
+
logger.warning('Network has active Geometries, new pores must be assigned a Geometry')
|
|
584
|
+
if (self._phases != []):
|
|
585
|
+
raise Exception('Network has active Phases, cannot proceed')
|
|
586
|
+
|
|
587
|
+
logger.debug('Cloning pores')
|
|
588
|
+
apply_label = list(apply_label)
|
|
589
|
+
#Clone pores
|
|
590
|
+
Np = self.num_pores()
|
|
591
|
+
Nt = self.num_throats()
|
|
592
|
+
parents = sp.array(pores,ndmin=1)
|
|
593
|
+
pcurrent = self['pore.coords']
|
|
594
|
+
pclone = pcurrent[pores,:]
|
|
595
|
+
pnew = sp.concatenate((pcurrent,pclone),axis=0)
|
|
596
|
+
Npnew = sp.shape(pnew)[0]
|
|
597
|
+
clones = sp.arange(Np,Npnew)
|
|
598
|
+
#Add clone labels to network
|
|
599
|
+
for item in apply_label:
|
|
600
|
+
if ('pore.'+item) not in self.keys():
|
|
601
|
+
self['pore.'+item] = False
|
|
602
|
+
if ('throat.'+item) not in self.keys():
|
|
603
|
+
self['throat.'+item] = False
|
|
604
|
+
#Add connections between parents and clones
|
|
605
|
+
if mode == 'parents':
|
|
606
|
+
tclone = sp.vstack((parents,clones)).T
|
|
607
|
+
self.extend(pore_coords=pclone,throat_conns=tclone)
|
|
608
|
+
if mode == 'siblings':
|
|
609
|
+
ts = self.find_neighbor_throats(pores=pores,mode='intersection')
|
|
610
|
+
tclone = self['throat.conns'][ts] + self.num_pores()
|
|
611
|
+
self.extend(pore_coords=pclone,throat_conns=tclone)
|
|
612
|
+
if mode == 'isolated':
|
|
613
|
+
self.extend(pore_coords=pclone)
|
|
614
|
+
#Apply provided labels to cloned pores
|
|
615
|
+
for item in apply_label:
|
|
616
|
+
self['pore.'+item][self.pores('all')>=Np] = True
|
|
617
|
+
self['throat.'+item][self.throats('all')>=Nt] = True
|
|
618
|
+
|
|
619
|
+
# Any existing adjacency and incidence matrices will be invalid
|
|
620
|
+
self._update_network()
|
|
621
|
+
|
|
622
|
+
def extend(self,pore_coords=[],throat_conns=[],labels=[]):
|
|
623
|
+
r'''
|
|
624
|
+
Add individual pores (or throats) to the network from a list of coords
|
|
625
|
+
or conns.
|
|
626
|
+
|
|
627
|
+
Parameters
|
|
628
|
+
----------
|
|
629
|
+
pore_coords : array_like
|
|
630
|
+
The coordinates of the pores to add
|
|
631
|
+
throat_conns : array_like
|
|
632
|
+
The throat connections to add
|
|
633
|
+
labels : string, or list of strings, optional
|
|
634
|
+
A list of labels to apply to the new pores and throats
|
|
635
|
+
|
|
636
|
+
Notes
|
|
637
|
+
-----
|
|
638
|
+
This needs to be enhanced so that it increases the size of all pore
|
|
639
|
+
and throat props and labels on ALL associated objects. At the moment
|
|
640
|
+
if throws an error is there are ANY associated objects.
|
|
641
|
+
|
|
642
|
+
'''
|
|
643
|
+
if (self._geometries != []):
|
|
644
|
+
logger.warning('Network has active Geometries, new pores must be assigned a Geometry')
|
|
645
|
+
if (self._phases != []):
|
|
646
|
+
raise Exception('Network has active Phases, cannot proceed')
|
|
647
|
+
|
|
648
|
+
logger.debug('Extending network')
|
|
649
|
+
Np_old = self.num_pores()
|
|
650
|
+
Nt_old = self.num_throats()
|
|
651
|
+
Np = Np_old + int(sp.size(pore_coords)/3)
|
|
652
|
+
Nt = Nt_old + int(sp.size(throat_conns)/2)
|
|
653
|
+
#Adjust 'all' labels
|
|
654
|
+
del self['pore.all'], self['throat.all']
|
|
655
|
+
self['pore.all'] = sp.ones((Np,),dtype=bool)
|
|
656
|
+
self['throat.all'] = sp.ones((Nt,),dtype=bool)
|
|
657
|
+
#Add coords and conns
|
|
658
|
+
if pore_coords != []:
|
|
659
|
+
coords = sp.vstack((self['pore.coords'],pore_coords))
|
|
660
|
+
self['pore.coords'] = coords
|
|
661
|
+
if throat_conns != []:
|
|
662
|
+
conns = sp.vstack((self['throat.conns'],throat_conns))
|
|
663
|
+
self['throat.conns'] = conns
|
|
664
|
+
for item in self.keys():
|
|
665
|
+
if item.split('.')[1] not in ['coords','conns','all']:
|
|
666
|
+
if item.split('.')[0] == 'pore':
|
|
667
|
+
N = Np
|
|
668
|
+
else:
|
|
669
|
+
N = Nt
|
|
670
|
+
if self[item].dtype == bool:
|
|
671
|
+
temp = self[item]
|
|
672
|
+
self[item] = sp.zeros((N,),dtype=bool)
|
|
673
|
+
self[item][temp] = True
|
|
674
|
+
elif self[item].dtype == object:
|
|
675
|
+
temp = self[item]
|
|
676
|
+
self[item] = sp.ndarray((N,),dtype=object)
|
|
677
|
+
self[item][sp.arange(0,sp.shape(temp)[0])] = temp
|
|
678
|
+
else:
|
|
679
|
+
temp = self[item]
|
|
680
|
+
try:
|
|
681
|
+
self[item] = sp.ones((N,sp.shape(temp)[1]),dtype=float)*sp.nan
|
|
682
|
+
except:
|
|
683
|
+
self[item] = sp.ones((N,),dtype=float)*sp.nan
|
|
684
|
+
self[item][sp.arange(0,sp.shape(temp)[0])] = temp
|
|
685
|
+
#Apply labels, if supplied
|
|
686
|
+
if labels != []:
|
|
687
|
+
#Convert labels to list if necessary
|
|
688
|
+
if type(labels) is str:
|
|
689
|
+
labels = [labels]
|
|
690
|
+
for label in labels:
|
|
691
|
+
#Remove pore or throat from label, if present
|
|
692
|
+
label = label.split('.')[-1]
|
|
693
|
+
if pore_coords != []:
|
|
694
|
+
Ps = sp.r_[Np_old:Np]
|
|
695
|
+
if 'pore.'+label not in self.labels():
|
|
696
|
+
self['pore.'+label] = False
|
|
697
|
+
self['pore.'+label][Ps] = True
|
|
698
|
+
if throat_conns != []:
|
|
699
|
+
Ts = sp.r_[Nt_old:Nt]
|
|
700
|
+
if 'throat.'+label not in self.labels():
|
|
701
|
+
self['throat.'+label] = False
|
|
702
|
+
self['throat.'+label][Ts] = True
|
|
703
|
+
|
|
704
|
+
self._update_network()
|
|
705
|
+
|
|
706
|
+
def trim(self, pores=[], throats=[]):
|
|
707
|
+
'''
|
|
708
|
+
Remove pores (or throats) from the network.
|
|
709
|
+
|
|
710
|
+
Parameters
|
|
711
|
+
----------
|
|
712
|
+
pores (or throats) : array_like
|
|
713
|
+
A boolean mask of length Np (or Nt) or a list of indices of the
|
|
714
|
+
pores (or throats) to be removed.
|
|
715
|
+
|
|
716
|
+
Notes
|
|
717
|
+
-----
|
|
718
|
+
Trimming only adjusts Phase, Geometry, and Physics objects. Trimming a
|
|
719
|
+
Network that has already been used to run simulations will break those
|
|
720
|
+
simulation objects.
|
|
721
|
+
|
|
722
|
+
Examples
|
|
723
|
+
--------
|
|
724
|
+
>>> import OpenPNM
|
|
725
|
+
>>> pn = OpenPNM.Network.TestNet()
|
|
726
|
+
>>> pn.Np
|
|
727
|
+
125
|
|
728
|
+
>>> pn.Nt
|
|
729
|
+
300
|
|
730
|
+
>>> pn.trim(pores=[1])
|
|
731
|
+
>>> pn.Np
|
|
732
|
+
124
|
|
733
|
+
>>> pn.Nt
|
|
734
|
+
296
|
|
735
|
+
|
|
736
|
+
'''
|
|
737
|
+
for net in self.controller.networks():
|
|
738
|
+
if net._parent is self:
|
|
739
|
+
raise Exception('This Network has been cloned, cannot trim')
|
|
740
|
+
|
|
741
|
+
if len(pores) > 0:
|
|
742
|
+
pores = sp.array(pores,ndmin=1)
|
|
743
|
+
Pkeep = sp.ones((self.num_pores(),),dtype=bool)
|
|
744
|
+
Pkeep[pores] = False
|
|
745
|
+
Tkeep = sp.ones((self.num_throats(),),dtype=bool)
|
|
746
|
+
Ts = self.find_neighbor_throats(pores)
|
|
747
|
+
if len(Ts)>0:
|
|
748
|
+
Tkeep[Ts] = False
|
|
749
|
+
elif len(throats) > 0:
|
|
750
|
+
throats = sp.array(throats,ndmin=1)
|
|
751
|
+
Tkeep = sp.ones((self.num_throats(),),dtype=bool)
|
|
752
|
+
Tkeep[throats] = False
|
|
753
|
+
Pkeep = self['pore.all'].copy()
|
|
754
|
+
else:
|
|
755
|
+
logger.warning('No pores or throats recieved')
|
|
756
|
+
return
|
|
757
|
+
|
|
758
|
+
# Trim all associated objects
|
|
759
|
+
for item in self._geometries+self._physics+self._phases:
|
|
760
|
+
Pnet = self['pore.'+item.name]*Pkeep
|
|
761
|
+
Tnet = self['throat.'+item.name]*Tkeep
|
|
762
|
+
temp = self.map_pores(pores=sp.where(Pnet)[0],target=item,return_mapping=True)
|
|
763
|
+
Ps = temp['target']
|
|
764
|
+
temp = self.map_throats(throats=sp.where(Tnet)[0],target=item,return_mapping=True)
|
|
765
|
+
Ts = temp['target']
|
|
766
|
+
# Then resize 'all
|
|
767
|
+
item.update({'pore.all' : sp.ones((sp.sum(Pnet),),dtype=bool)})
|
|
768
|
+
item.update({'throat.all' : sp.ones((sp.sum(Tnet),),dtype=bool)})
|
|
769
|
+
# Overwrite remaining data and info
|
|
770
|
+
for key in list(item.keys()):
|
|
771
|
+
if key.split('.')[1] not in ['all']:
|
|
772
|
+
temp = item.pop(key)
|
|
773
|
+
if key.split('.')[0] == 'throat':
|
|
774
|
+
logger.debug('Trimming {a} from {b}'.format(a=key,b=item.name))
|
|
775
|
+
item[key] = temp[Ts]
|
|
776
|
+
if key.split('.')[0] == 'pore':
|
|
777
|
+
logger.debug('Trimming {a} from {b}'.format(a=key,b=item.name))
|
|
778
|
+
item[key] = temp[Ps]
|
|
779
|
+
|
|
780
|
+
#Remap throat connections
|
|
781
|
+
Pmap = sp.ones((self.Np,),dtype=int)*-1
|
|
782
|
+
Pmap[Pkeep] = sp.arange(0,sp.sum(Pkeep))
|
|
783
|
+
tpore1 = self['throat.conns'][:,0]
|
|
784
|
+
tpore2 = self['throat.conns'][:,1]
|
|
785
|
+
Tnew1 = Pmap[tpore1[Tkeep]]
|
|
786
|
+
Tnew2 = Pmap[tpore2[Tkeep]]
|
|
787
|
+
#Write 'all' label specifically
|
|
788
|
+
self.update({'throat.all' : sp.ones((sp.sum(Tkeep),),dtype=bool)})
|
|
789
|
+
self.update({'pore.all' : sp.ones((sp.sum(Pkeep),),dtype=bool)})
|
|
790
|
+
# Write throat connections specifically
|
|
791
|
+
self.update({'throat.conns' : sp.vstack((Tnew1,Tnew2)).T})
|
|
792
|
+
# Overwrite remaining data and info
|
|
793
|
+
for item in list(self.keys()):
|
|
794
|
+
if item.split('.')[-1] not in ['conns','all']:
|
|
795
|
+
temp = self.pop(item)
|
|
796
|
+
if item.split('.')[0] == 'throat':
|
|
797
|
+
logger.debug('Trimming {a} from {b}'.format(a=item,b=self.name))
|
|
798
|
+
self[item] = temp[Tkeep]
|
|
799
|
+
if item.split('.')[0] == 'pore':
|
|
800
|
+
logger.debug('Trimming {a} from {b}'.format(a=item,b=self.name))
|
|
801
|
+
self[item] = temp[Pkeep]
|
|
802
|
+
|
|
803
|
+
#Reset network graphs
|
|
804
|
+
self._update_network(mode='regenerate')
|
|
805
|
+
|
|
806
|
+
#Check Network health
|
|
807
|
+
health = self.check_network_health()
|
|
808
|
+
if health['trim_pores'] != []:
|
|
809
|
+
logger.warning('Isolated pores exist! Run check_network_health to ID which pores to remove.')
|
|
810
|
+
pass
|
|
811
|
+
|
|
812
|
+
def stitch(self,donor,pores_1,pores_2,method='delaunay',len_max=sp.inf,label_suffix=''):
|
|
813
|
+
r'''
|
|
814
|
+
Stitches a second a network to the current network.
|
|
815
|
+
|
|
816
|
+
Parameters
|
|
817
|
+
----------
|
|
818
|
+
donor : OpenPNM Network Object
|
|
819
|
+
The Network to stitch on to the current Network
|
|
820
|
+
|
|
821
|
+
pores_1 : array_like
|
|
822
|
+
The pores on the current Network
|
|
823
|
+
|
|
824
|
+
pores_2 : array_like
|
|
825
|
+
The pores on the donor Network
|
|
826
|
+
|
|
827
|
+
label_suffix : string or None
|
|
828
|
+
Some text to append to each label in the donor Network before
|
|
829
|
+
inserting them into the recipient. The default is to append no
|
|
830
|
+
text, but a common option would be to append the donor Network's
|
|
831
|
+
name. To insert none of the donor labels, use None.
|
|
832
|
+
|
|
833
|
+
len_max : float
|
|
834
|
+
Set a length limit on length of new throats
|
|
835
|
+
|
|
836
|
+
method : string (default = 'delaunay')
|
|
837
|
+
The method to use when making pore to pore connections. Options are:
|
|
838
|
+
|
|
839
|
+
- 'delaunay' : Use a Delaunay tessellation
|
|
840
|
+
- 'nearest' : Connects each pore on the receptor network to its nearest pore on the donor network
|
|
841
|
+
|
|
842
|
+
Notes
|
|
843
|
+
-----
|
|
844
|
+
Before stitching it is necessary to translate the pore coordinates of
|
|
845
|
+
one of the Networks so that it is positioned correctly relative to the
|
|
846
|
+
other.
|
|
847
|
+
|
|
848
|
+
Examples
|
|
849
|
+
--------
|
|
850
|
+
>>> import OpenPNM
|
|
851
|
+
>>> pn = OpenPNM.Network.TestNet()
|
|
852
|
+
>>> pn2 = OpenPNM.Network.TestNet()
|
|
853
|
+
>>> [pn.Np, pn.Nt]
|
|
854
|
+
[125, 300]
|
|
855
|
+
>>> [pn2.Np, pn2.Nt]
|
|
856
|
+
[125, 300]
|
|
857
|
+
>>> pn2['pore.coords'][:,2] += 5.0 # Translate pn2 up 5 units in the Z-direction
|
|
858
|
+
>>> pn.stitch(donor=pn2,pores_1=pn.pores('top'),pores_2=pn2.pores('bottom'),len_max=1.0)
|
|
859
|
+
>>> [pn.Np, pn.Nt]
|
|
860
|
+
[250, 625]
|
|
861
|
+
|
|
862
|
+
'''
|
|
863
|
+
# Ensure Networks have no associated objects yet
|
|
864
|
+
if (len(self._simulation()) > 1) or (len(donor._simulation()) > 1):
|
|
865
|
+
raise Exception('Cannot stitch a Network with active sibling objects')
|
|
866
|
+
# Get the initial number of pores and throats
|
|
867
|
+
N_init = {}
|
|
868
|
+
N_init['pore'] = self.Np
|
|
869
|
+
N_init['throat'] = self.Nt
|
|
870
|
+
if method == 'delaunay':
|
|
871
|
+
P1 = pores_1
|
|
872
|
+
P2 = pores_2 + N_init['pore'] # Increment pores on donor
|
|
873
|
+
P = sp.hstack((P1,P2))
|
|
874
|
+
C1 = self['pore.coords'][pores_1]
|
|
875
|
+
C2 = donor['pore.coords'][pores_2]
|
|
876
|
+
C = sp.vstack((C1,C2))
|
|
877
|
+
T = sp.spatial.Delaunay(C)
|
|
878
|
+
a = T.simplices
|
|
879
|
+
b = []
|
|
880
|
+
for i in range(0,4): # Turn simplices into pairs of connections
|
|
881
|
+
for j in range(0,4):
|
|
882
|
+
if i != j:
|
|
883
|
+
b.append(sp.vstack((a[:,i],a[:,j])).T)
|
|
884
|
+
c = sp.vstack((b)) # Convert list b to numpy array
|
|
885
|
+
#Remove thraot connections to self
|
|
886
|
+
K1 = sp.in1d(P[c][:,0],P1)*sp.in1d(P[c][:,1],P2)
|
|
887
|
+
K2 = sp.in1d(P[c][:,0],P2)*sp.in1d(P[c][:,1],P1)
|
|
888
|
+
K = sp.where(K1 + K2)[0]
|
|
889
|
+
#Remove duplicate throat connections
|
|
890
|
+
i = P[c][K][:,0]
|
|
891
|
+
j = P[c][K][:,1]
|
|
892
|
+
v = sp.ones_like(i)
|
|
893
|
+
N = self.Np + donor.Np
|
|
894
|
+
adjmat = sprs.coo.coo_matrix((v,(i,j)),shape=(N,N))
|
|
895
|
+
#Convert to csr and back to coo to remove duplicates
|
|
896
|
+
adjmat = adjmat.tocsr()
|
|
897
|
+
adjmat = adjmat.tocoo()
|
|
898
|
+
#Remove lower triangular to remove bidirectional
|
|
899
|
+
adjmat = sprs.triu(adjmat,k=1,format="coo")
|
|
900
|
+
#Convert adjmat into 'throat.conns'
|
|
901
|
+
conns = sp.vstack((adjmat.row, adjmat.col)).T
|
|
902
|
+
if method == 'nearest':
|
|
903
|
+
P1 = pores_1
|
|
904
|
+
P2 = pores_2 + N_init['pore'] # Increment pores on donor
|
|
905
|
+
P = sp.hstack((P1,P2))
|
|
906
|
+
C1 = self['pore.coords'][pores_1]
|
|
907
|
+
C2 = donor['pore.coords'][pores_2]
|
|
908
|
+
D = sp.spatial.distance.cdist(C1,C2)
|
|
909
|
+
[P1_ind,P2_ind] = sp.where(D<=len_max)
|
|
910
|
+
conns = sp.vstack((P1[P1_ind],P2[P2_ind])).T
|
|
911
|
+
|
|
912
|
+
#Enter donor's pores into the Network
|
|
913
|
+
self.extend(pore_coords=donor['pore.coords'])
|
|
914
|
+
|
|
915
|
+
#Enter donor's throats into the Network
|
|
916
|
+
self.extend(throat_conns=donor['throat.conns']+N_init['pore'])
|
|
917
|
+
|
|
918
|
+
#Trim throats that are longer then given len_max
|
|
919
|
+
C1 = self['pore.coords'][conns[:,0]]
|
|
920
|
+
C2 = self['pore.coords'][conns[:,1]]
|
|
921
|
+
L = sp.sum((C1 - C2)**2,axis=1)**0.5
|
|
922
|
+
# conns = conns[L<=len_max]
|
|
923
|
+
|
|
924
|
+
#Add donor labels to recipient network
|
|
925
|
+
if label_suffix != None:
|
|
926
|
+
if label_suffix != '':
|
|
927
|
+
label_suffix = '_'+label_suffix
|
|
928
|
+
for label in donor.labels():
|
|
929
|
+
element = label.split('.')[0]
|
|
930
|
+
locations = sp.where(self._get_indices(element)>=N_init[element])[0]
|
|
931
|
+
try:
|
|
932
|
+
self[label+label_suffix]
|
|
933
|
+
except:
|
|
934
|
+
self[label+label_suffix] = False
|
|
935
|
+
self[label+label_suffix][locations] = donor[label]
|
|
936
|
+
|
|
937
|
+
#Add the new stitch throats to the Network
|
|
938
|
+
self.extend(throat_conns=conns,labels='stitched')
|
|
939
|
+
|
|
940
|
+
# Remove donor from Controller, if present
|
|
941
|
+
# This check allows for the reuse of a donor Network multiple times
|
|
942
|
+
if donor in ctrl.values():
|
|
943
|
+
ctrl.purge_object(donor)
|
|
944
|
+
|
|
945
|
+
def check_network_health(self):
|
|
946
|
+
r'''
|
|
947
|
+
This method check the network topological health by checking for:
|
|
948
|
+
|
|
949
|
+
(1) Isolated pores
|
|
950
|
+
(2) Islands or isolated clusters of pores
|
|
951
|
+
(3) Duplicate throats
|
|
952
|
+
(4) Bidirectional throats (ie. symmetrical adjacency matrix)
|
|
953
|
+
|
|
954
|
+
Returns
|
|
955
|
+
-------
|
|
956
|
+
A dictionary containing the offending pores or throat numbers under
|
|
957
|
+
each named key.
|
|
958
|
+
|
|
959
|
+
It also returns a list of which pores and throats should be trimmed
|
|
960
|
+
from the network to restore health. This list is a suggestion only,
|
|
961
|
+
and is based on keeping the largest cluster and trimming the others.
|
|
962
|
+
|
|
963
|
+
Notes
|
|
964
|
+
-----
|
|
965
|
+
- Does not yet check for duplicate pores
|
|
966
|
+
- Does not yet suggest which throats to remove
|
|
967
|
+
- This is just a 'check' method and does not 'fix' the problems it finds
|
|
968
|
+
'''
|
|
969
|
+
|
|
970
|
+
health = Tools.HealthDict()
|
|
971
|
+
health['disconnected_clusters'] = []
|
|
972
|
+
health['isolated_pores'] = []
|
|
973
|
+
health['trim_pores'] = []
|
|
974
|
+
health['duplicate_throats'] = []
|
|
975
|
+
health['bidirectional_throats'] = []
|
|
976
|
+
|
|
977
|
+
#Check for individual isolated pores
|
|
978
|
+
Ps = self.num_neighbors(self.pores())
|
|
979
|
+
if sp.sum(Ps==0) > 0:
|
|
980
|
+
logger.warning(str(sp.sum(Ps==0))+' pores have no neighbors')
|
|
981
|
+
health['isolated_pores'] = sp.where(Ps==0)[0]
|
|
982
|
+
|
|
983
|
+
#Check for separated clusters of pores
|
|
984
|
+
temp = []
|
|
985
|
+
Cs = self.find_clusters(self.tomask(throats=self.throats('all')))
|
|
986
|
+
if sp.shape(sp.unique(Cs))[0] > 1:
|
|
987
|
+
logger.warning('Isolated clusters exist in the network')
|
|
988
|
+
for i in sp.unique(Cs):
|
|
989
|
+
temp.append(sp.where(Cs==i)[0])
|
|
990
|
+
b = sp.array([len(item) for item in temp])
|
|
991
|
+
c = sp.argsort(b)[::-1]
|
|
992
|
+
for i in range(0,len(c)):
|
|
993
|
+
health['disconnected_clusters'].append(temp[c[i]])
|
|
994
|
+
if i > 0:
|
|
995
|
+
health['trim_pores'].extend(temp[c[i]])
|
|
996
|
+
|
|
997
|
+
#Check for duplicate throats
|
|
998
|
+
i = self['throat.conns'][:,0]
|
|
999
|
+
j = self['throat.conns'][:,1]
|
|
1000
|
+
v = sp.array(self['throat.all'],dtype=int)
|
|
1001
|
+
Np = self.num_pores()
|
|
1002
|
+
adjmat = sprs.coo_matrix((v,(i,j)),[Np,Np])
|
|
1003
|
+
temp = adjmat.tocsr() # Convert to CSR to combine duplicates
|
|
1004
|
+
temp = adjmat.tocoo() # And back to COO
|
|
1005
|
+
mergedTs = sp.where(temp.data>1)
|
|
1006
|
+
Ps12 = sp.vstack((temp.row[mergedTs], temp.col[mergedTs])).T
|
|
1007
|
+
dupTs = []
|
|
1008
|
+
for i in range(0,sp.shape(Ps12)[0]):
|
|
1009
|
+
dupTs.append(self.find_connecting_throat(Ps12[i,0],Ps12[i,1]).tolist)
|
|
1010
|
+
health['duplicate_throats'] = dupTs
|
|
1011
|
+
|
|
1012
|
+
#Check for bidirectional throats
|
|
1013
|
+
num_full = adjmat.sum()
|
|
1014
|
+
temp = sprs.triu(adjmat,k=1)
|
|
1015
|
+
num_upper = temp.sum()
|
|
1016
|
+
if num_full > num_upper:
|
|
1017
|
+
health['bidirectional_throats'] = str(num_full-num_upper)+' detected!'
|
|
1018
|
+
|
|
1019
|
+
#Check for coincident pores
|
|
1020
|
+
# temp = misc.dist(self['pore.coords'],self['pore.coords'])
|
|
1021
|
+
# temp = sp.triu(temp,k=1) # Remove lower triangular of matrix
|
|
1022
|
+
# temp = sp.where(temp==0) # Find 0 values in distance matrix
|
|
1023
|
+
# dupPs = sp.where(temp[1]>temp[0])[0] # Find 0 values above diagonal
|
|
1024
|
+
# health['duplicate_pores'] = dupPs
|
|
1025
|
+
|
|
1026
|
+
return health
|
|
1027
|
+
|
|
1028
|
+
def check_geometry_health(self):
|
|
1029
|
+
r'''
|
|
1030
|
+
Perform a check to find pores with overlapping or undefined Geometries
|
|
1031
|
+
'''
|
|
1032
|
+
geoms = self.geometries()
|
|
1033
|
+
Ptemp = sp.zeros((self.Np,))
|
|
1034
|
+
Ttemp = sp.zeros((self.Nt,))
|
|
1035
|
+
for item in geoms:
|
|
1036
|
+
Pind = self['pore.'+item]
|
|
1037
|
+
Tind = self['throat.'+item]
|
|
1038
|
+
Ptemp[Pind] = Ptemp[Pind] + 1
|
|
1039
|
+
Ttemp[Tind] = Ttemp[Tind] + 1
|
|
1040
|
+
health = Tools.HealthDict()
|
|
1041
|
+
health['overlapping_pores'] = sp.where(Ptemp>1)[0].tolist()
|
|
1042
|
+
health['undefined_pores'] = sp.where(Ptemp==0)[0].tolist()
|
|
1043
|
+
health['overlapping_throats'] = sp.where(Ttemp>1)[0].tolist()
|
|
1044
|
+
health['undefined_throats'] = sp.where(Ttemp==0)[0].tolist()
|
|
1045
|
+
return health
|
|
1046
|
+
|
|
1047
|
+
def _update_network(self,mode='clear'):
|
|
1048
|
+
r'''
|
|
1049
|
+
Regenerates the adjacency and incidence matrices
|
|
1050
|
+
|
|
1051
|
+
Parameters
|
|
1052
|
+
----------
|
|
1053
|
+
mode : string
|
|
1054
|
+
Controls the extent of the update. Options are:
|
|
1055
|
+
|
|
1056
|
+
- 'clear' : Removes exsiting adjacency and incidence matrices
|
|
1057
|
+
- 'regenerate' : Removes the existing matrices and regenerates new ones.
|
|
1058
|
+
|
|
1059
|
+
Notes
|
|
1060
|
+
-----
|
|
1061
|
+
The 'regenerate' mode is more time consuming, so repeated calls to
|
|
1062
|
+
this function (ie. during network merges, and adding boundaries)
|
|
1063
|
+
should use the 'clear' mode. The other methods that require these
|
|
1064
|
+
matrices will generate them as needed, so this pushes the 'generation'
|
|
1065
|
+
time to 'on demand'.
|
|
1066
|
+
'''
|
|
1067
|
+
logger.debug('Resetting adjacency and incidence matrices')
|
|
1068
|
+
self._adjacency_matrix['coo'] = {}
|
|
1069
|
+
self._adjacency_matrix['csr'] = {}
|
|
1070
|
+
self._adjacency_matrix['lil'] = {}
|
|
1071
|
+
self._incidence_matrix['coo'] = {}
|
|
1072
|
+
self._incidence_matrix['csr'] = {}
|
|
1073
|
+
self._incidence_matrix['lil'] = {}
|
|
1074
|
+
|
|
1075
|
+
if mode == 'regenerate':
|
|
1076
|
+
self._adjacency_matrix['coo'] = self.create_adjacency_matrix(sprsfmt='coo')
|
|
1077
|
+
self._adjacency_matrix['csr'] = self.create_adjacency_matrix(sprsfmt='csr')
|
|
1078
|
+
self._adjacency_matrix['lil'] = self.create_adjacency_matrix(sprsfmt='lil')
|
|
1079
|
+
self._incidence_matrix['coo'] = self.create_incidence_matrix(sprsfmt='coo')
|
|
1080
|
+
self._incidence_matrix['csr'] = self.create_incidence_matrix(sprsfmt='csr')
|
|
1081
|
+
self._incidence_matrix['lil'] = self.create_incidence_matrix(sprsfmt='lil')
|
|
1082
|
+
|
|
1083
|
+
#--------------------------------------------------------------------------
|
|
1084
|
+
'''Domain Geometry Methods'''
|
|
1085
|
+
#--------------------------------------------------------------------------
|
|
1086
|
+
def domain_bulk_volume(self):
|
|
1087
|
+
r'''
|
|
1088
|
+
'''
|
|
1089
|
+
raise NotImplementedError()
|
|
1090
|
+
|
|
1091
|
+
def domain_pore_volume(self):
|
|
1092
|
+
r'''
|
|
1093
|
+
'''
|
|
1094
|
+
raise NotImplementedError()
|
|
1095
|
+
|
|
1096
|
+
def domain_length(self,face_1,face_2):
|
|
1097
|
+
r'''
|
|
1098
|
+
Calculate the distance between two faces of the network
|
|
1099
|
+
|
|
1100
|
+
Parameters
|
|
1101
|
+
----------
|
|
1102
|
+
face_1 and face_2 : array_like
|
|
1103
|
+
Lists of pores belonging to opposite faces of the network
|
|
1104
|
+
|
|
1105
|
+
Returns
|
|
1106
|
+
-------
|
|
1107
|
+
The length of the domain in the specified direction
|
|
1108
|
+
|
|
1109
|
+
Notes
|
|
1110
|
+
-----
|
|
1111
|
+
- Does not yet check if input faces are perpendicular to each other
|
|
1112
|
+
'''
|
|
1113
|
+
#Ensure given points are coplanar before proceeding
|
|
1114
|
+
if misc.iscoplanar(self['pore.coords'][face_1]) and misc.iscoplanar(self['pore.coords'][face_2]):
|
|
1115
|
+
#Find distance between given faces
|
|
1116
|
+
x = self['pore.coords'][face_1]
|
|
1117
|
+
y = self['pore.coords'][face_2]
|
|
1118
|
+
Ds = misc.dist(x,y)
|
|
1119
|
+
L = sp.median(sp.amin(Ds,axis=0))
|
|
1120
|
+
else:
|
|
1121
|
+
logger.warning('The supplied pores are not coplanar. Length will be approximate.')
|
|
1122
|
+
f1 = self['pore.coords'][face_1]
|
|
1123
|
+
f2 = self['pore.coords'][face_2]
|
|
1124
|
+
distavg = [0,0,0]
|
|
1125
|
+
distavg[0] = sp.absolute(sp.average(f1[:,0]) - sp.average(f2[:,0]))
|
|
1126
|
+
distavg[1] = sp.absolute(sp.average(f1[:,1]) - sp.average(f2[:,1]))
|
|
1127
|
+
distavg[2] = sp.absolute(sp.average(f1[:,2]) - sp.average(f2[:,2]))
|
|
1128
|
+
L = max(distavg)
|
|
1129
|
+
return L
|
|
1130
|
+
|
|
1131
|
+
|
|
1132
|
+
def domain_area(self,face):
|
|
1133
|
+
r'''
|
|
1134
|
+
Calculate the area of a given network face
|
|
1135
|
+
|
|
1136
|
+
Parameters
|
|
1137
|
+
----------
|
|
1138
|
+
face : array_like
|
|
1139
|
+
List of pores of pore defining the face of interest
|
|
1140
|
+
|
|
1141
|
+
Returns
|
|
1142
|
+
-------
|
|
1143
|
+
The area of the specified face
|
|
1144
|
+
'''
|
|
1145
|
+
coords = self['pore.coords'][face]
|
|
1146
|
+
rads = self['pore.diameter'][face]/2.
|
|
1147
|
+
# calculate the area of the 3 principle faces of the bounding cuboid
|
|
1148
|
+
dx = max(coords[:,0]+rads) - min(coords[:,0]-rads)
|
|
1149
|
+
dy = max(coords[:,1]+rads) - min(coords[:,1]-rads)
|
|
1150
|
+
dz = max(coords[:,2]+rads) - min(coords[:,2]-rads)
|
|
1151
|
+
yz = dy*dz # x normal
|
|
1152
|
+
xz = dx*dz # y normal
|
|
1153
|
+
xy = dx*dy # z normal
|
|
1154
|
+
# find the directions parallel to the plane
|
|
1155
|
+
directions = sp.where([yz,xz,xy]!=max([yz,xz,xy]))[0]
|
|
1156
|
+
try:
|
|
1157
|
+
# now, use the whole network to do the area calculation
|
|
1158
|
+
coords = self['pore.coords']
|
|
1159
|
+
rads = self['pore.diameter']/2.
|
|
1160
|
+
d0 = (max(coords[:,directions[0]]+rads) - min(coords[:,directions[0]]-rads))
|
|
1161
|
+
d1 = (max(coords[:,directions[1]]+rads) - min(coords[:,directions[1]]-rads))
|
|
1162
|
+
A = d0*d1
|
|
1163
|
+
except:
|
|
1164
|
+
# if that fails, use the max face area of the bounding cuboid
|
|
1165
|
+
A = max([yz,xz,xy])
|
|
1166
|
+
if not misc.iscoplanar(self['pore.coords'][face]):
|
|
1167
|
+
logger.warning('The supplied pores are not coplanar. Area will be approximate')
|
|
1168
|
+
pass
|
|
1169
|
+
return A
|
|
1170
|
+
|
|
1171
|
+
if __name__ == '__main__':
|
|
1172
|
+
#Run doc tests
|
|
1173
|
+
import doctest
|
|
1174
|
+
doctest.testmod(verbose=True)
|
|
1175
|
+
|
|
1176
|
+
|
|
1177
|
+
|
|
1178
|
+
|
|
1179
|
+
|
|
1180
|
+
|
|
1181
|
+
|
|
1182
|
+
|
|
1183
|
+
|
|
1184
|
+
|