openpnm 1.0.0__zip

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (108) hide show
  1. OpenPNM-1.1/MANIFEST.in +2 -0
  2. OpenPNM-1.1/OpenPNM/Algorithms/__FickianDiffusion__.py +67 -0
  3. OpenPNM-1.1/OpenPNM/Algorithms/__FourierConduction__.py +63 -0
  4. OpenPNM-1.1/OpenPNM/Algorithms/__GenericAlgorithm__.py +235 -0
  5. OpenPNM-1.1/OpenPNM/Algorithms/__GenericLinearTransport__.py +641 -0
  6. OpenPNM-1.1/OpenPNM/Algorithms/__InvasionPercolationForImbibition__.py +703 -0
  7. OpenPNM-1.1/OpenPNM/Algorithms/__InvasionPercolationTimed__.py +702 -0
  8. OpenPNM-1.1/OpenPNM/Algorithms/__InvasionPercolation__.py +156 -0
  9. OpenPNM-1.1/OpenPNM/Algorithms/__OhmicConduction__.py +64 -0
  10. OpenPNM-1.1/OpenPNM/Algorithms/__OrdinaryPercolation__.py +402 -0
  11. OpenPNM-1.1/OpenPNM/Algorithms/__StokesFlow__.py +64 -0
  12. OpenPNM-1.1/OpenPNM/Algorithms/__Tortuosity__.py +91 -0
  13. OpenPNM-1.1/OpenPNM/Algorithms/__init__.py +48 -0
  14. OpenPNM-1.1/OpenPNM/Base/__Controller__.py +480 -0
  15. OpenPNM-1.1/OpenPNM/Base/__Core__.py +1522 -0
  16. OpenPNM-1.1/OpenPNM/Base/__ModelsDict__.py +345 -0
  17. OpenPNM-1.1/OpenPNM/Base/__Tools__.py +72 -0
  18. OpenPNM-1.1/OpenPNM/Base/__init__.py +32 -0
  19. OpenPNM-1.1/OpenPNM/Geometry/__Boundary__.py +80 -0
  20. OpenPNM-1.1/OpenPNM/Geometry/__Cube_and_Cuboid__.py +64 -0
  21. OpenPNM-1.1/OpenPNM/Geometry/__GenericGeometry__.py +106 -0
  22. OpenPNM-1.1/OpenPNM/Geometry/__SGL10__.py +67 -0
  23. OpenPNM-1.1/OpenPNM/Geometry/__Stick_and_Ball__.py +68 -0
  24. OpenPNM-1.1/OpenPNM/Geometry/__TestGeometry__.py +51 -0
  25. OpenPNM-1.1/OpenPNM/Geometry/__Toray090__.py +68 -0
  26. OpenPNM-1.1/OpenPNM/Geometry/__Voronoi__.py +98 -0
  27. OpenPNM-1.1/OpenPNM/Geometry/__init__.py +47 -0
  28. OpenPNM-1.1/OpenPNM/Geometry/models/__init__.py +33 -0
  29. OpenPNM-1.1/OpenPNM/Geometry/models/pore_area.py +27 -0
  30. OpenPNM-1.1/OpenPNM/Geometry/models/pore_centroid.py +35 -0
  31. OpenPNM-1.1/OpenPNM/Geometry/models/pore_diameter.py +127 -0
  32. OpenPNM-1.1/OpenPNM/Geometry/models/pore_misc.py +55 -0
  33. OpenPNM-1.1/OpenPNM/Geometry/models/pore_seed.py +212 -0
  34. OpenPNM-1.1/OpenPNM/Geometry/models/pore_surface_area.py +28 -0
  35. OpenPNM-1.1/OpenPNM/Geometry/models/pore_vertices.py +19 -0
  36. OpenPNM-1.1/OpenPNM/Geometry/models/pore_volume.py +133 -0
  37. OpenPNM-1.1/OpenPNM/Geometry/models/throat_area.py +47 -0
  38. OpenPNM-1.1/OpenPNM/Geometry/models/throat_centroid.py +80 -0
  39. OpenPNM-1.1/OpenPNM/Geometry/models/throat_diameter.py +106 -0
  40. OpenPNM-1.1/OpenPNM/Geometry/models/throat_length.py +95 -0
  41. OpenPNM-1.1/OpenPNM/Geometry/models/throat_misc.py +42 -0
  42. OpenPNM-1.1/OpenPNM/Geometry/models/throat_normal.py +31 -0
  43. OpenPNM-1.1/OpenPNM/Geometry/models/throat_offset_vertices.py +191 -0
  44. OpenPNM-1.1/OpenPNM/Geometry/models/throat_perimeter.py +26 -0
  45. OpenPNM-1.1/OpenPNM/Geometry/models/throat_seed.py +12 -0
  46. OpenPNM-1.1/OpenPNM/Geometry/models/throat_shape_factor.py +37 -0
  47. OpenPNM-1.1/OpenPNM/Geometry/models/throat_surface_area.py +44 -0
  48. OpenPNM-1.1/OpenPNM/Geometry/models/throat_vector.py +27 -0
  49. OpenPNM-1.1/OpenPNM/Geometry/models/throat_vertices.py +19 -0
  50. OpenPNM-1.1/OpenPNM/Geometry/models/throat_volume.py +45 -0
  51. OpenPNM-1.1/OpenPNM/Network/__Cubic__.py +316 -0
  52. OpenPNM-1.1/OpenPNM/Network/__DelaunayCubic__.py +127 -0
  53. OpenPNM-1.1/OpenPNM/Network/__Delaunay__.py +600 -0
  54. OpenPNM-1.1/OpenPNM/Network/__GenericNetwork__.py +1184 -0
  55. OpenPNM-1.1/OpenPNM/Network/__MatFile__.py +331 -0
  56. OpenPNM-1.1/OpenPNM/Network/__TestNet__.py +109 -0
  57. OpenPNM-1.1/OpenPNM/Network/__init__.py +40 -0
  58. OpenPNM-1.1/OpenPNM/Network/models/__init__.py +12 -0
  59. OpenPNM-1.1/OpenPNM/Network/models/pore_topology.py +106 -0
  60. OpenPNM-1.1/OpenPNM/Phases/__Air__.py +63 -0
  61. OpenPNM-1.1/OpenPNM/Phases/__GenericPhase__.py +146 -0
  62. OpenPNM-1.1/OpenPNM/Phases/__Mercury__.py +71 -0
  63. OpenPNM-1.1/OpenPNM/Phases/__TestPhase__.py +46 -0
  64. OpenPNM-1.1/OpenPNM/Phases/__Water__.py +56 -0
  65. OpenPNM-1.1/OpenPNM/Phases/__init__.py +38 -0
  66. OpenPNM-1.1/OpenPNM/Phases/models/__init__.py +22 -0
  67. OpenPNM-1.1/OpenPNM/Phases/models/contact_angle.py +34 -0
  68. OpenPNM-1.1/OpenPNM/Phases/models/density.py +81 -0
  69. OpenPNM-1.1/OpenPNM/Phases/models/diffusivity.py +95 -0
  70. OpenPNM-1.1/OpenPNM/Phases/models/electrical_conductivity.py +10 -0
  71. OpenPNM-1.1/OpenPNM/Phases/models/misc.py +125 -0
  72. OpenPNM-1.1/OpenPNM/Phases/models/molar_density.py +69 -0
  73. OpenPNM-1.1/OpenPNM/Phases/models/molar_mass.py +31 -0
  74. OpenPNM-1.1/OpenPNM/Phases/models/surface_tension.py +104 -0
  75. OpenPNM-1.1/OpenPNM/Phases/models/thermal_conductivity.py +98 -0
  76. OpenPNM-1.1/OpenPNM/Phases/models/vapor_pressure.py +69 -0
  77. OpenPNM-1.1/OpenPNM/Phases/models/viscosity.py +103 -0
  78. OpenPNM-1.1/OpenPNM/Physics/__GenericPhysics__.py +111 -0
  79. OpenPNM-1.1/OpenPNM/Physics/__Standard__.py +51 -0
  80. OpenPNM-1.1/OpenPNM/Physics/__TestPhysics__.py +50 -0
  81. OpenPNM-1.1/OpenPNM/Physics/__init__.py +30 -0
  82. OpenPNM-1.1/OpenPNM/Physics/models/__init__.py +18 -0
  83. OpenPNM-1.1/OpenPNM/Physics/models/capillary_pressure.py +122 -0
  84. OpenPNM-1.1/OpenPNM/Physics/models/diffusive_conductance.py +82 -0
  85. OpenPNM-1.1/OpenPNM/Physics/models/electrical_conductance.py +59 -0
  86. OpenPNM-1.1/OpenPNM/Physics/models/generic_source_term.py +564 -0
  87. OpenPNM-1.1/OpenPNM/Physics/models/hydraulic_conductance.py +76 -0
  88. OpenPNM-1.1/OpenPNM/Physics/models/multiphase.py +133 -0
  89. OpenPNM-1.1/OpenPNM/Physics/models/thermal_conductance.py +67 -0
  90. OpenPNM-1.1/OpenPNM/Postprocessing/Graphics.py +251 -0
  91. OpenPNM-1.1/OpenPNM/Postprocessing/Plots.py +369 -0
  92. OpenPNM-1.1/OpenPNM/Postprocessing/__init__.py +10 -0
  93. OpenPNM-1.1/OpenPNM/Utilities/IO.py +277 -0
  94. OpenPNM-1.1/OpenPNM/Utilities/Shortcuts.py +17 -0
  95. OpenPNM-1.1/OpenPNM/Utilities/__init__.py +16 -0
  96. OpenPNM-1.1/OpenPNM/Utilities/misc.py +226 -0
  97. OpenPNM-1.1/OpenPNM/Utilities/transformations.py +1923 -0
  98. OpenPNM-1.1/OpenPNM/Utilities/vertexops.py +824 -0
  99. OpenPNM-1.1/OpenPNM/__init__.py +56 -0
  100. OpenPNM-1.1/OpenPNM.egg-info/PKG-INFO +11 -0
  101. OpenPNM-1.1/OpenPNM.egg-info/SOURCES.txt +107 -0
  102. OpenPNM-1.1/OpenPNM.egg-info/dependency_links.txt +1 -0
  103. OpenPNM-1.1/OpenPNM.egg-info/requires.txt +1 -0
  104. OpenPNM-1.1/OpenPNM.egg-info/top_level.txt +1 -0
  105. OpenPNM-1.1/PKG-INFO +11 -0
  106. OpenPNM-1.1/README.txt +88 -0
  107. OpenPNM-1.1/setup.cfg +7 -0
  108. OpenPNM-1.1/setup.py +39 -0
@@ -0,0 +1,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
+