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