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,1522 @@
|
|
|
1
|
+
'''
|
|
2
|
+
###############################################################################
|
|
3
|
+
Core: Core Data Class
|
|
4
|
+
###############################################################################
|
|
5
|
+
'''
|
|
6
|
+
import pprint, string, random
|
|
7
|
+
import scipy as sp
|
|
8
|
+
import scipy.constants
|
|
9
|
+
from OpenPNM.Base import logging, Tools
|
|
10
|
+
from OpenPNM.Base import ModelsDict
|
|
11
|
+
logger = logging.getLogger()
|
|
12
|
+
from OpenPNM.Base import Controller
|
|
13
|
+
ctrl = Controller()
|
|
14
|
+
|
|
15
|
+
class Core(dict):
|
|
16
|
+
r'''
|
|
17
|
+
Contains OpenPNM specificmethods for working with the data in the dictionaries
|
|
18
|
+
'''
|
|
19
|
+
|
|
20
|
+
def __new__(typ, *args, **kwargs):
|
|
21
|
+
obj = dict.__new__(typ, *args, **kwargs)
|
|
22
|
+
obj.update({'pore.all': sp.array([],ndmin=1,dtype=bool)})
|
|
23
|
+
obj.update({'throat.all': sp.array([],ndmin=1,dtype=bool)})
|
|
24
|
+
#Initialize phase, physics, and geometry tracking lists
|
|
25
|
+
obj._name = None
|
|
26
|
+
obj._ctrl = {}
|
|
27
|
+
obj._phases = []
|
|
28
|
+
obj._geometries = []
|
|
29
|
+
obj._physics = []
|
|
30
|
+
obj._net = None
|
|
31
|
+
obj._parent = None
|
|
32
|
+
#Initialize ordered dict for storing property models
|
|
33
|
+
obj.models = ModelsDict()
|
|
34
|
+
return obj
|
|
35
|
+
|
|
36
|
+
def __init__(self, name=None, **kwargs):
|
|
37
|
+
r'''
|
|
38
|
+
Initialize
|
|
39
|
+
'''
|
|
40
|
+
super(Core,self).__init__()
|
|
41
|
+
logger.debug('Initializing Core class')
|
|
42
|
+
self.name = name
|
|
43
|
+
self.controller = ctrl
|
|
44
|
+
|
|
45
|
+
def __repr__(self):
|
|
46
|
+
return '<%s.%s object at %s>' % (
|
|
47
|
+
self.__class__.__module__,
|
|
48
|
+
self.__class__.__name__,
|
|
49
|
+
hex(id(self)))
|
|
50
|
+
|
|
51
|
+
def __eq__(self,other):
|
|
52
|
+
if hex(id(self)) == hex(id(other)):
|
|
53
|
+
return True
|
|
54
|
+
else:
|
|
55
|
+
return False
|
|
56
|
+
|
|
57
|
+
def __setitem__(self,key,value):
|
|
58
|
+
r'''
|
|
59
|
+
This is a subclass of the default __setitem__ behavior. The main aim
|
|
60
|
+
is to limit what type and shape of data can be written to protect
|
|
61
|
+
the integrity of the network.
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
Example
|
|
65
|
+
-------
|
|
66
|
+
>>> import OpenPNM
|
|
67
|
+
>>> pn = OpenPNM.Network.TestNet()
|
|
68
|
+
>>> pn['pore.example_property'] = 100
|
|
69
|
+
>>> pn['pore.example_property'][0]
|
|
70
|
+
100
|
|
71
|
+
|
|
72
|
+
'''
|
|
73
|
+
#Enforce correct dict naming
|
|
74
|
+
element = key.split('.')[0]
|
|
75
|
+
if (element != 'pore') and (element != 'throat'):
|
|
76
|
+
print('Array name \''+key+'\' does not begin with \'pore\' or \'throat\'')
|
|
77
|
+
return
|
|
78
|
+
#Convert value to an ndarray
|
|
79
|
+
value = sp.array(value,ndmin=1)
|
|
80
|
+
#Skip checks for 'coords', 'conns'
|
|
81
|
+
if (key == 'pore.coords') or (key == 'throat.conns'):
|
|
82
|
+
super(Core, self).__setitem__(key,value)
|
|
83
|
+
return
|
|
84
|
+
#Skip checks for protected props, and prevent changes if defined
|
|
85
|
+
if key.split('.')[1] in ['all']:
|
|
86
|
+
if key in self.keys():
|
|
87
|
+
if sp.shape(self[key]) == (0,):
|
|
88
|
+
logger.debug(key+' is being defined.')
|
|
89
|
+
super(Core, self).__setitem__(key,value)
|
|
90
|
+
else:
|
|
91
|
+
logger.warning(key+' is already defined.')
|
|
92
|
+
pass
|
|
93
|
+
else:
|
|
94
|
+
logger.debug(key+' is being defined.')
|
|
95
|
+
super(Core, self).__setitem__(key,value)
|
|
96
|
+
return
|
|
97
|
+
#Write value to dictionary
|
|
98
|
+
if sp.shape(value)[0] == 1: # If value is scalar
|
|
99
|
+
logger.debug('Broadcasting scalar value into vector: '+key)
|
|
100
|
+
value = sp.ones((self._count(element),),dtype=value.dtype)*value
|
|
101
|
+
super(Core, self).__setitem__(key,value)
|
|
102
|
+
elif sp.shape(value)[0] == self._count(element):
|
|
103
|
+
logger.debug('Updating vector: '+key)
|
|
104
|
+
super(Core, self).__setitem__(key,value)
|
|
105
|
+
else:
|
|
106
|
+
if self._count(element) == 0:
|
|
107
|
+
self.update({key:value})
|
|
108
|
+
else:
|
|
109
|
+
logger.warning('Cannot write vector with an array of the wrong length: '+key)
|
|
110
|
+
pass
|
|
111
|
+
|
|
112
|
+
def _set_ctrl(self,controller):
|
|
113
|
+
if self.name in controller.keys():
|
|
114
|
+
raise Exception('An object with that name is already present in simulation')
|
|
115
|
+
self._ctrl = controller
|
|
116
|
+
controller.update({self.name: self})
|
|
117
|
+
|
|
118
|
+
def _get_ctrl(self):
|
|
119
|
+
return self._ctrl
|
|
120
|
+
|
|
121
|
+
controller = property(_get_ctrl,_set_ctrl)
|
|
122
|
+
|
|
123
|
+
def _set_name(self,name):
|
|
124
|
+
if name in self.controller.keys():
|
|
125
|
+
raise Exception('An object named '+name+' already exists')
|
|
126
|
+
elif name is None:
|
|
127
|
+
name = ''.join(random.choice(string.ascii_uppercase + string.ascii_lowercase + string.digits) for _ in range(5))
|
|
128
|
+
name = self.__module__.split('.')[-1].strip('__') + '_' + name
|
|
129
|
+
elif self._name is not None:
|
|
130
|
+
logger.info('Changing the name of '+self.name+' to '+name)
|
|
131
|
+
# Check if name collides with any arrays in the simulation
|
|
132
|
+
objs = self._simulation()
|
|
133
|
+
for item in objs:
|
|
134
|
+
keys = [key.split('.')[-1] for key in item.keys()]
|
|
135
|
+
if name in keys:
|
|
136
|
+
raise Exception('That name is already in use as an array name')
|
|
137
|
+
for item in objs:
|
|
138
|
+
if 'pore.'+self.name in item.keys():
|
|
139
|
+
item['pore.'+name] = item.pop('pore.'+self.name)
|
|
140
|
+
if 'throat.'+self.name in item.keys():
|
|
141
|
+
item['throat.'+name] = item.pop('throat.'+self.name)
|
|
142
|
+
self._ctrl[name] = self._ctrl.pop(self.name)
|
|
143
|
+
self._name = name
|
|
144
|
+
|
|
145
|
+
def _get_name(self):
|
|
146
|
+
return self._name
|
|
147
|
+
|
|
148
|
+
name = property(_get_name,_set_name)
|
|
149
|
+
|
|
150
|
+
def _simulation(self):
|
|
151
|
+
temp = []
|
|
152
|
+
temp += [self._net]
|
|
153
|
+
temp += self._net._phases
|
|
154
|
+
temp += self._net._geometries
|
|
155
|
+
temp += self._net._physics
|
|
156
|
+
return temp
|
|
157
|
+
|
|
158
|
+
def clear(self):
|
|
159
|
+
r'''
|
|
160
|
+
A subclassed version of the standard dict's clear method
|
|
161
|
+
'''
|
|
162
|
+
pall = self['pore.all']
|
|
163
|
+
tall = self['throat.all']
|
|
164
|
+
super(Core,self).clear()
|
|
165
|
+
self.update({'throat.all':tall})
|
|
166
|
+
self.update({'pore.all':pall})
|
|
167
|
+
|
|
168
|
+
#--------------------------------------------------------------------------
|
|
169
|
+
'''Model Manipulation Methods'''
|
|
170
|
+
#--------------------------------------------------------------------------
|
|
171
|
+
#Note: These methods have been moved to the ModelsDict class but are left
|
|
172
|
+
#here for backward compatibility
|
|
173
|
+
def add_model(self,propname,model,regen_mode='normal',**kwargs):
|
|
174
|
+
self.models.add(propname=propname,model=model,regen_mode=regen_mode,**kwargs)
|
|
175
|
+
|
|
176
|
+
add_model.__doc__ = ModelsDict.add.__doc__
|
|
177
|
+
|
|
178
|
+
def regenerate(self,props='',mode='inclusive'):
|
|
179
|
+
self.models.regenerate(props=props,mode=mode)
|
|
180
|
+
|
|
181
|
+
regenerate.__doc__ = ModelsDict.regenerate.__doc__
|
|
182
|
+
|
|
183
|
+
#--------------------------------------------------------------------------
|
|
184
|
+
'Object lookup methods'
|
|
185
|
+
#--------------------------------------------------------------------------
|
|
186
|
+
|
|
187
|
+
def _find_object(self,obj_name='',obj_type=''):
|
|
188
|
+
r'''
|
|
189
|
+
Find objects associated with a given network model by name or type
|
|
190
|
+
|
|
191
|
+
Parameters
|
|
192
|
+
----------
|
|
193
|
+
obj_name : string
|
|
194
|
+
Name of sought object
|
|
195
|
+
|
|
196
|
+
obj_type : string
|
|
197
|
+
The type of object beign sought. Options are:
|
|
198
|
+
|
|
199
|
+
1. 'Network' or 'Networks'
|
|
200
|
+
2. 'Geometry' or 'Geometries'
|
|
201
|
+
3. 'Phase' or 'Phases'
|
|
202
|
+
4. 'Physics'
|
|
203
|
+
|
|
204
|
+
Returns
|
|
205
|
+
-------
|
|
206
|
+
OpenPNM object or list of objects
|
|
207
|
+
|
|
208
|
+
'''
|
|
209
|
+
if obj_name != '':
|
|
210
|
+
obj = []
|
|
211
|
+
if obj_name in ctrl.keys():
|
|
212
|
+
obj = ctrl[obj_name]
|
|
213
|
+
return obj
|
|
214
|
+
elif obj_type != '':
|
|
215
|
+
if obj_type in ['Geometry','Geometries','geometry','geometries']:
|
|
216
|
+
objs = ctrl.geometries()
|
|
217
|
+
elif obj_type in ['Phase','Phases','phase','phases']:
|
|
218
|
+
objs = ctrl.phases()
|
|
219
|
+
elif obj_type in ['Physics','physics']:
|
|
220
|
+
objs = ctrl.physics()
|
|
221
|
+
elif obj_type in ['Network','Networks','network','networks']:
|
|
222
|
+
objs = ctrl.networks()
|
|
223
|
+
return objs
|
|
224
|
+
|
|
225
|
+
def physics(self,phys_name=[]):
|
|
226
|
+
r'''
|
|
227
|
+
Retrieves Physics associated with the object
|
|
228
|
+
|
|
229
|
+
Parameters
|
|
230
|
+
----------
|
|
231
|
+
name : string or list of strings, optional
|
|
232
|
+
The name(s) of the Physics object to retrieve
|
|
233
|
+
Returns
|
|
234
|
+
-------
|
|
235
|
+
If name is NOT provided, then a list of Physics names is returned.
|
|
236
|
+
If a name or list of names IS provided, then the Physics object(s)
|
|
237
|
+
with those name(s) is returned.
|
|
238
|
+
'''
|
|
239
|
+
# If arg given as string, convert to list
|
|
240
|
+
if type(phys_name) == str:
|
|
241
|
+
phys_name = [phys_name]
|
|
242
|
+
if phys_name == []: # If default argument received
|
|
243
|
+
phys = [item.name for item in self._physics]
|
|
244
|
+
else: # If list of names received
|
|
245
|
+
phys = []
|
|
246
|
+
for item in self._physics:
|
|
247
|
+
if item.name in phys_name:
|
|
248
|
+
phys.append(item)
|
|
249
|
+
return phys
|
|
250
|
+
|
|
251
|
+
def phases(self,phase_name=[]):
|
|
252
|
+
r'''
|
|
253
|
+
Retrieves Phases associated with the object
|
|
254
|
+
|
|
255
|
+
Parameters
|
|
256
|
+
----------
|
|
257
|
+
name : string or list of strings, optional
|
|
258
|
+
The name(s) of the Phase object(s) to retrieve.
|
|
259
|
+
Returns
|
|
260
|
+
-------
|
|
261
|
+
If name is NOT provided, then a list of phase names is returned. If
|
|
262
|
+
a name are provided, then a list containing the requested objects
|
|
263
|
+
is returned.
|
|
264
|
+
'''
|
|
265
|
+
# If arg given as string, convert to list
|
|
266
|
+
if type(phase_name) == str:
|
|
267
|
+
phase_name = [phase_name]
|
|
268
|
+
if phase_name == []: # If default argument received
|
|
269
|
+
phase = [item.name for item in self._phases]
|
|
270
|
+
else: # If list of names received
|
|
271
|
+
phase = []
|
|
272
|
+
for item in self._phases:
|
|
273
|
+
if item.name in phase_name:
|
|
274
|
+
phase.append(item)
|
|
275
|
+
return phase
|
|
276
|
+
|
|
277
|
+
def geometries(self,geom_name=[]):
|
|
278
|
+
r'''
|
|
279
|
+
Retrieves Geometry object(s) associated with the object
|
|
280
|
+
|
|
281
|
+
Parameters
|
|
282
|
+
----------
|
|
283
|
+
name : string or list of strings, optional
|
|
284
|
+
The name(s) of the Geometry object to retrieve.
|
|
285
|
+
Returns
|
|
286
|
+
-------
|
|
287
|
+
If name is NOT provided, then a list of Geometry names is returned.
|
|
288
|
+
If a name IS provided, then the Geometry object of that name is
|
|
289
|
+
returned.
|
|
290
|
+
'''
|
|
291
|
+
# If arg given as string, convert to list
|
|
292
|
+
if type(geom_name) == str:
|
|
293
|
+
geom_name = [geom_name]
|
|
294
|
+
if geom_name == []: # If default argument received
|
|
295
|
+
geom = [item.name for item in self._geometries]
|
|
296
|
+
else: # If list of names received
|
|
297
|
+
geom = []
|
|
298
|
+
for item in self._geometries:
|
|
299
|
+
if item.name in geom_name:
|
|
300
|
+
geom.append(item)
|
|
301
|
+
return geom
|
|
302
|
+
|
|
303
|
+
def network(self,name=''):
|
|
304
|
+
r'''
|
|
305
|
+
Retrieves the network associated with the object. If the object is
|
|
306
|
+
a network, then it returns a handle to itself.
|
|
307
|
+
|
|
308
|
+
Parameters
|
|
309
|
+
----------
|
|
310
|
+
name : string, optional
|
|
311
|
+
The name of the Network object to retrieve.
|
|
312
|
+
|
|
313
|
+
Returns
|
|
314
|
+
-------
|
|
315
|
+
If a name IS provided, then the parent netowrk object is returned.
|
|
316
|
+
|
|
317
|
+
Notes
|
|
318
|
+
-----
|
|
319
|
+
This doesn't quite work yet...we have to decide how to treat sub-nets first
|
|
320
|
+
'''
|
|
321
|
+
if name == '':
|
|
322
|
+
if self._net is None:
|
|
323
|
+
net = [self]
|
|
324
|
+
else:
|
|
325
|
+
net = [self._net]
|
|
326
|
+
else:
|
|
327
|
+
net = []
|
|
328
|
+
temp = self._find_object(obj_name=name)
|
|
329
|
+
if hasattr(temp,'_isa'):
|
|
330
|
+
if temp._isa('Network'):
|
|
331
|
+
net = temp
|
|
332
|
+
return net
|
|
333
|
+
|
|
334
|
+
#--------------------------------------------------------------------------
|
|
335
|
+
'''Data Query Methods'''
|
|
336
|
+
#--------------------------------------------------------------------------
|
|
337
|
+
def props(self,element='',mode='all'):
|
|
338
|
+
r'''
|
|
339
|
+
Returns a list containing the names of all defined pore or throat
|
|
340
|
+
properties.
|
|
341
|
+
|
|
342
|
+
Parameters
|
|
343
|
+
----------
|
|
344
|
+
element : string, optional
|
|
345
|
+
Can be either 'pore' or 'throat' to specify what properties are
|
|
346
|
+
returned. If no element is given, both are returned
|
|
347
|
+
|
|
348
|
+
mode : string, optional
|
|
349
|
+
Controls what type of properties are returned. Options are:
|
|
350
|
+
|
|
351
|
+
- 'all' : Returns all properties on the object
|
|
352
|
+
- 'models' : Returns only properties that are associated with a model
|
|
353
|
+
- 'constants' : Returns only properties that are set as constant values
|
|
354
|
+
|
|
355
|
+
Returns
|
|
356
|
+
-------
|
|
357
|
+
A an alphabetically sorted list containing the string name of all
|
|
358
|
+
pore or throat properties currently defined. This list is an iterable,
|
|
359
|
+
so is useful for scanning through properties.
|
|
360
|
+
|
|
361
|
+
See Also
|
|
362
|
+
--------
|
|
363
|
+
labels
|
|
364
|
+
|
|
365
|
+
Examples
|
|
366
|
+
--------
|
|
367
|
+
>>> import OpenPNM
|
|
368
|
+
>>> pn = OpenPNM.Network.TestNet()
|
|
369
|
+
>>> pn.props('pore')
|
|
370
|
+
['pore.coords']
|
|
371
|
+
>>> pn.props('throat')
|
|
372
|
+
['throat.conns']
|
|
373
|
+
>>> #pn.props() # this lists both, but in random order, which breaks
|
|
374
|
+
>>> # # our automatic document testing so it's commented here
|
|
375
|
+
'''
|
|
376
|
+
|
|
377
|
+
props = []
|
|
378
|
+
for item in self.keys():
|
|
379
|
+
if self[item].dtype != bool:
|
|
380
|
+
props.append(item)
|
|
381
|
+
|
|
382
|
+
all_models = list(self.models.keys())
|
|
383
|
+
constants = [item for item in props if item not in all_models]
|
|
384
|
+
models = [item for item in props if item in all_models]
|
|
385
|
+
|
|
386
|
+
if element in ['pore','pores']:
|
|
387
|
+
element = 'pore'
|
|
388
|
+
elif element in ['throat','throats']:
|
|
389
|
+
element = 'throat'
|
|
390
|
+
|
|
391
|
+
temp = []
|
|
392
|
+
if mode == 'all':
|
|
393
|
+
if element == '': temp = props
|
|
394
|
+
else: temp = [item for item in props if item.split('.')[0]==element]
|
|
395
|
+
elif mode == 'models':
|
|
396
|
+
if element == '': temp = models
|
|
397
|
+
else: temp = [item for item in models if item.split('.')[0]==element]
|
|
398
|
+
elif mode == 'constants':
|
|
399
|
+
if element == '': temp = constants
|
|
400
|
+
else: temp = [item for item in constants if item.split('.')[0]==element]
|
|
401
|
+
a = Tools.PrintableList(temp)
|
|
402
|
+
# a.sort()
|
|
403
|
+
return a
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
def _get_labels(self,element='',locations=[],mode='union'):
|
|
407
|
+
r'''
|
|
408
|
+
This is the actual label getter method, but it should not be called directly.
|
|
409
|
+
Wrapper methods have been created, use labels().
|
|
410
|
+
'''
|
|
411
|
+
# Collect list of all pore OR throat labels
|
|
412
|
+
labels = []
|
|
413
|
+
for item in self.keys():
|
|
414
|
+
if item.split('.')[0] == element:
|
|
415
|
+
if self[item].dtype in ['bool']:
|
|
416
|
+
labels.append(item)
|
|
417
|
+
labels.sort()
|
|
418
|
+
if locations == []:
|
|
419
|
+
return Tools.PrintableList(labels)
|
|
420
|
+
else:
|
|
421
|
+
labels = sp.array(labels)
|
|
422
|
+
locations = sp.array(locations,ndmin=1)
|
|
423
|
+
if locations.dtype in ['bool']:
|
|
424
|
+
locations = self._get_indices(element=element)[locations]
|
|
425
|
+
else:
|
|
426
|
+
locations = sp.array(locations,dtype=int)
|
|
427
|
+
arr = sp.zeros((sp.shape(locations)[0],len(labels)),dtype=bool)
|
|
428
|
+
col = 0
|
|
429
|
+
for item in labels:
|
|
430
|
+
arr[:,col] = self[item][locations]
|
|
431
|
+
col = col + 1
|
|
432
|
+
if mode == 'count':
|
|
433
|
+
return sp.sum(arr,axis=1)
|
|
434
|
+
if mode == 'union':
|
|
435
|
+
temp = labels[sp.sum(arr,axis=0)>0]
|
|
436
|
+
temp.tolist()
|
|
437
|
+
return Tools.PrintableList(temp)
|
|
438
|
+
if mode == 'intersection':
|
|
439
|
+
temp = labels[sp.sum(arr,axis=0)==sp.shape(locations,)[0]]
|
|
440
|
+
temp.tolist()
|
|
441
|
+
return Tools.PrintableList(temp)
|
|
442
|
+
if mode in ['difference', 'not']:
|
|
443
|
+
temp = labels[sp.sum(arr,axis=0)!=sp.shape(locations,)[0]]
|
|
444
|
+
temp.tolist()
|
|
445
|
+
return Tools.PrintableList(temp)
|
|
446
|
+
if mode == 'mask':
|
|
447
|
+
return arr
|
|
448
|
+
if mode == 'none':
|
|
449
|
+
temp = sp.ndarray((sp.shape(locations,)[0],),dtype=object)
|
|
450
|
+
for i in sp.arange(0,sp.shape(locations,)[0]):
|
|
451
|
+
temp[i] = list(labels[arr[i,:]])
|
|
452
|
+
return temp
|
|
453
|
+
else:
|
|
454
|
+
print('unrecognized mode')
|
|
455
|
+
|
|
456
|
+
def labels(self,element='',pores=[],throats=[],mode='union'):
|
|
457
|
+
r'''
|
|
458
|
+
Returns the labels applied to specified pore or throat locations
|
|
459
|
+
|
|
460
|
+
Parameters
|
|
461
|
+
----------
|
|
462
|
+
pores (or throats) : array_like
|
|
463
|
+
The pores (or throats) whose labels are sought. If left empty a
|
|
464
|
+
dictionary containing all pore and throat labels is returned.
|
|
465
|
+
mode : string, optional
|
|
466
|
+
Controls how the query should be performed
|
|
467
|
+
|
|
468
|
+
* 'none' : An N x Li list of all labels applied to each input pore (or throats). Li can vary betwen pores (and throats)
|
|
469
|
+
|
|
470
|
+
* 'union' : A list of labels applied to ANY of the given pores (or throats)
|
|
471
|
+
|
|
472
|
+
* 'intersection' : Label applied to ALL of the given pores (or throats)
|
|
473
|
+
|
|
474
|
+
* 'not' : Labels NOT applied to ALL pores (or throats)
|
|
475
|
+
|
|
476
|
+
* 'count' : The number of labels on each pores (or throats)
|
|
477
|
+
|
|
478
|
+
* 'mask' : returns an N x Lt array, where each row corresponds to a pore (or throat) location, and each column contains the truth value for the existance of labels as returned from labels(pores='all',mode='union')).
|
|
479
|
+
|
|
480
|
+
Examples
|
|
481
|
+
--------
|
|
482
|
+
>>> import OpenPNM
|
|
483
|
+
>>> pn = OpenPNM.Network.TestNet()
|
|
484
|
+
>>> pn.labels(pores=[0,1,5,6])
|
|
485
|
+
['pore.all', 'pore.bottom', 'pore.front', 'pore.left']
|
|
486
|
+
>>> pn.labels(pores=[0,1,5,6],mode='intersection')
|
|
487
|
+
['pore.all', 'pore.bottom']
|
|
488
|
+
'''
|
|
489
|
+
if (pores == []) and (throats == []):
|
|
490
|
+
if element == '':
|
|
491
|
+
temp = []
|
|
492
|
+
temp = self._get_labels(element='pore')
|
|
493
|
+
temp.extend(self._get_labels(element='throat'))
|
|
494
|
+
elif element in ['pore','pores']:
|
|
495
|
+
temp = self._get_labels(element='pore',locations=[], mode=mode)
|
|
496
|
+
elif element in ['throat','throats']:
|
|
497
|
+
temp = self._get_labels(element='throat',locations=[], mode=mode)
|
|
498
|
+
else:
|
|
499
|
+
logger.error('Unrecognized element')
|
|
500
|
+
return
|
|
501
|
+
elif pores is not []:
|
|
502
|
+
if pores == 'all':
|
|
503
|
+
pores = self.pores()
|
|
504
|
+
pores = sp.array(pores,ndmin=1)
|
|
505
|
+
temp = self._get_labels(element='pore',locations=pores, mode=mode)
|
|
506
|
+
elif throats is not []:
|
|
507
|
+
if throats == 'all':
|
|
508
|
+
throats = self.throats()
|
|
509
|
+
throats = sp.array(throats,ndmin=1)
|
|
510
|
+
temp = self._get_labels(element='throat',locations=throats,mode=mode)
|
|
511
|
+
return temp
|
|
512
|
+
|
|
513
|
+
def filter_by_label(self,pores=[],throats=[],labels='',mode='union'):
|
|
514
|
+
r'''
|
|
515
|
+
Returns which of the supplied pores (or throats) has the specified label
|
|
516
|
+
|
|
517
|
+
Parameters
|
|
518
|
+
----------
|
|
519
|
+
pores, or throats : array_like
|
|
520
|
+
List of pores or throats to be filtered
|
|
521
|
+
|
|
522
|
+
labels : list of strings
|
|
523
|
+
The labels to apply as a filter
|
|
524
|
+
|
|
525
|
+
mode : string
|
|
526
|
+
Controls how the filter is applied. Options include:
|
|
527
|
+
|
|
528
|
+
* 'union' : (default) All locations with ANY of the given labels are kept.
|
|
529
|
+
|
|
530
|
+
* 'intersection' : Only locations with ALL the given labels are kept.
|
|
531
|
+
|
|
532
|
+
* 'not_intersection' : Only locations with exactly one of the given labels are kept.
|
|
533
|
+
|
|
534
|
+
* 'not' : Only locations with none of the given labels are kept.
|
|
535
|
+
|
|
536
|
+
See Also
|
|
537
|
+
--------
|
|
538
|
+
pores
|
|
539
|
+
throats
|
|
540
|
+
|
|
541
|
+
Examples
|
|
542
|
+
--------
|
|
543
|
+
>>> import OpenPNM
|
|
544
|
+
>>> pn = OpenPNM.Network.TestNet()
|
|
545
|
+
>>> pn.filter_by_label(pores=[0,1,5,6],labels='left')
|
|
546
|
+
array([0, 1])
|
|
547
|
+
>>> Ps = pn.pores(['top','bottom','front'],mode='union')
|
|
548
|
+
>>> pn.filter_by_label(pores=Ps,labels=['top','front'],mode='intersection')
|
|
549
|
+
array([100, 105, 110, 115, 120])
|
|
550
|
+
'''
|
|
551
|
+
if type(labels) == str: # Convert input to list
|
|
552
|
+
labels = [labels]
|
|
553
|
+
# Convert inputs to locations and element
|
|
554
|
+
if pores != []:
|
|
555
|
+
element = 'pore'
|
|
556
|
+
locations = sp.array(pores)
|
|
557
|
+
if throats != []:
|
|
558
|
+
element = 'throat'
|
|
559
|
+
locations = sp.array(throats)
|
|
560
|
+
# Do it
|
|
561
|
+
labels = [element+'.'+item.split('.')[-1] for item in labels]
|
|
562
|
+
all_locs = self._get_indices(element=element,labels=labels,mode=mode)
|
|
563
|
+
mask = self._tomask(locations=all_locs,element=element)
|
|
564
|
+
ind = mask[locations]
|
|
565
|
+
return locations[ind]
|
|
566
|
+
|
|
567
|
+
def _get_indices(self,element,labels=['all'],mode='union'):
|
|
568
|
+
r'''
|
|
569
|
+
This is the actual method for getting indices, but should not be called
|
|
570
|
+
directly. Use pores or throats instead.
|
|
571
|
+
'''
|
|
572
|
+
element.rstrip('s') # Correct plural form of element keyword
|
|
573
|
+
if element+'.all' not in self.keys():
|
|
574
|
+
raise Exception('Cannot proceed without {}.all'.format(element))
|
|
575
|
+
if type(labels) == str: # Convert string to list, if necessary
|
|
576
|
+
labels = [labels]
|
|
577
|
+
for label in labels: # Parse the labels list for wildcards "*"
|
|
578
|
+
if label.startswith('*'):
|
|
579
|
+
labels.remove(label)
|
|
580
|
+
temp = [item for item in self.labels() if item.split('.')[-1].endswith(label.strip('*'))]
|
|
581
|
+
if temp == []:
|
|
582
|
+
temp = [label.strip('*')]
|
|
583
|
+
labels.extend(temp)
|
|
584
|
+
if label.endswith('*'):
|
|
585
|
+
labels.remove(label)
|
|
586
|
+
temp = [item for item in self.labels() if item.split('.')[-1].startswith(label.strip('*'))]
|
|
587
|
+
if temp == []:
|
|
588
|
+
temp = [label.strip('*')]
|
|
589
|
+
labels.extend(temp)
|
|
590
|
+
# Begin computing label array
|
|
591
|
+
if mode == 'union':
|
|
592
|
+
union = sp.zeros_like(self[element+'.all'],dtype=bool)
|
|
593
|
+
for item in labels: #iterate over labels list and collect all indices
|
|
594
|
+
union = union + self[element+'.'+item.split('.')[-1]]
|
|
595
|
+
ind = union
|
|
596
|
+
elif mode == 'intersection':
|
|
597
|
+
intersect = sp.ones_like(self[element+'.all'],dtype=bool)
|
|
598
|
+
for item in labels: #iterate over labels list and collect all indices
|
|
599
|
+
intersect = intersect*self[element+'.'+item.split('.')[-1]]
|
|
600
|
+
ind = intersect
|
|
601
|
+
elif mode == 'not_intersection':
|
|
602
|
+
not_intersect = sp.zeros_like(self[element+'.all'],dtype=int)
|
|
603
|
+
for item in labels: #iterate over labels list and collect all indices
|
|
604
|
+
info = self[element+'.'+item.split('.')[-1]]
|
|
605
|
+
not_intersect = not_intersect + sp.int8(info)
|
|
606
|
+
ind = (not_intersect == 1)
|
|
607
|
+
elif mode in ['difference','not']:
|
|
608
|
+
none = sp.zeros_like(self[element+'.all'],dtype=int)
|
|
609
|
+
for item in labels: #iterate over labels list and collect all indices
|
|
610
|
+
info = self[element+'.'+item.split('.')[-1]]
|
|
611
|
+
none = none - sp.int8(info)
|
|
612
|
+
ind = (none == 0)
|
|
613
|
+
#Extract indices from boolean mask
|
|
614
|
+
ind = sp.where(ind==True)[0]
|
|
615
|
+
ind = ind.astype(dtype=int)
|
|
616
|
+
return ind
|
|
617
|
+
|
|
618
|
+
def pores(self,labels='all',mode='union'):
|
|
619
|
+
r'''
|
|
620
|
+
Returns pore locations where given labels exist.
|
|
621
|
+
|
|
622
|
+
Parameters
|
|
623
|
+
----------
|
|
624
|
+
labels : list of strings, optional
|
|
625
|
+
The pore label(s) whose locations are requested. If omitted, all
|
|
626
|
+
pore inidices are returned. This argument also accepts '*' for
|
|
627
|
+
wildcard searches.
|
|
628
|
+
mode : string, optional
|
|
629
|
+
Specifies how the query should be performed. The options are:
|
|
630
|
+
|
|
631
|
+
* 'union' : (default) All pores with ANY of the given labels are returned.
|
|
632
|
+
|
|
633
|
+
* 'intersection' : Only pore with ALL the given labels are returned.
|
|
634
|
+
|
|
635
|
+
* 'not_intersection' : Only pores with exactly one of the given labels are returned.
|
|
636
|
+
|
|
637
|
+
* 'not' : Only pores with none of the given labels are returned.
|
|
638
|
+
|
|
639
|
+
Examples
|
|
640
|
+
--------
|
|
641
|
+
>>> import OpenPNM
|
|
642
|
+
>>> pn = OpenPNM.Network.TestNet()
|
|
643
|
+
>>> pind = pn.pores(labels=['top','front'],mode='union')
|
|
644
|
+
>>> pind[[0,1,2,-3,-2,-1]]
|
|
645
|
+
array([ 0, 5, 10, 122, 123, 124])
|
|
646
|
+
>>> pn.pores(labels=['top','front'],mode='intersection')
|
|
647
|
+
array([100, 105, 110, 115, 120])
|
|
648
|
+
'''
|
|
649
|
+
if labels == 'all':
|
|
650
|
+
Np = sp.shape(self['pore.all'])[0]
|
|
651
|
+
ind = sp.arange(0,Np)
|
|
652
|
+
else:
|
|
653
|
+
ind = self._get_indices(element='pore',labels=labels,mode=mode)
|
|
654
|
+
return ind
|
|
655
|
+
|
|
656
|
+
@property
|
|
657
|
+
def Ps(self):
|
|
658
|
+
r'''
|
|
659
|
+
A shortcut to get a list of all pores on the object
|
|
660
|
+
'''
|
|
661
|
+
return self.pores()
|
|
662
|
+
|
|
663
|
+
def throats(self,labels='all',mode='union'):
|
|
664
|
+
r'''
|
|
665
|
+
Returns throat locations where given labels exist.
|
|
666
|
+
|
|
667
|
+
Parameters
|
|
668
|
+
----------
|
|
669
|
+
labels : list of strings, optional
|
|
670
|
+
The throat label(s) whose locations are requested. If omitted,
|
|
671
|
+
'all' throat inidices are returned. This argument also accepts
|
|
672
|
+
'*' for wildcard searches.
|
|
673
|
+
mode : string, optional
|
|
674
|
+
Specifies how the query should be performed. The options are:
|
|
675
|
+
|
|
676
|
+
* 'union' : (default) All throats with ANY of the given labels are returned.
|
|
677
|
+
|
|
678
|
+
* 'intersection' : Only throats with ALL the given labels are counted.
|
|
679
|
+
|
|
680
|
+
* 'not_intersection' : Only throats with exactly one of the given labels are counted.
|
|
681
|
+
|
|
682
|
+
* 'not' : Only throats with none of the given labels are returned.
|
|
683
|
+
|
|
684
|
+
Examples
|
|
685
|
+
--------
|
|
686
|
+
>>> import OpenPNM
|
|
687
|
+
>>> pn = OpenPNM.Network.TestNet()
|
|
688
|
+
>>> Tind = pn.throats()
|
|
689
|
+
>>> Tind[0:5]
|
|
690
|
+
array([0, 1, 2, 3, 4])
|
|
691
|
+
|
|
692
|
+
'''
|
|
693
|
+
if labels == 'all':
|
|
694
|
+
Nt = sp.shape(self['throat.all'])[0]
|
|
695
|
+
ind = sp.arange(0,Nt)
|
|
696
|
+
else:
|
|
697
|
+
ind = self._get_indices(element='throat',labels=labels,mode=mode)
|
|
698
|
+
return ind
|
|
699
|
+
|
|
700
|
+
@property
|
|
701
|
+
def Ts(self):
|
|
702
|
+
r'''
|
|
703
|
+
A shortcut to get a list of all throats on the object
|
|
704
|
+
'''
|
|
705
|
+
return self.throats()
|
|
706
|
+
|
|
707
|
+
def _tomask(self,locations,element):
|
|
708
|
+
r'''
|
|
709
|
+
This is a generalized version of tomask that accepts a string of
|
|
710
|
+
'pore' or 'throat' for programmatic access.
|
|
711
|
+
'''
|
|
712
|
+
if sp.shape(locations)[0] == 0:
|
|
713
|
+
return sp.zeros_like(self._get_indices(element=element),dtype=bool)
|
|
714
|
+
if element in ['pore','pores']:
|
|
715
|
+
Np = sp.shape(self['pore.all'])[0]
|
|
716
|
+
pores = sp.array(locations,ndmin=1)
|
|
717
|
+
mask = sp.zeros((Np,),dtype=bool)
|
|
718
|
+
mask[pores] = True
|
|
719
|
+
if element in ['throat','throats']:
|
|
720
|
+
Nt = sp.shape(self['throat.all'])[0]
|
|
721
|
+
throats = sp.array(locations,ndmin=1)
|
|
722
|
+
mask = sp.zeros((Nt,),dtype=bool)
|
|
723
|
+
mask[throats] = True
|
|
724
|
+
return mask
|
|
725
|
+
|
|
726
|
+
def tomask(self,pores=None,throats=None):
|
|
727
|
+
r'''
|
|
728
|
+
Convert a list of pore or throat indices into a boolean mask of the
|
|
729
|
+
correct length
|
|
730
|
+
|
|
731
|
+
Parameters
|
|
732
|
+
----------
|
|
733
|
+
pores or throats : array_like
|
|
734
|
+
List of pore or throat indices
|
|
735
|
+
|
|
736
|
+
Returns
|
|
737
|
+
-------
|
|
738
|
+
mask : array_like
|
|
739
|
+
A boolean mask of length Np or Nt with True in the locations of
|
|
740
|
+
pores or throats received.
|
|
741
|
+
|
|
742
|
+
'''
|
|
743
|
+
if pores is not None:
|
|
744
|
+
mask = self._tomask(element='pore',locations=pores)
|
|
745
|
+
if throats is not None:
|
|
746
|
+
mask = self._tomask(element='throat',locations=throats)
|
|
747
|
+
return mask
|
|
748
|
+
|
|
749
|
+
def toindices(self,mask):
|
|
750
|
+
r'''
|
|
751
|
+
Convert a boolean mask a list of pore or throat indices
|
|
752
|
+
|
|
753
|
+
Parameters
|
|
754
|
+
----------
|
|
755
|
+
mask : array_like booleans
|
|
756
|
+
A boolean array with True at locations where indices are desired.
|
|
757
|
+
The appropriate indices are returned based an the length of mask,
|
|
758
|
+
which must be either Np or Nt long.
|
|
759
|
+
|
|
760
|
+
Returns
|
|
761
|
+
-------
|
|
762
|
+
indices : array_like
|
|
763
|
+
A list of pore or throat indices corresponding the locations where
|
|
764
|
+
the received mask was True.
|
|
765
|
+
|
|
766
|
+
Notes
|
|
767
|
+
-----
|
|
768
|
+
This behavior could just as easily be accomplished by using the mask
|
|
769
|
+
in pn.pores()[mask] or pn.throats()[mask]. This method is just a thin
|
|
770
|
+
convenience function and is a compliment to tomask().
|
|
771
|
+
|
|
772
|
+
'''
|
|
773
|
+
if sp.shape(mask)[0] == self.num_pores():
|
|
774
|
+
indices = self.pores()[mask]
|
|
775
|
+
elif sp.shape(mask)[0] == self.num_throats():
|
|
776
|
+
indices = self.throats()[mask]
|
|
777
|
+
else:
|
|
778
|
+
raise Exception('Mask received was neither Np nor Nt long')
|
|
779
|
+
return indices
|
|
780
|
+
|
|
781
|
+
def interpolate_data(self,data):
|
|
782
|
+
r"""
|
|
783
|
+
Determines a pore (or throat) property as the average of it's neighboring
|
|
784
|
+
throats (or pores)
|
|
785
|
+
|
|
786
|
+
Parameters
|
|
787
|
+
----------
|
|
788
|
+
data : array_like
|
|
789
|
+
A list of specific values to be interpolated. List MUST be either
|
|
790
|
+
Np or Nt long
|
|
791
|
+
|
|
792
|
+
Returns
|
|
793
|
+
-------
|
|
794
|
+
An array containing interpolated pore (or throat) data
|
|
795
|
+
|
|
796
|
+
Notes
|
|
797
|
+
-----
|
|
798
|
+
- This uses an unweighted average, without attempting to account for distances or sizes of pores and throats.
|
|
799
|
+
- Only one of pores, throats OR data are accepted
|
|
800
|
+
|
|
801
|
+
"""
|
|
802
|
+
mro = [module.__name__ for module in self.__class__.__mro__]
|
|
803
|
+
if 'GenericNetwork' in mro:
|
|
804
|
+
net = self
|
|
805
|
+
Ts = net.throats()
|
|
806
|
+
Ps = net.pores()
|
|
807
|
+
label = 'all'
|
|
808
|
+
elif ('GenericPhase' in mro) or ('GenericAlgorithm' in mro):
|
|
809
|
+
net = self._net
|
|
810
|
+
Ts = net.throats()
|
|
811
|
+
Ps = net.pores()
|
|
812
|
+
label = 'all'
|
|
813
|
+
elif ('GenericGeometry' in mro) or ('GenericPhysics' in mro):
|
|
814
|
+
net = self._net
|
|
815
|
+
Ts = net.throats(self.name)
|
|
816
|
+
Ps = net.pores(self.name)
|
|
817
|
+
label = self.name
|
|
818
|
+
if sp.shape(data)[0] == self.Nt:
|
|
819
|
+
#Upcast data to full network size
|
|
820
|
+
temp = sp.ones((net.Nt,))*sp.nan
|
|
821
|
+
temp[Ts] = data
|
|
822
|
+
data = temp
|
|
823
|
+
temp = sp.ones((net.Np,))*sp.nan
|
|
824
|
+
for pore in Ps:
|
|
825
|
+
neighborTs = net.find_neighbor_throats(pore)
|
|
826
|
+
neighborTs = net.filter_by_label(throats=neighborTs,labels=label)
|
|
827
|
+
temp[pore] = sp.mean(data[neighborTs])
|
|
828
|
+
values = temp[Ps]
|
|
829
|
+
elif sp.shape(data)[0] == self.Np:
|
|
830
|
+
#Upcast data to full network size
|
|
831
|
+
temp = sp.ones((net.Np,))*sp.nan
|
|
832
|
+
temp[Ps] = data
|
|
833
|
+
data = temp
|
|
834
|
+
Ps12 = net.find_connected_pores(throats=Ts,flatten=False)
|
|
835
|
+
values = sp.mean(data[Ps12],axis=1)
|
|
836
|
+
else:
|
|
837
|
+
logger.error('Received data was an ambiguous length')
|
|
838
|
+
raise Exception()
|
|
839
|
+
return values
|
|
840
|
+
|
|
841
|
+
def _interleave_data(self,prop,sources):
|
|
842
|
+
r'''
|
|
843
|
+
Retrieves requested property from associated objects, to produce a full
|
|
844
|
+
Np or Nt length array.
|
|
845
|
+
|
|
846
|
+
Parameters
|
|
847
|
+
----------
|
|
848
|
+
prop : string
|
|
849
|
+
The property name to be retrieved
|
|
850
|
+
sources : list
|
|
851
|
+
List of object names OR objects from which data is retrieved
|
|
852
|
+
|
|
853
|
+
Returns
|
|
854
|
+
-------
|
|
855
|
+
A full length (Np or Nt) array of requested property values.
|
|
856
|
+
|
|
857
|
+
Notes
|
|
858
|
+
-----
|
|
859
|
+
This makes an effort to maintain the data 'type' when possible; however
|
|
860
|
+
when data is missing this can be tricky. Float and boolean data is
|
|
861
|
+
fine, but missing ints are converted to float when nans are inserted.
|
|
862
|
+
|
|
863
|
+
Examples
|
|
864
|
+
--------
|
|
865
|
+
>>> import OpenPNM
|
|
866
|
+
>>> pn = OpenPNM.Network.TestNet()
|
|
867
|
+
>>> Ps = pn.pores('top',mode='not')
|
|
868
|
+
>>> Ts = pn.find_neighbor_throats(pores=Ps,mode='intersection',flatten=True)
|
|
869
|
+
>>> geom = OpenPNM.Geometry.TestGeometry(network=pn,pores=Ps,throats=Ts)
|
|
870
|
+
>>> Ps = pn.pores('top')
|
|
871
|
+
>>> Ts = pn.find_neighbor_throats(pores=Ps,mode='not_intersection')
|
|
872
|
+
>>> boun = OpenPNM.Geometry.Boundary(network=pn,pores=Ps,throats=Ts)
|
|
873
|
+
>>> geom['pore.test_int'] = sp.random.randint(0,100,geom.Np)
|
|
874
|
+
>>> print(pn['pore.test_int'].dtype)
|
|
875
|
+
float64
|
|
876
|
+
>>> boun['pore.test_int'] = sp.ones(boun.Np).astype(int)
|
|
877
|
+
>>> print(pn['pore.test_int'].dtype)
|
|
878
|
+
int32
|
|
879
|
+
>>> boun['pore.test_int'] = sp.rand(boun.Np)<0.5
|
|
880
|
+
>>> print(pn['pore.test_int'].dtype)
|
|
881
|
+
bool
|
|
882
|
+
>>> geom['pore.test_bool'] = sp.rand(geom.Np)<0.5
|
|
883
|
+
>>> print(pn['pore.test_bool'].dtype)
|
|
884
|
+
bool
|
|
885
|
+
>>> boun['pore.test_bool'] = sp.ones(boun.Np).astype(int)
|
|
886
|
+
>>> print(pn['pore.test_bool'].dtype)
|
|
887
|
+
bool
|
|
888
|
+
>>> boun['pore.test_bool'] = sp.rand(boun.Np)<0.5
|
|
889
|
+
>>> print(pn['pore.test_bool'].dtype)
|
|
890
|
+
bool
|
|
891
|
+
'''
|
|
892
|
+
element = prop.split('.')[0]
|
|
893
|
+
temp = sp.ndarray((self._count(element)))
|
|
894
|
+
nan_locs = sp.ndarray((self._count(element)),dtype='bool')
|
|
895
|
+
nan_locs.fill(False)
|
|
896
|
+
bool_locs = sp.ndarray((self._count(element)),dtype='bool')
|
|
897
|
+
bool_locs.fill(False)
|
|
898
|
+
dtypes = []
|
|
899
|
+
dtypenames = []
|
|
900
|
+
prop_found = False #Flag to indicate if prop was found on a sub-object
|
|
901
|
+
values_dim=0
|
|
902
|
+
for item in sources:
|
|
903
|
+
#Check if sources were given as list of objects OR names
|
|
904
|
+
try: item.name
|
|
905
|
+
except: item = self._find_object(obj_name=item)
|
|
906
|
+
locations = self._get_indices(element=element,labels=item.name,mode='union')
|
|
907
|
+
if prop not in item.keys():
|
|
908
|
+
values = sp.ones_like(temp[locations])*sp.nan
|
|
909
|
+
dtypenames.append('nan')
|
|
910
|
+
dtypes.append(sp.dtype(bool))
|
|
911
|
+
nan_locs[locations]=True
|
|
912
|
+
else:
|
|
913
|
+
prop_found = True
|
|
914
|
+
values = item[prop]
|
|
915
|
+
dtypenames.append(values.dtype.name)
|
|
916
|
+
dtypes.append(values.dtype)
|
|
917
|
+
if values.dtype == 'bool':
|
|
918
|
+
bool_locs[locations]=True
|
|
919
|
+
try: values_dim = sp.shape(values)[1]
|
|
920
|
+
except: pass
|
|
921
|
+
if values_dim > 0:
|
|
922
|
+
try:
|
|
923
|
+
temp_dim = sp.shape(temp)[1]
|
|
924
|
+
if temp_dim != values_dim:
|
|
925
|
+
logger.warning(prop+' data has different dimensions, consider revising data in object '+str(item.name))
|
|
926
|
+
except:
|
|
927
|
+
temp = sp.ndarray([self._count(element),values_dim])
|
|
928
|
+
if values.dtype == 'object' and temp.dtype != 'object':
|
|
929
|
+
temp = temp.astype('object')
|
|
930
|
+
temp[locations] = values #Assign values
|
|
931
|
+
#Check if requested prop was found on any sub-objects
|
|
932
|
+
if prop_found == False:
|
|
933
|
+
raise KeyError(prop)
|
|
934
|
+
#Analyze and assign data type
|
|
935
|
+
if sp.all([t in ['bool','nan'] for t in dtypenames]): # If all entries are 'bool' (or 'nan')
|
|
936
|
+
temp = sp.array(temp,dtype='bool')
|
|
937
|
+
if sp.sum(nan_locs)>0:
|
|
938
|
+
temp[nan_locs]=False
|
|
939
|
+
elif sp.all([t == dtypenames[0] for t in dtypenames]) : # If all entries are same type
|
|
940
|
+
temp = sp.array(temp,dtype=dtypes[0])
|
|
941
|
+
elif sp.all([t in ['int','nan','float','int32','int64','float32','float64','bool'] for t in dtypenames]): # If all entries are 'bool' (or 'nan')
|
|
942
|
+
if 'bool' in dtypenames:
|
|
943
|
+
temp = sp.array(temp,dtype='bool')
|
|
944
|
+
temp[~bool_locs]=False
|
|
945
|
+
logger.info(prop+' has been converted to bool, some data may be lost')
|
|
946
|
+
else:
|
|
947
|
+
temp = sp.array(temp,dtype='float')
|
|
948
|
+
logger.info(prop+' has been converted to float.')
|
|
949
|
+
elif sp.all([t in ['object','nan'] for t in dtypenames]): # If all entries are 'bool' (or 'nan')
|
|
950
|
+
pass
|
|
951
|
+
else:
|
|
952
|
+
temp = sp.array(temp,dtype=max(dtypes))
|
|
953
|
+
logger.info('Data type of '+prop+' differs between sub-objects...converting to larger data type')
|
|
954
|
+
return temp
|
|
955
|
+
|
|
956
|
+
def num_pores(self,labels='all',mode='union'):
|
|
957
|
+
r'''
|
|
958
|
+
Returns the number of pores of the specified labels
|
|
959
|
+
|
|
960
|
+
Parameters
|
|
961
|
+
----------
|
|
962
|
+
labels : list of strings, optional
|
|
963
|
+
The pore labels that should be included in the count.
|
|
964
|
+
If not supplied, all pores are counted.
|
|
965
|
+
labels : list of strings
|
|
966
|
+
Label of pores to be returned
|
|
967
|
+
mode : string, optional
|
|
968
|
+
Specifies how the count should be performed. The options are:
|
|
969
|
+
|
|
970
|
+
* 'union' : (default) All pores with ANY of the given labels are counted.
|
|
971
|
+
|
|
972
|
+
* 'intersection' : Only pores with ALL the given labels are counted.
|
|
973
|
+
|
|
974
|
+
* 'not_intersection' : Only pores with exactly one of the given labels are counted.
|
|
975
|
+
|
|
976
|
+
* 'difference' : Only pores with none of the given labels are counted.
|
|
977
|
+
|
|
978
|
+
Returns
|
|
979
|
+
-------
|
|
980
|
+
Np : int
|
|
981
|
+
Number of pores with the specified labels
|
|
982
|
+
|
|
983
|
+
See Also
|
|
984
|
+
--------
|
|
985
|
+
num_throats
|
|
986
|
+
count
|
|
987
|
+
|
|
988
|
+
Examples
|
|
989
|
+
--------
|
|
990
|
+
>>> import OpenPNM
|
|
991
|
+
>>> pn = OpenPNM.Network.TestNet()
|
|
992
|
+
>>> pn.num_pores()
|
|
993
|
+
125
|
|
994
|
+
>>> pn.num_pores(labels=['top'])
|
|
995
|
+
25
|
|
996
|
+
>>> pn.num_pores(labels=['top','front'],mode='union') #'union' is default
|
|
997
|
+
45
|
|
998
|
+
>>> pn.num_pores(labels=['top','front'],mode='intersection')
|
|
999
|
+
5
|
|
1000
|
+
>>> pn.num_pores(labels=['top','front'],mode='not_intersection')
|
|
1001
|
+
40
|
|
1002
|
+
|
|
1003
|
+
'''
|
|
1004
|
+
if labels == 'all':
|
|
1005
|
+
Np = sp.shape(self.get('pore.all'))[0]
|
|
1006
|
+
else:
|
|
1007
|
+
#convert string to list, if necessary
|
|
1008
|
+
if type(labels) == str:
|
|
1009
|
+
labels = [labels]
|
|
1010
|
+
#Count number of pores of specified type
|
|
1011
|
+
Ps = self.pores(labels=labels,mode=mode)
|
|
1012
|
+
Np = sp.shape(Ps)[0]
|
|
1013
|
+
return Np
|
|
1014
|
+
|
|
1015
|
+
@property
|
|
1016
|
+
def Np(self):
|
|
1017
|
+
r'''
|
|
1018
|
+
A shortcut to query the total number of pores on the object'
|
|
1019
|
+
'''
|
|
1020
|
+
return self.num_pores()
|
|
1021
|
+
|
|
1022
|
+
def num_throats(self,labels='all',mode='union'):
|
|
1023
|
+
r'''
|
|
1024
|
+
Return the number of throats of the specified labels
|
|
1025
|
+
|
|
1026
|
+
Parameters
|
|
1027
|
+
----------
|
|
1028
|
+
labels : list of strings, optional
|
|
1029
|
+
The throat labels that should be included in the count.
|
|
1030
|
+
If not supplied, all throats are counted.
|
|
1031
|
+
mode : string, optional
|
|
1032
|
+
Specifies how the count should be performed. The options are:
|
|
1033
|
+
|
|
1034
|
+
* 'union' : (default) All throats with ANY of the given labels are counted.
|
|
1035
|
+
|
|
1036
|
+
* 'intersection' : Only throats with ALL the given labels are counted.
|
|
1037
|
+
|
|
1038
|
+
* 'not_intersection' : Only throats with exactly one of the given labels are counted.
|
|
1039
|
+
|
|
1040
|
+
* 'difference' : Only throats with none of the given labels are counted.
|
|
1041
|
+
|
|
1042
|
+
Returns
|
|
1043
|
+
-------
|
|
1044
|
+
Nt : int
|
|
1045
|
+
Number of throats with the specified labels
|
|
1046
|
+
|
|
1047
|
+
See Also
|
|
1048
|
+
--------
|
|
1049
|
+
num_pores
|
|
1050
|
+
count
|
|
1051
|
+
|
|
1052
|
+
Examples
|
|
1053
|
+
--------
|
|
1054
|
+
>>> import OpenPNM
|
|
1055
|
+
>>> pn = OpenPNM.Network.TestNet()
|
|
1056
|
+
>>> pn.num_throats()
|
|
1057
|
+
300
|
|
1058
|
+
>>> pn.num_throats(labels=['top'])
|
|
1059
|
+
40
|
|
1060
|
+
>>> pn.num_throats(labels=['top','front'],mode='union') #'union' is default
|
|
1061
|
+
76
|
|
1062
|
+
>>> pn.num_throats(labels=['top','front'],mode='intersection')
|
|
1063
|
+
4
|
|
1064
|
+
>>> pn.num_throats(labels=['top','front'],mode='not_intersection')
|
|
1065
|
+
72
|
|
1066
|
+
|
|
1067
|
+
'''
|
|
1068
|
+
if labels == 'all':
|
|
1069
|
+
Nt = sp.shape(self.get('throat.all'))[0]
|
|
1070
|
+
else:
|
|
1071
|
+
#convert string to list, if necessary
|
|
1072
|
+
if type(labels) == str: labels = [labels]
|
|
1073
|
+
#Count number of pores of specified type
|
|
1074
|
+
Ts = self.throats(labels=labels,mode=mode)
|
|
1075
|
+
Nt = sp.shape(Ts)[0]
|
|
1076
|
+
return Nt
|
|
1077
|
+
|
|
1078
|
+
@property
|
|
1079
|
+
def Nt(self):
|
|
1080
|
+
r'''
|
|
1081
|
+
A shortcut to query the total number of throats on the object'
|
|
1082
|
+
'''
|
|
1083
|
+
return self.num_throats()
|
|
1084
|
+
|
|
1085
|
+
def _count(self,element=None):
|
|
1086
|
+
r'''
|
|
1087
|
+
Returns a dictionary containing the number of pores and throats in
|
|
1088
|
+
the network, stored under the keys 'pore' or 'throat'
|
|
1089
|
+
|
|
1090
|
+
Parameters
|
|
1091
|
+
----------
|
|
1092
|
+
element : string, optional
|
|
1093
|
+
Can be either 'pore' , 'pores', 'throat' or 'throats', which
|
|
1094
|
+
specifies which count to return.
|
|
1095
|
+
|
|
1096
|
+
Returns
|
|
1097
|
+
-------
|
|
1098
|
+
A dictionary containing the number of pores and throats under the
|
|
1099
|
+
'pore' and 'throat' key respectively.
|
|
1100
|
+
|
|
1101
|
+
See Also
|
|
1102
|
+
--------
|
|
1103
|
+
num_pores
|
|
1104
|
+
num_throats
|
|
1105
|
+
|
|
1106
|
+
Notes
|
|
1107
|
+
-----
|
|
1108
|
+
The ability to send plurals is useful for some types of 'programmatic'
|
|
1109
|
+
access. For instance, the standard argument for locations is pores
|
|
1110
|
+
or throats. If these are bundled up in a **kwargs dict then you can
|
|
1111
|
+
just use the dict key in count() without removing the 's'.
|
|
1112
|
+
|
|
1113
|
+
Examples
|
|
1114
|
+
--------
|
|
1115
|
+
>>> import OpenPNM
|
|
1116
|
+
>>> pn = OpenPNM.Network.TestNet()
|
|
1117
|
+
>>> pn._count('pore')
|
|
1118
|
+
125
|
|
1119
|
+
>>> pn._count('throat')
|
|
1120
|
+
300
|
|
1121
|
+
'''
|
|
1122
|
+
if element in ['pore','pores']:
|
|
1123
|
+
temp = self.num_pores()
|
|
1124
|
+
elif element in ['throat','throats']:
|
|
1125
|
+
temp = self.num_throats()
|
|
1126
|
+
elif element is None:
|
|
1127
|
+
temp = {}
|
|
1128
|
+
temp['pore'] = self.num_pores()
|
|
1129
|
+
temp['throat'] = self.num_throats()
|
|
1130
|
+
return temp
|
|
1131
|
+
|
|
1132
|
+
def _set_locations(self,element,locations,mode='add'):
|
|
1133
|
+
r'''
|
|
1134
|
+
Private method used for assigning Geometry and Physics objects to
|
|
1135
|
+
specified locations
|
|
1136
|
+
|
|
1137
|
+
Parameters
|
|
1138
|
+
----------
|
|
1139
|
+
element : string
|
|
1140
|
+
Either 'pore' or 'throat' indicating which type of element is being
|
|
1141
|
+
work upon
|
|
1142
|
+
locations : array_like
|
|
1143
|
+
The pore or throat locations in terms of Network numbering to add
|
|
1144
|
+
(or remove) from the object
|
|
1145
|
+
mode : string
|
|
1146
|
+
Either 'add' or 'remove', the default is add.
|
|
1147
|
+
|
|
1148
|
+
Examples
|
|
1149
|
+
--------
|
|
1150
|
+
>>> import OpenPNM
|
|
1151
|
+
>>> pn = OpenPNM.Network.TestNet()
|
|
1152
|
+
>>> pn.Np
|
|
1153
|
+
125
|
|
1154
|
+
>>> geom = OpenPNM.Geometry.GenericGeometry(network=pn,pores=sp.arange(5,125),throats=pn.Ts)
|
|
1155
|
+
>>> [geom.Np, geom.Nt]
|
|
1156
|
+
[120, 300]
|
|
1157
|
+
>>> geom['pore.dummy'] = True
|
|
1158
|
+
>>> health = pn.check_geometry_health()
|
|
1159
|
+
>>> pores = health['undefined_pores']
|
|
1160
|
+
>>> geom.set_locations(pores=pores)
|
|
1161
|
+
>>> [geom.Np, geom.Nt]
|
|
1162
|
+
[125, 300]
|
|
1163
|
+
>>> geom.pores(labels='dummy',mode='not') # Dummy as assigned BEFORE these pores were added
|
|
1164
|
+
array([0, 1, 2, 3, 4])
|
|
1165
|
+
>>> geom.set_locations(pores=pores,mode='remove')
|
|
1166
|
+
>>> [geom.Np, geom.Nt]
|
|
1167
|
+
[125, 300]
|
|
1168
|
+
>>> geom.num_pores(labels='dummy',mode='not') # All pores without 'dummy' label are gone
|
|
1169
|
+
0
|
|
1170
|
+
'''
|
|
1171
|
+
net = self._net
|
|
1172
|
+
if self._isa('Geometry'):
|
|
1173
|
+
boss_obj = self._net
|
|
1174
|
+
co_objs = boss_obj.geometries()
|
|
1175
|
+
elif self._isa('Physics'):
|
|
1176
|
+
boss_obj = self._phases[0]
|
|
1177
|
+
co_objs = boss_obj.physics()
|
|
1178
|
+
else:
|
|
1179
|
+
logger.warning('Setting locations only applies to Geometry or Physics objects')
|
|
1180
|
+
return
|
|
1181
|
+
|
|
1182
|
+
if mode == 'add':
|
|
1183
|
+
# Check if any constant values exist on the object
|
|
1184
|
+
for item in self.props():
|
|
1185
|
+
if (item not in self.models.keys()) or (self.models[item]['regen_mode'] == 'constant'):
|
|
1186
|
+
logger.critical('Constant values or models were found on object.'
|
|
1187
|
+
'These will be the wrong length after this operation, '
|
|
1188
|
+
'which will break the data integrity. '
|
|
1189
|
+
'Run network.check_data_health to investigate further.')
|
|
1190
|
+
# Ensure locations are not already assigned to another object
|
|
1191
|
+
temp = sp.zeros((net._count(element),),bool)
|
|
1192
|
+
for key in co_objs:
|
|
1193
|
+
temp += net[element+'.'+key]
|
|
1194
|
+
overlaps = sp.sum(temp*net._tomask(locations=locations,element=element))
|
|
1195
|
+
if overlaps > 0:
|
|
1196
|
+
raise Exception('Some of the given '+element+'s overlap with an existing object')
|
|
1197
|
+
|
|
1198
|
+
# Store original Network indices for later use
|
|
1199
|
+
old_inds = sp.copy(net[element+'.'+self.name])
|
|
1200
|
+
|
|
1201
|
+
# Create new 'all' label for new size
|
|
1202
|
+
new_len = self._count(element=element) + sp.size(locations)
|
|
1203
|
+
self.update({element+'.all': sp.ones((new_len,),dtype=bool)}) # Initialize new 'all' array
|
|
1204
|
+
|
|
1205
|
+
# Set locations in Network (and Phase) dictionary
|
|
1206
|
+
if element+'.'+self.name not in net.keys():
|
|
1207
|
+
net[element+'.'+self.name] = False
|
|
1208
|
+
net[element+'.'+self.name][locations] = True
|
|
1209
|
+
if element+'.'+self.name not in boss_obj.keys():
|
|
1210
|
+
boss_obj[element+'.'+self.name] = False
|
|
1211
|
+
boss_obj[element+'.'+self.name][locations] = True
|
|
1212
|
+
|
|
1213
|
+
# Increase size of labels (add False at new locations)
|
|
1214
|
+
blank = ~sp.copy(self[element+'.all'])
|
|
1215
|
+
labels = self.labels()
|
|
1216
|
+
labels.remove(element+'.all')
|
|
1217
|
+
for item in labels:
|
|
1218
|
+
if item.split('.')[0] == element:
|
|
1219
|
+
blank[old_inds] = self[item]
|
|
1220
|
+
self.update({item:blank[net[element+'.all']]})
|
|
1221
|
+
|
|
1222
|
+
if mode == 'remove':
|
|
1223
|
+
self_inds = boss_obj._map(element=element,locations=locations,target=self)
|
|
1224
|
+
keep = ~self._tomask(locations=self_inds,element=element)
|
|
1225
|
+
for item in self.keys():
|
|
1226
|
+
if item.split('.')[0] == element:
|
|
1227
|
+
temp = self[item][keep]
|
|
1228
|
+
self.update({item:temp})
|
|
1229
|
+
#Set locations in Network dictionary
|
|
1230
|
+
net[element+'.'+self.name][locations] = False
|
|
1231
|
+
boss_obj[element+'.'+self.name][locations] = False
|
|
1232
|
+
|
|
1233
|
+
# Finally, regenerate all models to correct the length of all prop array
|
|
1234
|
+
self.models.regenerate()
|
|
1235
|
+
|
|
1236
|
+
def _map(self,element,locations,target,return_mapping=False):
|
|
1237
|
+
r'''
|
|
1238
|
+
'''
|
|
1239
|
+
# Initialize things
|
|
1240
|
+
locations = sp.array(locations,ndmin=1)
|
|
1241
|
+
mapping = {}
|
|
1242
|
+
|
|
1243
|
+
# Analyze input object's relationship
|
|
1244
|
+
if self._net == target._net: # Objects are siblings...easy
|
|
1245
|
+
maskS = self._net[element+'.'+self.name]
|
|
1246
|
+
maskT = target._net[element+'.'+target.name]
|
|
1247
|
+
else: # One or more of the objects is a clone
|
|
1248
|
+
if self._parent is None: # Self is parent object
|
|
1249
|
+
maskS = self._net[element+'.'+self.name]
|
|
1250
|
+
maskT = ~self._net[element+'.all']
|
|
1251
|
+
tempT = target._net[element+'.'+target.name]
|
|
1252
|
+
inds = target._net[element+'.'+self._net.name][tempT]
|
|
1253
|
+
maskT[inds] = True
|
|
1254
|
+
if target._parent is None: # Target is parent object
|
|
1255
|
+
maskT = target._net[element+'.'+target.name]
|
|
1256
|
+
maskS = ~target._net[element+'.all']
|
|
1257
|
+
tempS = self._net[element+'.'+self.name]
|
|
1258
|
+
inds = self._net[element+'.'+target._net.name][tempS]
|
|
1259
|
+
maskS[inds] = True
|
|
1260
|
+
|
|
1261
|
+
# Convert source locations to Network indices
|
|
1262
|
+
temp = sp.zeros(sp.shape(maskS),dtype=int)-1
|
|
1263
|
+
temp[maskS] = self._get_indices(element=element)
|
|
1264
|
+
locsS = sp.where(sp.in1d(temp,locations))[0]
|
|
1265
|
+
mapping['source'] = locations
|
|
1266
|
+
|
|
1267
|
+
# Find locations in target
|
|
1268
|
+
temp = sp.zeros(sp.shape(maskT),dtype=int)-1
|
|
1269
|
+
temp[maskT] = target._get_indices(element=element)
|
|
1270
|
+
locsT = temp[locsS]
|
|
1271
|
+
mapping['target'] = locsT
|
|
1272
|
+
|
|
1273
|
+
# Find overlapping locations in source and target to define mapping
|
|
1274
|
+
keep = (locsS>=0)*(locsT>=0)
|
|
1275
|
+
mapping['source'] = mapping['source'][keep]
|
|
1276
|
+
mapping['target'] = mapping['target'][keep]
|
|
1277
|
+
|
|
1278
|
+
# Return results as an arrary or one-to-one mapping if requested
|
|
1279
|
+
if return_mapping == True:
|
|
1280
|
+
return mapping
|
|
1281
|
+
else:
|
|
1282
|
+
if sp.sum(locsS>=0) < sp.shape(sp.unique(locations))[0]:
|
|
1283
|
+
raise Exception('Some locations not found on Source object')
|
|
1284
|
+
if sp.sum(locsT>=0) < sp.shape(sp.unique(locations))[0]:
|
|
1285
|
+
raise Exception('Some locations not found on Target object')
|
|
1286
|
+
return mapping['target']
|
|
1287
|
+
|
|
1288
|
+
def map_pores(self,target=None,pores=None,return_mapping=False):
|
|
1289
|
+
r'''
|
|
1290
|
+
Accepts a list of pores from the caller object and maps them onto the
|
|
1291
|
+
given target object
|
|
1292
|
+
|
|
1293
|
+
Parameters
|
|
1294
|
+
----------
|
|
1295
|
+
pores : array_like
|
|
1296
|
+
The list of pores on the caller object. If no pores are supplied
|
|
1297
|
+
then all the pores of the calling object are used.
|
|
1298
|
+
|
|
1299
|
+
target : OpenPNM object, optional
|
|
1300
|
+
The object for which a list of pores is desired. If no object is
|
|
1301
|
+
supplied then the object's associated Network is used.
|
|
1302
|
+
|
|
1303
|
+
return_mapping : boolean (default is False)
|
|
1304
|
+
If True, a dictionary containing 'source' locations, and 'target'
|
|
1305
|
+
locations is returned. Any 'source' locations not found in the
|
|
1306
|
+
'target' object are removed from the list.
|
|
1307
|
+
|
|
1308
|
+
Returns
|
|
1309
|
+
-------
|
|
1310
|
+
pores : array_like
|
|
1311
|
+
A list of pores mapped onto the target object
|
|
1312
|
+
|
|
1313
|
+
Examples
|
|
1314
|
+
--------
|
|
1315
|
+
>>> import OpenPNM
|
|
1316
|
+
>>> pn = OpenPNM.Network.TestNet()
|
|
1317
|
+
>>> Ps = pn.pores(labels=['top','left'],mode='intersection')
|
|
1318
|
+
>>> Ps
|
|
1319
|
+
array([100, 101, 102, 103, 104])
|
|
1320
|
+
>>> geom = OpenPNM.Geometry.GenericGeometry(network=pn,pores=Ps)
|
|
1321
|
+
>>> geom.Ps
|
|
1322
|
+
array([0, 1, 2, 3, 4])
|
|
1323
|
+
>>> geom.map_pores(target=pn,pores=geom.Ps)
|
|
1324
|
+
array([100, 101, 102, 103, 104])
|
|
1325
|
+
>>> pn.map_pores(target=geom,pores=Ps)
|
|
1326
|
+
array([0, 1, 2, 3, 4])
|
|
1327
|
+
'''
|
|
1328
|
+
if pores is None:
|
|
1329
|
+
pores = self.Ps
|
|
1330
|
+
if target is None:
|
|
1331
|
+
if self._net is None:
|
|
1332
|
+
target = self
|
|
1333
|
+
else:
|
|
1334
|
+
target = self._net
|
|
1335
|
+
Ps = self._map(element='pore',locations=pores,target=target,return_mapping=return_mapping)
|
|
1336
|
+
return Ps
|
|
1337
|
+
|
|
1338
|
+
def map_throats(self,target=None,throats=None,return_mapping=False):
|
|
1339
|
+
r'''
|
|
1340
|
+
Accepts a list of throats from the caller object and maps them onto the
|
|
1341
|
+
given target object
|
|
1342
|
+
|
|
1343
|
+
Parameters
|
|
1344
|
+
----------
|
|
1345
|
+
throats : array_like
|
|
1346
|
+
The list of throats on the caller object. If no throats are
|
|
1347
|
+
supplied then all the throats of the calling object are used.
|
|
1348
|
+
|
|
1349
|
+
target : OpenPNM object, optional
|
|
1350
|
+
The object for which a list of pores is desired. If no object is
|
|
1351
|
+
supplied then the object's associated Network is used.
|
|
1352
|
+
|
|
1353
|
+
return_mapping : boolean (default is False)
|
|
1354
|
+
If True, a dictionary containing 'source' locations, and 'target'
|
|
1355
|
+
locations is returned. Any 'source' locations not found in the
|
|
1356
|
+
'target' object are removed from the list.
|
|
1357
|
+
|
|
1358
|
+
Returns
|
|
1359
|
+
-------
|
|
1360
|
+
throats : array_like
|
|
1361
|
+
A list of throats mapped onto the target object
|
|
1362
|
+
|
|
1363
|
+
Examples
|
|
1364
|
+
--------
|
|
1365
|
+
>>> import OpenPNM
|
|
1366
|
+
>>> pn = OpenPNM.Network.TestNet()
|
|
1367
|
+
>>> Ts = pn.throats(labels=['top','left'],mode='intersection')
|
|
1368
|
+
>>> Ts
|
|
1369
|
+
array([260, 262, 264, 266])
|
|
1370
|
+
>>> geom = OpenPNM.Geometry.GenericGeometry(network=pn,throats=Ts)
|
|
1371
|
+
>>> geom.Ts
|
|
1372
|
+
array([0, 1, 2, 3])
|
|
1373
|
+
>>> geom.map_throats(target=pn,throats=geom.Ts)
|
|
1374
|
+
array([260, 262, 264, 266])
|
|
1375
|
+
>>> pn.map_throats(target=geom,throats=Ts)
|
|
1376
|
+
array([0, 1, 2, 3])
|
|
1377
|
+
'''
|
|
1378
|
+
if throats is None:
|
|
1379
|
+
throats = self.Ts
|
|
1380
|
+
if target is None:
|
|
1381
|
+
if self._net is None:
|
|
1382
|
+
target = self
|
|
1383
|
+
else:
|
|
1384
|
+
target = self._net
|
|
1385
|
+
Ts = self._map(element='throat',locations=throats,target=target,return_mapping=return_mapping)
|
|
1386
|
+
return Ts
|
|
1387
|
+
|
|
1388
|
+
Tnet = property(fget=map_throats)
|
|
1389
|
+
Pnet = property(fget=map_pores)
|
|
1390
|
+
|
|
1391
|
+
def _isa(self,keyword=None,obj=None):
|
|
1392
|
+
r'''
|
|
1393
|
+
'''
|
|
1394
|
+
if keyword is None:
|
|
1395
|
+
mro = [item.__name__ for item in self.__class__.__mro__]
|
|
1396
|
+
if obj is None:
|
|
1397
|
+
query = False
|
|
1398
|
+
mro = [item.__name__ for item in self.__class__.__mro__]
|
|
1399
|
+
if keyword in ['net','Network','GenericNetwork']:
|
|
1400
|
+
if 'GenericNetwork' in mro:
|
|
1401
|
+
query = True
|
|
1402
|
+
elif keyword in ['geom','Geometry','GenericGeometry']:
|
|
1403
|
+
if 'GenericGeometry' in mro:
|
|
1404
|
+
query = True
|
|
1405
|
+
elif keyword in ['phase','Phase','GenericPhase']:
|
|
1406
|
+
if 'GenericPhase' in mro:
|
|
1407
|
+
query = True
|
|
1408
|
+
elif keyword in ['phys','Physics','GenericPhysics']:
|
|
1409
|
+
if 'GenericPhysics' in mro:
|
|
1410
|
+
query = True
|
|
1411
|
+
elif keyword in ['alg','Algorithm','GenericAlgorithm']:
|
|
1412
|
+
if 'GenericAlgorithm' in mro:
|
|
1413
|
+
query = True
|
|
1414
|
+
elif keyword in ['clone']:
|
|
1415
|
+
if self._net is None:
|
|
1416
|
+
if self._parent is not None:
|
|
1417
|
+
query = True
|
|
1418
|
+
else:
|
|
1419
|
+
if self._net._parent is not None:
|
|
1420
|
+
query = True
|
|
1421
|
+
return query
|
|
1422
|
+
else:
|
|
1423
|
+
query = False
|
|
1424
|
+
if keyword in ['sibling']:
|
|
1425
|
+
if (self._isa('net')) and (obj._net is self):
|
|
1426
|
+
query = True
|
|
1427
|
+
elif (obj._isa('net')) and (self._net is obj):
|
|
1428
|
+
query = True
|
|
1429
|
+
elif self._net is obj._net:
|
|
1430
|
+
query = True
|
|
1431
|
+
return query
|
|
1432
|
+
|
|
1433
|
+
def check_data_health(self,props=[],element=''):
|
|
1434
|
+
r'''
|
|
1435
|
+
Check the health of pore and throat data arrays.
|
|
1436
|
+
|
|
1437
|
+
Parameters
|
|
1438
|
+
----------
|
|
1439
|
+
element : string, optional
|
|
1440
|
+
Can be either 'pore' or 'throat', which will limit the checks to
|
|
1441
|
+
only those data arrays.
|
|
1442
|
+
props : list of pore (or throat) properties, optional
|
|
1443
|
+
If given, will limit the health checks to only the specfied
|
|
1444
|
+
properties. Also useful for checking existance.
|
|
1445
|
+
|
|
1446
|
+
Returns
|
|
1447
|
+
-------
|
|
1448
|
+
Returns a HealthDict object which a basic dictionary with an added
|
|
1449
|
+
``health`` attribute that is True is all entries in the dict are
|
|
1450
|
+
deemed healthy (empty lists), or False otherwise.
|
|
1451
|
+
|
|
1452
|
+
Examples
|
|
1453
|
+
--------
|
|
1454
|
+
>>> import OpenPNM
|
|
1455
|
+
>>> pn = OpenPNM.Network.TestNet()
|
|
1456
|
+
>>> health = pn.check_data_health()
|
|
1457
|
+
>>> print(health)
|
|
1458
|
+
------------------------------------------------------------
|
|
1459
|
+
key value
|
|
1460
|
+
------------------------------------------------------------
|
|
1461
|
+
throat.conns []
|
|
1462
|
+
pore.coords []
|
|
1463
|
+
------------------------------------------------------------
|
|
1464
|
+
>>> a.health
|
|
1465
|
+
True
|
|
1466
|
+
'''
|
|
1467
|
+
health = Tools.HealthDict()
|
|
1468
|
+
if props == []:
|
|
1469
|
+
props = self.props(element)
|
|
1470
|
+
else:
|
|
1471
|
+
if type(props) == str:
|
|
1472
|
+
props = [props]
|
|
1473
|
+
for item in props:
|
|
1474
|
+
health[item] = []
|
|
1475
|
+
try:
|
|
1476
|
+
if sp.sum(sp.isnan(self[item])) > 0:
|
|
1477
|
+
health[item] = 'Has NaNs'
|
|
1478
|
+
elif sp.shape(self[item])[0] != self._count(item.split('.')[0]):
|
|
1479
|
+
health[item] = 'Wrong Length'
|
|
1480
|
+
except:
|
|
1481
|
+
health[item] = 'Does not exist'
|
|
1482
|
+
return health
|
|
1483
|
+
|
|
1484
|
+
def __str__(self):
|
|
1485
|
+
header = '-'*60
|
|
1486
|
+
print(header)
|
|
1487
|
+
print(self.__module__.replace('__','')+': \t',self.name)
|
|
1488
|
+
print(header)
|
|
1489
|
+
print("{a:<5s} {b:<35s} {c:<10s}".format(a='#', b='Properties', c='Valid Values'))
|
|
1490
|
+
print(header)
|
|
1491
|
+
count = 0
|
|
1492
|
+
props = self.props()
|
|
1493
|
+
props.sort()
|
|
1494
|
+
for item in props:
|
|
1495
|
+
if self[item].dtype != object:
|
|
1496
|
+
count = count + 1
|
|
1497
|
+
prop=item
|
|
1498
|
+
if len(prop)>35:
|
|
1499
|
+
prop = prop[0:32]+'...'
|
|
1500
|
+
required = self._count(item.split('.')[0])
|
|
1501
|
+
a = sp.isnan(self[item])
|
|
1502
|
+
defined = sp.shape(self[item])[0] - a.sum(axis=0,keepdims=(a.ndim-1)==0)[0]
|
|
1503
|
+
print("{a:<5d} {b:<35s} {c:>5d} / {d:<5d}".format(a=count, b=prop, c=defined, d=required))
|
|
1504
|
+
print(header)
|
|
1505
|
+
print("{a:<5s} {b:<35s} {c:<10s}".format(a='#', b='Labels', c='Assigned Locations'))
|
|
1506
|
+
print(header)
|
|
1507
|
+
count = 0
|
|
1508
|
+
labels = self.labels()
|
|
1509
|
+
labels.sort()
|
|
1510
|
+
for item in labels:
|
|
1511
|
+
count = count + 1
|
|
1512
|
+
prop=item
|
|
1513
|
+
if len(prop)>35:
|
|
1514
|
+
prop = prop[0:32]+'...'
|
|
1515
|
+
print("{a:<5d} {b:<35s} {c:<10d}".format(a=count, b=prop, c=sp.sum(self[item])))
|
|
1516
|
+
print(header)
|
|
1517
|
+
return ''
|
|
1518
|
+
|
|
1519
|
+
if __name__ == '__main__':
|
|
1520
|
+
import doctest
|
|
1521
|
+
doctest.testmod(verbose=False)
|
|
1522
|
+
|