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,369 @@
|
|
|
1
|
+
import scipy as _sp
|
|
2
|
+
import matplotlib.pylab as _plt
|
|
3
|
+
|
|
4
|
+
def profiles(network,
|
|
5
|
+
fig=None,
|
|
6
|
+
values=None,
|
|
7
|
+
bins=[10,10,10]):
|
|
8
|
+
r'''
|
|
9
|
+
Compute the profiles for the property of interest and plots it in all
|
|
10
|
+
three dimensions
|
|
11
|
+
|
|
12
|
+
Parameters
|
|
13
|
+
----------
|
|
14
|
+
network : OpenPNM Network object
|
|
15
|
+
|
|
16
|
+
values : array_like, optional
|
|
17
|
+
The pore property values to be plotted as a profile
|
|
18
|
+
|
|
19
|
+
bins : int or list of ints, optional
|
|
20
|
+
The number of bins to divide the domain into for averaging.
|
|
21
|
+
|
|
22
|
+
Notes
|
|
23
|
+
-----
|
|
24
|
+
Either propname or values can be sent, but not both
|
|
25
|
+
|
|
26
|
+
'''
|
|
27
|
+
if fig is None:
|
|
28
|
+
fig = _plt.figure()
|
|
29
|
+
ax1 = fig.add_subplot(131)
|
|
30
|
+
ax2 = fig.add_subplot(132)
|
|
31
|
+
ax3 = fig.add_subplot(133)
|
|
32
|
+
ax = [ax1,ax2,ax3]
|
|
33
|
+
xlab = ['x coordinate','y_coordinate','z_coordinate']
|
|
34
|
+
for n in [0,1,2]:
|
|
35
|
+
n_min, n_max = [_sp.amin(network['pore.coords'][:,n]), _sp.amax(network['pore.coords'][:,n])]
|
|
36
|
+
steps = _sp.linspace(n_min,n_max,bins[n]+1,endpoint=True)
|
|
37
|
+
vals = _sp.zeros_like(steps)
|
|
38
|
+
for i in range(0,len(steps)-1):
|
|
39
|
+
temp = (network['pore.coords'][:,n] > steps[i])*(network['pore.coords'][:,n] <= steps[i+1])
|
|
40
|
+
vals[i] = _sp.mean(values[temp])
|
|
41
|
+
yaxis = vals[:-1]
|
|
42
|
+
xaxis = (steps[:-1] + (steps[1]-steps[0])/2)/n_max
|
|
43
|
+
ax[n].plot(xaxis,yaxis,'bo-')
|
|
44
|
+
ax[n].set_xlabel(xlab[n])
|
|
45
|
+
ax[n].set_ylabel('Slice Value')
|
|
46
|
+
fig.show()
|
|
47
|
+
|
|
48
|
+
def porosity_profile(network,
|
|
49
|
+
fig=None, axis=2):
|
|
50
|
+
|
|
51
|
+
r'''
|
|
52
|
+
Compute and plot the porosity profile in all three dimensions
|
|
53
|
+
|
|
54
|
+
Parameters
|
|
55
|
+
----------
|
|
56
|
+
network : OpenPNM Network object
|
|
57
|
+
axis : integer type 0 for x-axis, 1 for y-axis, 2 for z-axis
|
|
58
|
+
|
|
59
|
+
Notes
|
|
60
|
+
-----
|
|
61
|
+
the area of the porous medium at any position is calculated from the
|
|
62
|
+
maximum pore coordinates in each direction
|
|
63
|
+
|
|
64
|
+
'''
|
|
65
|
+
if fig is None:
|
|
66
|
+
fig = _plt.figure()
|
|
67
|
+
L_x = _sp.amax(network['pore.coords'][:,0]) + _sp.mean(((21/88.0)*network['pore.volume'])**(1/3.0))
|
|
68
|
+
L_y = _sp.amax(network['pore.coords'][:,1]) + _sp.mean(((21/88.0)*network['pore.volume'])**(1/3.0))
|
|
69
|
+
L_z = _sp.amax(network['pore.coords'][:,2]) + _sp.mean(((21/88.0)*network['pore.volume'])**(1/3.0))
|
|
70
|
+
if axis is 0:
|
|
71
|
+
xlab = 'x-direction'
|
|
72
|
+
area = L_y*L_z
|
|
73
|
+
elif axis is 1:
|
|
74
|
+
xlab = 'y-direction'
|
|
75
|
+
area = L_x*L_z
|
|
76
|
+
else:
|
|
77
|
+
axis = 2
|
|
78
|
+
xlab = 'z-direction'
|
|
79
|
+
area = L_x*L_y
|
|
80
|
+
n_max = _sp.amax(network['pore.coords'][:,axis]) + _sp.mean(((21/88.0)*network['pore.volume'])**(1/3.0))
|
|
81
|
+
steps = _sp.linspace(0,n_max,100,endpoint=True)
|
|
82
|
+
vals = _sp.zeros_like(steps)
|
|
83
|
+
p_area = _sp.zeros_like(steps)
|
|
84
|
+
t_area = _sp.zeros_like(steps)
|
|
85
|
+
|
|
86
|
+
rp = ((21/88.0)*network['pore.volume'])**(1/3.0)
|
|
87
|
+
p_upper = network['pore.coords'][:,axis] + rp
|
|
88
|
+
p_lower = network['pore.coords'][:,axis] - rp
|
|
89
|
+
TC1 = network['throat.conns'][:,0]
|
|
90
|
+
TC2 = network['throat.conns'][:,1]
|
|
91
|
+
t_upper = network['pore.coords'][:,axis][TC1]
|
|
92
|
+
t_lower = network['pore.coords'][:,axis][TC2]
|
|
93
|
+
|
|
94
|
+
for i in range(0,len(steps)):
|
|
95
|
+
p_temp = (p_upper > steps[i])*(p_lower < steps[i])
|
|
96
|
+
t_temp = (t_upper > steps[i])*(t_lower < steps[i])
|
|
97
|
+
p_area[i] = sum((22/7.0)*(rp[p_temp]**2 - (network['pore.coords'][:,axis][p_temp]-steps[i])**2))
|
|
98
|
+
t_area[i] = sum(network['throat.area'][t_temp])
|
|
99
|
+
vals[i] = (p_area[i]+t_area[i])/area
|
|
100
|
+
yaxis = vals
|
|
101
|
+
xaxis = steps/n_max
|
|
102
|
+
_plt.plot(xaxis,yaxis,'bo-')
|
|
103
|
+
_plt.xlabel(xlab)
|
|
104
|
+
_plt.ylabel('Porosity')
|
|
105
|
+
fig.show()
|
|
106
|
+
|
|
107
|
+
def saturation_profile(network, phase, fig=None, axis=2):
|
|
108
|
+
|
|
109
|
+
r'''
|
|
110
|
+
Compute and plot the saturation profile in all three dimensions
|
|
111
|
+
|
|
112
|
+
Parameters
|
|
113
|
+
----------
|
|
114
|
+
network : OpenPNM Network object
|
|
115
|
+
phase : the invading or defending phase to plot its saturation distribution
|
|
116
|
+
axis : integer type 0 for x-axis, 1 for y-axis, 2 for z-axis
|
|
117
|
+
|
|
118
|
+
'''
|
|
119
|
+
if fig is None:
|
|
120
|
+
fig = _plt.figure()
|
|
121
|
+
if phase is None:
|
|
122
|
+
raise Exception('The phase for saturation profile plot is not given' )
|
|
123
|
+
if axis is 0:
|
|
124
|
+
xlab = 'x-direction'
|
|
125
|
+
elif axis is 1:
|
|
126
|
+
xlab = 'y-direction'
|
|
127
|
+
else:
|
|
128
|
+
axis = 2
|
|
129
|
+
xlab = 'z-direction'
|
|
130
|
+
n_max = _sp.amax(network['pore.coords'][:,axis]) + _sp.mean(((21/88.0)*network['pore.volume'])**(1/3.0))
|
|
131
|
+
steps = _sp.linspace(0,n_max,100,endpoint=True)
|
|
132
|
+
p_area = _sp.zeros_like(steps)
|
|
133
|
+
op_area = _sp.zeros_like(steps)
|
|
134
|
+
t_area = _sp.zeros_like(steps)
|
|
135
|
+
ot_area = _sp.zeros_like(steps)
|
|
136
|
+
vals = _sp.zeros_like(steps)
|
|
137
|
+
PO = phase['pore.occupancy']
|
|
138
|
+
TO = phase['throat.occupancy']
|
|
139
|
+
rp = ((21/88.0)*network['pore.volume'])**(1/3.0)
|
|
140
|
+
p_upper = network['pore.coords'][:,axis] + rp
|
|
141
|
+
p_lower = network['pore.coords'][:,axis] - rp
|
|
142
|
+
|
|
143
|
+
TC1 = network['throat.conns'][:,0]
|
|
144
|
+
TC2 = network['throat.conns'][:,1]
|
|
145
|
+
t_upper = network['pore.coords'][:,axis][TC1]
|
|
146
|
+
t_lower = network['pore.coords'][:,axis][TC2]
|
|
147
|
+
|
|
148
|
+
for i in range(0,len(steps)):
|
|
149
|
+
op_temp = (p_upper > steps[i])*(p_lower < steps[i])*PO
|
|
150
|
+
ot_temp = (t_upper > steps[i])*(t_lower < steps[i])*TO
|
|
151
|
+
op_temp = _sp.array(op_temp, dtype='bool')
|
|
152
|
+
ot_temp = _sp.array(op_temp, dtype='bool')
|
|
153
|
+
p_temp = (p_upper > steps[i])*(p_lower < steps[i])
|
|
154
|
+
t_temp = (t_upper > steps[i])*(t_lower < steps[i])
|
|
155
|
+
op_area[i] = sum((22/7.0)*(rp[op_temp]**2 - (network['pore.coords'][:,axis][op_temp]-steps[i])**2))
|
|
156
|
+
ot_area[i] = sum(network['throat.area'][ot_temp])
|
|
157
|
+
p_area[i] = sum((22/7.0)*(rp[p_temp]**2 - (network['pore.coords'][:,axis][p_temp]-steps[i])**2))
|
|
158
|
+
t_area[i] = sum(network['throat.area'][t_temp])
|
|
159
|
+
vals[i] = (op_area[i]+ot_area[i])/(p_area[i]+t_area[i])
|
|
160
|
+
if vals[i]>1:
|
|
161
|
+
vals[i]=1.
|
|
162
|
+
if _sp.isnan(vals[i]):
|
|
163
|
+
vals[i]=1.
|
|
164
|
+
|
|
165
|
+
if vals[-1]==1.:
|
|
166
|
+
vals = vals[::-1]
|
|
167
|
+
|
|
168
|
+
yaxis = vals
|
|
169
|
+
xaxis = steps/n_max
|
|
170
|
+
_plt.plot(xaxis,yaxis,'bo-')
|
|
171
|
+
_plt.xlabel(xlab)
|
|
172
|
+
_plt.ylabel('Saturation')
|
|
173
|
+
fig.show()
|
|
174
|
+
|
|
175
|
+
def distributions(net,
|
|
176
|
+
fig = None,
|
|
177
|
+
throat_diameter='throat.diameter',
|
|
178
|
+
pore_diameter='pore.diameter',
|
|
179
|
+
throat_length='throat.length',
|
|
180
|
+
exclude_boundaries=True,
|
|
181
|
+
geom_list=None):
|
|
182
|
+
r"""
|
|
183
|
+
Plot a montage of key network size distribution histograms
|
|
184
|
+
|
|
185
|
+
Parameters
|
|
186
|
+
----------
|
|
187
|
+
net : OpenPNM Network Object
|
|
188
|
+
The network for which the graphs are desired
|
|
189
|
+
|
|
190
|
+
"""
|
|
191
|
+
if fig is None:
|
|
192
|
+
fig = _plt.figure()
|
|
193
|
+
|
|
194
|
+
fig.subplots_adjust(hspace = 0.4)
|
|
195
|
+
fig.subplots_adjust(wspace = 0.4)
|
|
196
|
+
|
|
197
|
+
if geom_list is not None:
|
|
198
|
+
include_pores = [False]*net.num_pores()
|
|
199
|
+
include_throats = [False]*net.num_throats()
|
|
200
|
+
for geom in geom_list:
|
|
201
|
+
include_pores = include_pores | net["pore."+geom]
|
|
202
|
+
include_throats = include_throats | net["throat."+geom]
|
|
203
|
+
else:
|
|
204
|
+
include_pores = net["pore.all"]
|
|
205
|
+
include_throats = net["throat.all"]
|
|
206
|
+
pores = net.pores()[include_pores]
|
|
207
|
+
throats = net.throats()[include_throats]
|
|
208
|
+
|
|
209
|
+
|
|
210
|
+
ax1 = fig.add_subplot(221)
|
|
211
|
+
ax1.hist(net[pore_diameter][pores],25,facecolor='green')
|
|
212
|
+
ax1.set_xlabel('Pore Diameter')
|
|
213
|
+
ax1.set_ylabel('Frequency')
|
|
214
|
+
ax1.ticklabel_format(style='sci', axis='x', scilimits=(0,0))
|
|
215
|
+
|
|
216
|
+
ax2 = fig.add_subplot(222)
|
|
217
|
+
x = net.num_neighbors(pores,flatten=False)
|
|
218
|
+
ax2.hist(x,25,facecolor='yellow')
|
|
219
|
+
ax2.set_xlabel('Coordination Number')
|
|
220
|
+
ax2.set_ylabel('Frequency')
|
|
221
|
+
|
|
222
|
+
ax3 = fig.add_subplot(223)
|
|
223
|
+
ax3.hist(net[throat_diameter][throats],25,facecolor='blue')
|
|
224
|
+
ax3.set_xlabel('Throat Diameter')
|
|
225
|
+
ax3.set_ylabel('Frequency')
|
|
226
|
+
ax3.ticklabel_format(style='sci', axis='x', scilimits=(0,0))
|
|
227
|
+
|
|
228
|
+
ax4 = fig.add_subplot(224)
|
|
229
|
+
ax4.hist(net[throat_length][throats],25,facecolor='red')
|
|
230
|
+
ax4.set_xlabel('Throat Length')
|
|
231
|
+
ax4.set_ylabel('Frequency')
|
|
232
|
+
ax4.ticklabel_format(style='sci', axis='x', scilimits=(0,0))
|
|
233
|
+
fig.show()
|
|
234
|
+
|
|
235
|
+
def pore_size_distribution(network, fig=None,):
|
|
236
|
+
|
|
237
|
+
r'''
|
|
238
|
+
Plot the pore and throat size distribution which is the accumulated
|
|
239
|
+
volume vs. the diameter in a semilog plot
|
|
240
|
+
|
|
241
|
+
Parameters
|
|
242
|
+
----------
|
|
243
|
+
network : OpenPNM Network object
|
|
244
|
+
|
|
245
|
+
'''
|
|
246
|
+
if fig is None:
|
|
247
|
+
fig = _plt.figure()
|
|
248
|
+
dp = network['pore.diameter']
|
|
249
|
+
Vp = network['pore.volume']
|
|
250
|
+
dt = network['throat.diameter']
|
|
251
|
+
Vt = network['throat.volume']
|
|
252
|
+
dmax = max(max(dp),max(dt))
|
|
253
|
+
steps = _sp.linspace(0,dmax,100,endpoint=True)
|
|
254
|
+
vals = _sp.zeros_like(steps)
|
|
255
|
+
for i in range(0,len(steps)-1):
|
|
256
|
+
temp1 = dp > steps[i]
|
|
257
|
+
temp2 = dt > steps[i]
|
|
258
|
+
vals[i] = sum(Vp[temp1]) + sum(Vt[temp2])
|
|
259
|
+
yaxis = vals
|
|
260
|
+
xaxis = steps
|
|
261
|
+
_plt.semilogx(xaxis,yaxis,'b.-')
|
|
262
|
+
_plt.xlabel('Pore & Throat Diameter (m)')
|
|
263
|
+
_plt.ylabel('Cumulative Volume (m^3)')
|
|
264
|
+
fig.show()
|
|
265
|
+
|
|
266
|
+
def drainage_curves(inv_alg,
|
|
267
|
+
fig=None,
|
|
268
|
+
Pc='inv_Pc',
|
|
269
|
+
sat='inv_sat',
|
|
270
|
+
seq='inv_seq',
|
|
271
|
+
timing=None):
|
|
272
|
+
r"""
|
|
273
|
+
Plot a montage of key saturation plots
|
|
274
|
+
|
|
275
|
+
Parameters
|
|
276
|
+
----------
|
|
277
|
+
inv_alg : OpenPNM Algorithm Object
|
|
278
|
+
The invasion algorithm for which the graphs are desired
|
|
279
|
+
|
|
280
|
+
timing : string
|
|
281
|
+
if algorithm keeps track of simulated time, insert string here.
|
|
282
|
+
|
|
283
|
+
Examples
|
|
284
|
+
--------
|
|
285
|
+
>>> import OpenPNM
|
|
286
|
+
>>> pn = OpenPNM.Network.TestNet()
|
|
287
|
+
>>> geo = OpenPNM.Geometry.TestGeometry(network=pn,pores=pn.pores(),throats=pn.throats())
|
|
288
|
+
>>> phase1 = OpenPNM.Phases.TestPhase(network=pn)
|
|
289
|
+
>>> phase2 = OpenPNM.Phases.TestPhase(network=pn)
|
|
290
|
+
>>> phys1 = OpenPNM.Physics.TestPhysics(network=pn, phase=phase1,pores=pn.pores(),throats=pn.throats())
|
|
291
|
+
>>> phys2 = OpenPNM.Physics.TestPhysics(network=pn, phase=phase2,pores=pn.pores(),throats=pn.throats())
|
|
292
|
+
>>> IP = OpenPNM.Algorithms.InvasionPercolationTimed(network=pn)
|
|
293
|
+
>>> IP.run(invading_phase=phase1, defending_phase=phase2, inlets=pn.pores('top'), outlets=pn.pores('bottom'))
|
|
294
|
+
IP algorithm at 0 % completion at 0.0 seconds
|
|
295
|
+
IP algorithm at 20 % completion at 0.0 seconds
|
|
296
|
+
IP algorithm at 40 % completion at 0.0 seconds
|
|
297
|
+
IP algorithm at 60 % completion at 0.0 seconds
|
|
298
|
+
IP algorithm at 100% completion at 0.0 seconds
|
|
299
|
+
>>> OpenPNM.Postprocessing.Plots.drainage_curves(IP,timing='inv_time')
|
|
300
|
+
"""
|
|
301
|
+
inv_throats = inv_alg.toindices(inv_alg['throat.'+seq]>0)
|
|
302
|
+
sort_seq = _sp.argsort(inv_alg['throat.'+seq][inv_throats])
|
|
303
|
+
inv_throats = inv_throats[sort_seq]
|
|
304
|
+
|
|
305
|
+
if fig is None:
|
|
306
|
+
fig = _plt.figure(num=1, figsize=(13, 10), dpi=80, facecolor='w', edgecolor='k')
|
|
307
|
+
ax1 = fig.add_subplot(231) #left
|
|
308
|
+
ax2 = fig.add_subplot(232) #middle
|
|
309
|
+
ax3 = fig.add_subplot(233) #right
|
|
310
|
+
ax4 = fig.add_subplot(234) #left
|
|
311
|
+
ax5 = fig.add_subplot(235) #middle
|
|
312
|
+
ax6 = fig.add_subplot(236) #right
|
|
313
|
+
|
|
314
|
+
ax1.plot(inv_alg['throat.'+Pc][inv_throats],inv_alg['throat.'+sat][inv_throats])
|
|
315
|
+
ax1.set_xlabel('Capillary Pressure (Pa)')
|
|
316
|
+
ax1.set_ylabel('Saturation')
|
|
317
|
+
ax1.set_ylim([0,1])
|
|
318
|
+
ax1.set_xlim([0.99*min(inv_alg['throat.'+Pc][inv_throats]),1.01*max(inv_alg['throat.'+Pc][inv_throats])])
|
|
319
|
+
|
|
320
|
+
ax2.plot(inv_alg['throat.'+seq][inv_throats],inv_alg['throat.'+sat][inv_throats])
|
|
321
|
+
ax2.set_xlabel('Simulation Step')
|
|
322
|
+
ax2.set_ylabel('Saturation')
|
|
323
|
+
ax2.set_ylim([0,1])
|
|
324
|
+
ax2.set_xlim([0,1.01*max(inv_alg['throat.'+seq][inv_throats])])
|
|
325
|
+
|
|
326
|
+
if timing==None:
|
|
327
|
+
ax3.plot(0,0)
|
|
328
|
+
ax3.set_xlabel('No Time Data Available')
|
|
329
|
+
else:
|
|
330
|
+
ax3.plot(inv_alg['throat.'+timing][inv_throats],inv_alg['throat.'+sat][inv_throats])
|
|
331
|
+
ax3.set_xlabel('Time (s)')
|
|
332
|
+
ax3.set_ylabel('Saturation')
|
|
333
|
+
ax3.set_ylim([0,1])
|
|
334
|
+
ax3.set_xlim([0,1.01*max(inv_alg['throat.'+timing][inv_throats])])
|
|
335
|
+
|
|
336
|
+
ax4.plot(inv_alg['throat.'+sat][inv_throats],inv_alg['throat.'+Pc][inv_throats])
|
|
337
|
+
ax4.set_ylabel('Capillary Pressure (Pa)')
|
|
338
|
+
ax4.set_xlabel('Saturation')
|
|
339
|
+
ax4.set_xlim([0,1])
|
|
340
|
+
ax4.set_ylim([0.99*min(inv_alg['throat.'+Pc][inv_throats]),1.01*max(inv_alg['throat.'+Pc][inv_throats])])
|
|
341
|
+
|
|
342
|
+
ax5.plot(inv_alg['throat.'+seq][inv_throats],inv_alg['throat.'+Pc][inv_throats])
|
|
343
|
+
ax5.set_xlabel('Simulation Step')
|
|
344
|
+
ax5.set_ylabel('Capillary Pressure (Pa)')
|
|
345
|
+
ax5.set_ylim([0.99*min(inv_alg['throat.'+Pc][inv_throats]),1.01*max(inv_alg['throat.'+Pc][inv_throats])])
|
|
346
|
+
ax5.set_xlim([0,1.01*max(inv_alg['throat.'+seq][inv_throats])])
|
|
347
|
+
|
|
348
|
+
if timing==None:
|
|
349
|
+
ax6.plot(0,0)
|
|
350
|
+
ax6.set_xlabel('No Time Data Available')
|
|
351
|
+
else:
|
|
352
|
+
ax6.plot(inv_alg['throat.'+timing][inv_throats],inv_alg['throat.'+Pc][inv_throats])
|
|
353
|
+
ax6.set_xlabel('Time (s)')
|
|
354
|
+
ax6.set_ylabel('Capillary Pressure (Pa)')
|
|
355
|
+
ax6.set_ylim([0.99*min(inv_alg['throat.'+Pc][inv_throats]),1.01*max(inv_alg['throat.'+Pc][inv_throats])])
|
|
356
|
+
ax6.set_xlim([0,1.01*max(inv_alg['throat.'+timing][inv_throats])])
|
|
357
|
+
|
|
358
|
+
fig.subplots_adjust(left=0.08, right=0.99, top=0.95, bottom=0.1)
|
|
359
|
+
ax1.grid(True)
|
|
360
|
+
ax2.grid(True)
|
|
361
|
+
ax3.grid(True)
|
|
362
|
+
ax4.grid(True)
|
|
363
|
+
ax5.grid(True)
|
|
364
|
+
ax6.grid(True)
|
|
365
|
+
fig.show()
|
|
366
|
+
|
|
367
|
+
if __name__ == '__main__':
|
|
368
|
+
import doctest
|
|
369
|
+
doctest.testmod(verbose=True)
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
r"""
|
|
2
|
+
###############################################################################
|
|
3
|
+
:mod:`OpenPNM.Postprocessing` -- Analysis, Plotting and some Visualization
|
|
4
|
+
###############################################################################
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from . import Plots
|
|
10
|
+
from . import Graphics
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
from OpenPNM.Utilities import misc
|
|
2
|
+
import scipy as _sp
|
|
3
|
+
import numpy as _np
|
|
4
|
+
import os as _os
|
|
5
|
+
import pickle as _pickle
|
|
6
|
+
from xml.etree import ElementTree as _ET
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class VTK():
|
|
11
|
+
r"""
|
|
12
|
+
Class for writing a Vtp file to be read by ParaView
|
|
13
|
+
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
_TEMPLATE = '''
|
|
17
|
+
<?xml version="1.0" ?>
|
|
18
|
+
<VTKFile byte_order="LittleEndian" type="PolyData" version="0.1">
|
|
19
|
+
<PolyData>
|
|
20
|
+
<Piece NumberOfLines="0" NumberOfPoints="0">
|
|
21
|
+
<Points>
|
|
22
|
+
</Points>
|
|
23
|
+
<Lines>
|
|
24
|
+
</Lines>
|
|
25
|
+
<PointData>
|
|
26
|
+
</PointData>
|
|
27
|
+
<CellData>
|
|
28
|
+
</CellData>
|
|
29
|
+
</Piece>
|
|
30
|
+
</PolyData>
|
|
31
|
+
</VTKFile>
|
|
32
|
+
'''.strip()
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def __init__(self,**kwargs):
|
|
36
|
+
r"""
|
|
37
|
+
Initialize
|
|
38
|
+
"""
|
|
39
|
+
super().__init__(**kwargs)
|
|
40
|
+
|
|
41
|
+
@staticmethod
|
|
42
|
+
def save(network,filename='',phases=[]):
|
|
43
|
+
r'''
|
|
44
|
+
Save network and phase data to a single vtp file for visualizing in
|
|
45
|
+
Paraview
|
|
46
|
+
|
|
47
|
+
Parameters
|
|
48
|
+
----------
|
|
49
|
+
network : OpenPNM Network Object
|
|
50
|
+
The Network containing the data to be written
|
|
51
|
+
|
|
52
|
+
filename : string, optional
|
|
53
|
+
Filename to write data. If no name is given the file is named after
|
|
54
|
+
ther network
|
|
55
|
+
|
|
56
|
+
phases : list, optional
|
|
57
|
+
A list contain OpenPNM Phase object(s) containing data to be written
|
|
58
|
+
|
|
59
|
+
Examples
|
|
60
|
+
--------
|
|
61
|
+
>>> import OpenPNM
|
|
62
|
+
>>> pn = OpenPNM.Network.Cubic(shape=[3,3,3])
|
|
63
|
+
>>> geo = OpenPNM.Geometry.Stick_and_Ball(network=pn,pores=pn.pores(),throats=pn.throats())
|
|
64
|
+
>>> air = OpenPNM.Phases.Air(network=pn)
|
|
65
|
+
>>> phys = OpenPNM.Physics.Standard(network=pn,phase=air,pores=pn.pores(),throats=pn.throats())
|
|
66
|
+
|
|
67
|
+
>>> import OpenPNM.Utilities.IO as io
|
|
68
|
+
>>> io.VTK.save(pn,'test_pn.vtp',[air])
|
|
69
|
+
|
|
70
|
+
>>> # Delete the new file
|
|
71
|
+
>>> import os
|
|
72
|
+
>>> os.remove('test_pn.vtp')
|
|
73
|
+
'''
|
|
74
|
+
|
|
75
|
+
if filename == '':
|
|
76
|
+
filename = network.name
|
|
77
|
+
filename = filename.split('.')[0]+'.vtp'
|
|
78
|
+
|
|
79
|
+
root = _ET.fromstring(VTK._TEMPLATE)
|
|
80
|
+
objs = []
|
|
81
|
+
if type(phases) != list:
|
|
82
|
+
phases = [phases]
|
|
83
|
+
for phase in phases:
|
|
84
|
+
objs.append(phase)
|
|
85
|
+
objs.append(network)
|
|
86
|
+
am = misc.amalgamate_data(objs=objs)
|
|
87
|
+
key_list = list(sorted(am.keys()))
|
|
88
|
+
points = network['pore.coords']
|
|
89
|
+
pairs = network['throat.conns']
|
|
90
|
+
|
|
91
|
+
num_points = len(points)
|
|
92
|
+
num_throats = len(pairs)
|
|
93
|
+
|
|
94
|
+
piece_node = root.find('PolyData').find('Piece')
|
|
95
|
+
piece_node.set("NumberOfPoints", str(num_points))
|
|
96
|
+
piece_node.set("NumberOfLines", str(num_throats))
|
|
97
|
+
|
|
98
|
+
points_node = piece_node.find('Points')
|
|
99
|
+
coords = VTK._array_to_element("coords", points.T.ravel('F'), n=3)
|
|
100
|
+
points_node.append(coords)
|
|
101
|
+
|
|
102
|
+
lines_node = piece_node.find('Lines')
|
|
103
|
+
connectivity = VTK._array_to_element("connectivity", pairs)
|
|
104
|
+
lines_node.append(connectivity)
|
|
105
|
+
offsets = VTK._array_to_element("offsets", 2*_np.arange(len(pairs))+2)
|
|
106
|
+
lines_node.append(offsets)
|
|
107
|
+
|
|
108
|
+
point_data_node = piece_node.find('PointData')
|
|
109
|
+
for key in key_list:
|
|
110
|
+
array = am[key]
|
|
111
|
+
if array.dtype == _np.bool: array = array.astype(int)
|
|
112
|
+
if array.size != num_points: continue
|
|
113
|
+
element = VTK._array_to_element(key, array)
|
|
114
|
+
point_data_node.append(element)
|
|
115
|
+
|
|
116
|
+
cell_data_node = piece_node.find('CellData')
|
|
117
|
+
for key in key_list:
|
|
118
|
+
array = am[key]
|
|
119
|
+
if array.dtype == _np.bool: array = array.astype(int)
|
|
120
|
+
if array.size != num_throats: continue
|
|
121
|
+
element = VTK._array_to_element(key, array)
|
|
122
|
+
cell_data_node.append(element)
|
|
123
|
+
|
|
124
|
+
tree = _ET.ElementTree(root)
|
|
125
|
+
tree.write(filename)
|
|
126
|
+
|
|
127
|
+
#Make pretty
|
|
128
|
+
with open(filename, "r+") as f:
|
|
129
|
+
string = f.read()
|
|
130
|
+
string = string.replace("</DataArray>", "</DataArray>\n\t\t\t")
|
|
131
|
+
f.seek(0)
|
|
132
|
+
# consider adding header: '<?xml version="1.0"?>\n'+
|
|
133
|
+
f.write(string)
|
|
134
|
+
|
|
135
|
+
@staticmethod
|
|
136
|
+
def load(filename):
|
|
137
|
+
r'''
|
|
138
|
+
Read in pore and throat data from a saved VTK file.
|
|
139
|
+
|
|
140
|
+
Notes
|
|
141
|
+
-----
|
|
142
|
+
This will NOT reproduce original simulation, since all models and object
|
|
143
|
+
relationships are lost. Use IO.Save and IO.Load for that.'''
|
|
144
|
+
network = OpenPNM.Network.GenericNetwork()
|
|
145
|
+
tree = _ET.parse(filename)
|
|
146
|
+
piece_node = tree.find('PolyData').find('Piece')
|
|
147
|
+
|
|
148
|
+
# extract connectivity
|
|
149
|
+
conn_element = piece_node.find('Lines').find('DataArray')
|
|
150
|
+
array = VTK._element_to_array(conn_element, 2)
|
|
151
|
+
network['throat.conns'] = array.T
|
|
152
|
+
|
|
153
|
+
for element in piece_node.find('PointData').iter('DataArray'):
|
|
154
|
+
key = element.get('Name')
|
|
155
|
+
array = VTK._element_to_array(element)
|
|
156
|
+
netname = key.split('.')[0]
|
|
157
|
+
propname = key.strip(netname+'.')
|
|
158
|
+
network[propname] = array
|
|
159
|
+
|
|
160
|
+
return network
|
|
161
|
+
|
|
162
|
+
@staticmethod
|
|
163
|
+
def _array_to_element(name, array, n=1):
|
|
164
|
+
dtype_map = {
|
|
165
|
+
'int8' : 'Int8',
|
|
166
|
+
'int16' : 'Int16',
|
|
167
|
+
'int32' : 'Int32',
|
|
168
|
+
'int64' : 'Int64',
|
|
169
|
+
'uint8' : 'UInt8',
|
|
170
|
+
'uint16' : 'UInt16',
|
|
171
|
+
'uint32' : 'UInt32',
|
|
172
|
+
'uint64' : 'UInt64',
|
|
173
|
+
'float32': 'Float32',
|
|
174
|
+
'float64': 'Float64',
|
|
175
|
+
'str' : 'String',
|
|
176
|
+
}
|
|
177
|
+
element = _ET.Element('DataArray')
|
|
178
|
+
element.set("Name", name)
|
|
179
|
+
element.set("NumberOfComponents", str(n))
|
|
180
|
+
element.set("type", dtype_map[str(array.dtype)])
|
|
181
|
+
element.text = '\t'.join(map(str,array.ravel()))
|
|
182
|
+
return element
|
|
183
|
+
|
|
184
|
+
@staticmethod
|
|
185
|
+
def _element_to_array(element, n=1):
|
|
186
|
+
string = element.text
|
|
187
|
+
dtype = element.get("type")
|
|
188
|
+
array = _np.fromstring(string, sep='\t')
|
|
189
|
+
array = array.astype(dtype)
|
|
190
|
+
if n is not 1:
|
|
191
|
+
array = array.reshape(array.size//n, n)
|
|
192
|
+
return array
|
|
193
|
+
|
|
194
|
+
class MAT():
|
|
195
|
+
r'''
|
|
196
|
+
Class for reading and writing OpenPNM data to a Matlab 'mat' file
|
|
197
|
+
'''
|
|
198
|
+
|
|
199
|
+
def __init__(self,**kwargs):
|
|
200
|
+
r"""
|
|
201
|
+
Initialize
|
|
202
|
+
"""
|
|
203
|
+
super().__init__(**kwargs)
|
|
204
|
+
|
|
205
|
+
@staticmethod
|
|
206
|
+
def save(network, filename='', phases=[]):
|
|
207
|
+
r"""
|
|
208
|
+
Write Network to a Mat file for exporting to Matlab. This method will be
|
|
209
|
+
enhanced in a future update, and it's functionality may change!
|
|
210
|
+
|
|
211
|
+
Parameters
|
|
212
|
+
----------
|
|
213
|
+
|
|
214
|
+
network : OpenPNM Network Object
|
|
215
|
+
|
|
216
|
+
filename : string
|
|
217
|
+
Desired file name, defaults to network name if not given
|
|
218
|
+
|
|
219
|
+
phases : list of phase objects ([])
|
|
220
|
+
Phases that have properties we want to write to file
|
|
221
|
+
|
|
222
|
+
Examples
|
|
223
|
+
--------
|
|
224
|
+
>>> import OpenPNM
|
|
225
|
+
>>> pn = OpenPNM.Network.TestNet()
|
|
226
|
+
>>> geo = OpenPNM.Geometry.TestGeometry(network=pn,pores=pn.pores(),throats=pn.throats())
|
|
227
|
+
>>> air = OpenPNM.Phases.TestPhase()
|
|
228
|
+
>>> import OpenPNM.Utilities.IO as io
|
|
229
|
+
>>> io.MAT.save(network=pn,filename='test_pn.mat',phases=air)
|
|
230
|
+
|
|
231
|
+
>>> #Remove newly created file
|
|
232
|
+
>>> import os
|
|
233
|
+
>>> os.remove('test_pn.mat')
|
|
234
|
+
|
|
235
|
+
"""
|
|
236
|
+
if filename == '':
|
|
237
|
+
filename = network.name
|
|
238
|
+
filename = filename.split('.')[0]+'.mat'
|
|
239
|
+
|
|
240
|
+
pnMatlab = {}
|
|
241
|
+
new = []
|
|
242
|
+
old = []
|
|
243
|
+
for keys in network.keys():
|
|
244
|
+
old.append(keys)
|
|
245
|
+
new.append(keys.replace('.','_'))
|
|
246
|
+
|
|
247
|
+
for i in range(len(network)):
|
|
248
|
+
pnMatlab[new[i]] = network[old[i]]
|
|
249
|
+
|
|
250
|
+
if type(phases) != list:
|
|
251
|
+
phases = [phases]
|
|
252
|
+
if len(phases) != 0:
|
|
253
|
+
for j in range(len(phases)):
|
|
254
|
+
new = []
|
|
255
|
+
old = []
|
|
256
|
+
|
|
257
|
+
for keys in phases[j].keys():
|
|
258
|
+
old.append(keys)
|
|
259
|
+
new.append(phases[j].name+'_'+keys.replace('.','_'))
|
|
260
|
+
|
|
261
|
+
for i in range(len(phases[j])):
|
|
262
|
+
pnMatlab[new[i]] = phases[j][old[i]]
|
|
263
|
+
|
|
264
|
+
_sp.io.savemat(file_name=filename,mdict=pnMatlab)
|
|
265
|
+
|
|
266
|
+
@staticmethod
|
|
267
|
+
def load():
|
|
268
|
+
r'''
|
|
269
|
+
This method is not implemented yet.
|
|
270
|
+
'''
|
|
271
|
+
raise NotImplemented()
|
|
272
|
+
|
|
273
|
+
|
|
274
|
+
if __name__ == '__main__':
|
|
275
|
+
import doctest
|
|
276
|
+
doctest.testmod(verbose=True)
|
|
277
|
+
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import string, random
|
|
2
|
+
import OpenPNM.Phases
|
|
3
|
+
|
|
4
|
+
def solve_linear(pn, ics):
|
|
5
|
+
# circumvent bug with naming by creating random names
|
|
6
|
+
name = ''.join(random.choice(string.ascii_uppercase + string.digits) for _ in range(6))
|
|
7
|
+
pseudo = OpenPNM.Phases.GenericPhase(network=pn, name=name)
|
|
8
|
+
pseudo['throat.electrical_conductance']=1
|
|
9
|
+
|
|
10
|
+
alg = OpenPNM.Algorithms.OhmicConduction(network=pn)
|
|
11
|
+
alg['pore.BCval']=ics
|
|
12
|
+
alg['pore.Dirichlet']=ics!=0
|
|
13
|
+
alg.run(active_phase=pseudo)
|
|
14
|
+
alg.return_results()
|
|
15
|
+
|
|
16
|
+
out = pseudo['pore.voltage']
|
|
17
|
+
return out
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
r"""
|
|
2
|
+
###############################################################################
|
|
3
|
+
:mod:`OpenPNM.Utilities` -- IO, geometry tools and other functions
|
|
4
|
+
###############################################################################
|
|
5
|
+
|
|
6
|
+
.. automodule:: OpenPNM.Utilities.IO
|
|
7
|
+
:members:
|
|
8
|
+
:undoc-members:
|
|
9
|
+
:show-inheritance:
|
|
10
|
+
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from . import IO
|
|
14
|
+
from . import transformations
|
|
15
|
+
from . import misc
|
|
16
|
+
from . import vertexops
|