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,702 @@
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ ===============================================================================
4
+ InvasionPercolationTimed -- Invasion Percolation with Timed Injection Rates
5
+ ===============================================================================
6
+
7
+ """
8
+ import scipy as sp
9
+ import numpy as np
10
+ import heapq
11
+ from OpenPNM.Utilities import misc
12
+ from OpenPNM.Algorithms import GenericAlgorithm
13
+ from OpenPNM.Base import logging
14
+ logger = logging.getLogger(__name__)
15
+
16
+
17
+ class InvasionPercolationTimed(GenericAlgorithm):
18
+ r"""
19
+ Invasion percolation with cluster growth timing
20
+
21
+ Parameters
22
+ ----------
23
+ network : Descendent of OpenPNM.Network.GenericNetwork
24
+ A valid network for this algorithm
25
+ name : string
26
+ The name this algorithm will go by
27
+
28
+ Examples
29
+ --------
30
+ >>> import OpenPNM
31
+ >>> pn = OpenPNM.Network.TestNet()
32
+ >>> geo = OpenPNM.Geometry.TestGeometry(network=pn,pores=pn.pores(),throats=pn.throats())
33
+ >>> phase1 = OpenPNM.Phases.TestPhase(network=pn)
34
+ >>> phase2 = OpenPNM.Phases.TestPhase(network=pn)
35
+ >>> phys1 = OpenPNM.Physics.TestPhysics(network=pn, phase=phase1,pores=pn.pores(),throats=pn.throats())
36
+ >>> phys2 = OpenPNM.Physics.TestPhysics(network=pn, phase=phase2,pores=pn.pores(),throats=pn.throats())
37
+ >>> IP = OpenPNM.Algorithms.InvasionPercolationTimed(network=pn)
38
+ >>> IP.run(invading_phase=phase1, defending_phase=phase2, inlets=pn.pores('top'), outlets=pn.pores('bottom'),report=0)
39
+ IP algorithm at 0 % completion at 0.0 seconds
40
+ IP algorithm at 100% completion at 0.0 seconds
41
+ >>> IP.return_results()
42
+ >>> max(phase1['pore.IP_inv_seq']) #unless something changed with our test objects, this should print "60"
43
+ 60
44
+
45
+ Suggested Improvements ::
46
+
47
+ a) Allow updating of cluster flow-rates (this will require a delta-t calculation at each step, instead of a total t calculation).
48
+ b) Allow for a non-linear relationship between pressure and throat-cap volume.
49
+
50
+
51
+ """
52
+ def __init__(self,**kwords):
53
+ r'''
54
+ '''
55
+ super(InvasionPercolationTimed,self).__init__(**kwords)
56
+ logger.info("Create IP Algorithm Object")
57
+
58
+ def run(self,invading_phase,
59
+ defending_phase,
60
+ inlets=[0],
61
+ outlets=[-1],
62
+ end_condition='breakthrough',
63
+ capillary_pressure='capillary_pressure',
64
+ pore_volume_name='volume',
65
+ throat_volume_name='volume',
66
+ throat_diameter_name='diameter',
67
+ timing='ON',
68
+ inlet_flow=1e-12, #default flowrate is 1 nanoliter/sec/cluster
69
+ report=20):
70
+ r"""
71
+ Runs the IP algorithm
72
+
73
+ Parameters
74
+ ----------
75
+ invading_phase : OpenPNM Phase Object
76
+ phase which will displace defending phase
77
+ defending_phase : OpenPNM Phase Object
78
+ phase which will be displaced by invading phase
79
+ inlets : list of integers (default: [0])
80
+ list of inlet nodes
81
+ outlets : list of integers (default: [-1])
82
+ list of outlet nodes
83
+ end_condition : string('breakthrough')
84
+ choice between 'breakthrough' and 'total'
85
+ capillary_pressure : string('capillary_pressure')
86
+ name given to throat capillary pressure property
87
+ pore_volume_name : string('volume')
88
+ name given to pore volume property
89
+ throat_diameter_name : string('diameter')
90
+ name given to throat diameter property
91
+ timing : string ('ON')
92
+ turns volume and flowrate calculations 'ON' or 'OFF'
93
+ inlet_flow : float (1)
94
+ m3/s for each cluster (affects timestamp of pore filling)
95
+ report : int (20)
96
+ percentage multiple at which a progress report is printed
97
+
98
+
99
+ Returns
100
+ -------
101
+ The algorithm will aquire the following pore data ::
102
+
103
+ invaded : True for invaded, False for uninvaded
104
+ defended : True for uninvaded, False for invaded
105
+ cluster_final : 0 for uninvaded, merged cluster number for invaded
106
+ cluster_original : 0 for uninvaded, original cluster number for invaded
107
+ inv_seq : 0 for uninvaded, simulation step for invaded
108
+ inv_time : 0 for uninvaded, simulation time for invaded
109
+ inv_sat : 0 for uninvaded, simulation saturation for invaded
110
+ inv_pres : 0 for uninvaded, simulation pressure for invaded
111
+
112
+ and throat data ::
113
+
114
+ invaded : True for invaded, False for uninvaded
115
+ defended : True for uninvaded, False for invaded
116
+ cluster_final : 0 for uninvaded, merged cluster number for invaded
117
+ inv_seq : 0 for uninvaded, simulation step for invaded
118
+ inv_time : 0 for uninvaded, simulation time for invaded
119
+ inv_sat : 0 for uninvaded, simulation saturation for invaded
120
+ inv_Pc : throat capillary pressures
121
+ inv_pres : 0 for uninvaded, simulation pressure for invaded
122
+
123
+ """
124
+
125
+ logger.info("\t end condition: "+end_condition)
126
+ self._inlets = inlets
127
+ self._outlets = outlets
128
+ if end_condition=='total':
129
+ self._brkevent = []
130
+ self._inlet_flow = inlet_flow
131
+ try: self._phase = self._net._phases[invading_phase]
132
+ except: self._phase = invading_phase
133
+ try: self._phase_def = self._net._phases[defending_phase]
134
+ except: self._phase_def = defending_phase
135
+
136
+ if sp.size(inlets) == 1:
137
+ self._inlets = [inlets]
138
+ if sp.size(outlets) == 1:
139
+ self._outlets = [outlets]
140
+ self._end_condition = end_condition
141
+ self._counter = 0
142
+ self._condition = 1
143
+ self._rough_increment = report
144
+ if report == 0:
145
+ self._rough_increment = 100
146
+ self._timing = timing=='ON'
147
+ self._capillary_pressure_name = capillary_pressure
148
+ self._pore_volume_name = pore_volume_name
149
+ self._throat_volume_name = throat_volume_name
150
+ self._throat_diameter_name = throat_diameter_name
151
+
152
+ super(InvasionPercolationTimed,self).run()
153
+
154
+ def _setup_for_IP(self):
155
+ r"""
156
+ Determines cluster labelling and condition for completion
157
+ """
158
+ self._clock_start = misc.tic()
159
+ logger.debug( '+='*25)
160
+ logger.debug( 'INITIAL SETUP (STEP 1)')
161
+ # if empty, add Pc_entry to throat_properties
162
+ tdia = self._net['throat.'+self._throat_diameter_name]
163
+ # calculate Pc_entry from diameters
164
+ try:
165
+ self['throat.inv_Pc'] = self._phase['throat.'+self._capillary_pressure_name]
166
+ except:
167
+ logger.error('Capillary pressure not assigned to invading phase '+self._phase.name
168
+ +', check for capillary pressure in defending phase '+self._phase_def.name +' instead')
169
+ try:
170
+ self['throat.inv_Pc'] = self._phase_def['throat.'+self._capillary_pressure_name]
171
+ self._phase['throat.'+self._capillary_pressure_name] = self._phase_def['throat.'+self._capillary_pressure_name]
172
+ except:
173
+ logger.error('Capillary pressure neither assigned to defending phase '+self._phase_def.name
174
+ +' nor to invading phase '+self._phase.name)
175
+ pass
176
+ if self._timing:
177
+ # calculate Volume_coef for each throat
178
+ self._Tvol_coef = tdia*tdia*tdia*np.pi/12/self['throat.inv_Pc']
179
+ # Creating an array for invaded Pores(Np long, 0 for uninvaded, cluster number for inaveded)
180
+ self['pore.cluster_final'] = 0
181
+ self['pore.cluster_original'] = 0
182
+ # Creating an array for invaded throats(Nt long, 0 for uninvaded, cluster number for inaveded)
183
+ self['throat.cluster_final'] = 0
184
+ # Creating arrays for tracking invaded Pores(Np long, 0 for uninvaded, sequence for inaveded)
185
+ self['pore.inv_seq'] =0
186
+ # Creating arrays for tracking invaded Pores(Np long, 0 for uninvaded, pressure for inaveded)
187
+ self['pore.inv_pres'] =0
188
+ if self._timing:
189
+ # Creating arrays for tracking invaded Pores(Np long, -1 for uninvaded, simulation time for inaveded)
190
+ self['pore.inv_time'] = -1.
191
+ # Creating arrays for tracking invaded throats(Nt long, 0 for uninvaded, sequence for inaveded)
192
+ self['throat.inv_seq'] = 0
193
+ # Creating arrays for tracking invaded throats(Nt long, 0 for uninvaded, pressure for inaveded)
194
+ self['throat.inv_pres'] = 0
195
+ if self._timing:
196
+ # Creating arrays for tracking invaded Pores(Np long, -1 for uninvaded, simulation time for inaveded)
197
+ self['throat.inv_time'] = -1.
198
+ # Iterator variables for sequences and cluster numbers
199
+ clusterNumber = 1
200
+ # Determine how many clusters there are
201
+ self._clusterCount = 0
202
+ for i in self._inlets:
203
+ self._clusterCount += 1
204
+ # Storage for cluster information
205
+ self._cluster_data = {}
206
+ if self._timing:
207
+ self._cluster_data['flow_rate'] = np.ones((self._clusterCount),dtype=float)*self._inlet_flow
208
+ self._cluster_data['haines_pressure'] = np.zeros((self._clusterCount),dtype=float)
209
+ self._cluster_data['haines_time'] = np.zeros((self._clusterCount),dtype=float)
210
+ self._cluster_data['vol_coef'] = np.zeros((self._clusterCount),dtype=float)
211
+ self._cluster_data['cap_volume'] = np.zeros((self._clusterCount),dtype=float)
212
+ self._cluster_data['pore_volume'] = np.zeros((self._clusterCount),dtype=float)
213
+ self._cluster_data['throat_volume'] = np.zeros((self._clusterCount),dtype=float)
214
+ self._cluster_data['haines_throat'] = np.zeros((self._clusterCount),dtype=int)
215
+ self._cluster_data['active'] = np.ones((self._clusterCount),dtype=int)
216
+ self._cluster_data['transform'] = np.zeros((self._clusterCount),dtype=int)
217
+ for i in range(self._clusterCount):
218
+ self._cluster_data['transform'][i] = i+1
219
+ # Creating an empty list to store the list of potential throats for invasion in each cluster.
220
+ # its length is equal to the maximum number of possible clusters.
221
+ self._tlists = [[] for i in self._inlets]
222
+ # Creating a list for each cluster to store both potential throat and corresponding throat value
223
+ self._tpoints = [[] for i in self._inlets]
224
+ # Initializing invasion percolation for each possible cluster
225
+ self._pore_volumes = self._net['pore.'+self._pore_volume_name]
226
+ self._throat_volumes = self._net['throat.'+self._throat_volume_name]
227
+ for pores in self._inlets:
228
+ if sp.shape(pores) == ():
229
+ pores = [pores]
230
+ # Label all invaded pores with their cluster
231
+ self['pore.cluster_original'][pores] = clusterNumber
232
+ # Label all inlet pores as invaded
233
+ self['pore.inv_seq'][pores] = self._tseq
234
+ self['pore.inv_pres'][pores] = 0
235
+ if self._timing:
236
+ self['pore.inv_time'][pores] = self._sim_time
237
+ # Find all throats that border invaded pores
238
+ interface_throat_numbers = self._net.find_neighbor_throats(pores)
239
+ self.cluster_update(clusterNumber,pores,[],interface_throat_numbers)
240
+ clusterNumber += 1
241
+ if self._timing:
242
+ logger.debug( 'pore volumes')
243
+ logger.debug(self._cluster_data['pore_volume'])
244
+ logger.debug( 'cap volumes')
245
+ logger.debug( self._cluster_data['cap_volume'])
246
+ pass
247
+ logger.debug( 'haines_throats')
248
+ logger.debug( self._cluster_data['haines_throat'])
249
+ self._tseq += 1
250
+ self._pseq += 1
251
+ self._current_cluster = 0
252
+ # Calculate the distance between the inlet and outlet pores
253
+ self._outlet_position = np.average(self._net['pore.coords'][self._outlets],0)
254
+ if any([sp.shape(i) > () for i in self._inlets]):
255
+ inlets = []
256
+ for i in self._inlets:
257
+ inlets = sp.union1d(inlets,i)
258
+ inlets = sp.array(inlets,int)
259
+ else:
260
+ inlets = self._inlets
261
+ inlet_position = np.average(self._net['pore.coords'][inlets],0)
262
+ dist_sqrd = (self._outlet_position-inlet_position)*(self._outlet_position-inlet_position)
263
+ self._initial_distance = np.sqrt(dist_sqrd[0]+dist_sqrd[1]+dist_sqrd[2])
264
+ logger.debug( 'initial distance')
265
+ logger.debug( self._initial_distance)
266
+ self._current_distance = self._initial_distance
267
+ self._percent_complete = np.round((self._initial_distance-self._current_distance)/self._initial_distance*100, decimals = 1)
268
+ logger.info( 'percent complete')
269
+ logger.info( self._percent_complete)
270
+ self._rough_complete = 0
271
+ print(' IP algorithm at',np.int(self._rough_complete),'% completion at',np.round(misc.toc(quiet=True)),'seconds')
272
+ logger.debug( '+='*25)
273
+
274
+ def _do_outer_iteration_stage(self):
275
+ r"""
276
+ Executes the outer iteration stage
277
+ """
278
+ logger.info("Outer Iteration Stage ")
279
+ self._pseq = 1
280
+ self._tseq = 1
281
+ self._ppres = 0
282
+ self._tpres = 0
283
+ self._NewPore = -1
284
+ # Time keeper
285
+ self._sim_time = 0
286
+ self._setup_for_IP()
287
+ self._condition_update()
288
+ #self['throat.cluster_final'] = np.zeros(self._net.num_throats())
289
+ while self._condition:
290
+ self._do_one_outer_iteration()
291
+
292
+ #Calculate Saturations
293
+ v_total = sp.sum(self._net['pore.volume'])+sp.sum(self._net['throat.volume'])
294
+ sat = 0.
295
+ self['pore.inv_sat'] = 1.
296
+ self['throat.inv_sat'] = 1.
297
+ for i in range(1,self._tseq+1):
298
+ inv_pores = sp.where(self['pore.inv_seq']==i)[0]
299
+ inv_throats = sp.where(self['throat.inv_seq']==i)[0]
300
+ new_sat = (sum(self._pore_volumes[inv_pores])+sum(self._throat_volumes[inv_throats]))/v_total
301
+ sat += new_sat
302
+ self['pore.inv_sat'][inv_pores] = sat
303
+ self['throat.inv_sat'][inv_throats] = sat
304
+ self.sat = sat
305
+
306
+ def _do_one_outer_iteration(self):
307
+ r"""
308
+ One iteration of an outer iteration loop for an algorithm
309
+ (e.g. time or parametric study)
310
+ """
311
+ if (sp.mod(self._counter,500)==False):
312
+ logger.info("Outer Iteration (counter = "+str(self._counter)+")")
313
+ pass
314
+ self._do_inner_iteration_stage()
315
+ self._condition_update()
316
+ self._counter += 1
317
+
318
+ def _do_inner_iteration_stage(self):
319
+ r"""
320
+ Executes the inner iteration stage
321
+ """
322
+ logger.debug(" Inner Iteration Stage: ")
323
+
324
+ self._plast = len(np.nonzero(self['pore.cluster_final'])[0])
325
+ if self._timing:
326
+ # determine the cluster with the earliest Haines time
327
+ self._current_cluster = 1 + self._cluster_data['haines_time'].tolist().index(min(self._cluster_data['haines_time']))
328
+ # update simulation clock
329
+ logger.debug( 'sim time = ')
330
+ logger.debug(self._sim_time)
331
+ logger.debug(' haines time:')
332
+ logger.debug( self._cluster_data['haines_time'])
333
+ # The code really messes up when the [0] isn't in the next line. sim_time seems to just point to a place on the haines time array
334
+ self._sim_time = min(self._cluster_data['haines_time'])
335
+ logger.debug( 'sim time after update= ')
336
+ logger.debug(self._sim_time)
337
+ else:
338
+ # Cycle to the next active cluster
339
+ condition = 0
340
+ loop_count = 0
341
+ original_cluster = self._current_cluster
342
+ cnum = original_cluster+1
343
+ while condition == 0:
344
+ if cnum > self._clusterCount:
345
+ cnum = 1
346
+ if self._cluster_data['active'][cnum-1] == 1:
347
+ condition = 1
348
+ self._current_cluster = cnum
349
+ if cnum == original_cluster:
350
+ loop_count = loop_count+1
351
+ if loop_count > 1:
352
+ logger.error('No clusters active. Stuck in infinite loop.')
353
+ pass
354
+ cnum = cnum + 1
355
+
356
+ # run through the Haines Jump steps
357
+ self._do_one_inner_iteration()
358
+ self._pnew = len(np.nonzero(self['pore.cluster_final'])[0])
359
+ self._tseq += 1
360
+ if self._pnew>self._plast:
361
+ self._pseq += 1
362
+
363
+
364
+ def _do_one_inner_iteration(self):
365
+ r"""
366
+ Executes one inner iteration
367
+ """
368
+ logger.debug(" Inner Iteration")
369
+ # Fill throat and connecting pore
370
+ # Pop out the largest throat (lowest inv_Pc) in the list, read the throat number
371
+ tinvade = heapq.heappop(self._tpoints[self._current_cluster-1])[1]
372
+ emptyCluster = -1
373
+ fullCluster = self._current_cluster
374
+ if self._tpoints[self._current_cluster-1] == []:
375
+ emptyCluster = self._current_cluster
376
+ logger.debug( ' ')
377
+ logger.debug( '--------------------------------------------------')
378
+ logger.debug( 'STEP')
379
+ logger.debug(self._tseq)
380
+ logger.debug( 'trying to access cluster: ')
381
+ logger.debug(self._current_cluster)
382
+ logger.debug( 'when these clusters are active active: ')
383
+ logger.debug(sp.nonzero(self._cluster_data['active'])[0])
384
+ logger.debug( 'Haines at throat,time: ')
385
+ logger.debug(tinvade)
386
+ if self._timing:
387
+ logger.debug(self._sim_time)
388
+ pass
389
+
390
+ # Mark throat as invaded
391
+ self['throat.inv_seq'][tinvade] = self._tseq
392
+ self['throat.inv_pres'][tinvade] = max(max(self['throat.inv_pres']),self['throat.inv_Pc'][tinvade])
393
+ if self._timing:
394
+ self['throat.inv_time'][tinvade] = self._sim_time
395
+ # update self._cluster_data.['pore_volume']
396
+ self._cluster_data['throat_volume'][self._current_cluster-1] += self._throat_volumes[tinvade]
397
+ # Remove throat's contribution to the vol_coef
398
+ self._cluster_data['vol_coef'][self._current_cluster-1] = self._cluster_data['vol_coef'][self._current_cluster-1] - self._Tvol_coef[tinvade]
399
+ # Mark pore as invaded
400
+ Pores = self._net.find_connected_pores(tinvade)
401
+ # If both pores are already invaded:
402
+ if np.in1d(Pores,np.nonzero(self['pore.cluster_final'])[0]).all():
403
+ self._NewPore = -1
404
+ # Label invaded throat with smaller cluster number
405
+ #find cluster 1
406
+ clusters = self._cluster_data['transform'][self['pore.cluster_final'][Pores]-1]
407
+ logger.debug('clusters = ')
408
+ logger.debug(clusters)
409
+ self._current_cluster = min(clusters)
410
+ self['throat.cluster_final'][tinvade] = self._current_cluster
411
+ # if pores are from 2 different clusters:
412
+ if self['pore.cluster_final'][Pores[0]]!=self['pore.cluster_final'][Pores[1]] :
413
+ # find name of larger cluster number
414
+ maxCluster = max(clusters)
415
+ curCluster = self._current_cluster
416
+ if emptyCluster == maxCluster:
417
+ fullCluster = curCluster
418
+ if emptyCluster == curCluster:
419
+ fullCluster = maxCluster
420
+ logger.info(' ')
421
+ logger.info('CLUSTERS COMBINING:')
422
+ logger.info(curCluster)
423
+ logger.info(maxCluster)
424
+ if self._timing:
425
+ logger.info('at time')
426
+ logger.info(self._sim_time)
427
+ pass
428
+ # update the cluster transform
429
+ self._cluster_data['transform'][self._cluster_data['transform']==maxCluster] = [curCluster][0]
430
+ # check if either was inactive (broke through already)
431
+ if self._cluster_data['active'][maxCluster-1] + self._cluster_data['active'][self._current_cluster-1]<2:
432
+ logger.debug('making clusters ')
433
+ logger.debug(self._current_cluster)
434
+ logger.debug('and')
435
+ logger.debug(maxCluster)
436
+ logger.debug('inactive due to one being inactive already')
437
+ logger.debug(self._cluster_data['active'][curCluster-1])
438
+ logger.debug(self._cluster_data['active'][maxCluster-1])
439
+ self.cluster_remove(curCluster)
440
+ logger.info(' ')
441
+ logger.info('CLUSTER MERGED WITH A BREAKTHROUGH CLUSTER')
442
+ else:
443
+ # relabel all pores and throats from larger number with smaller number
444
+ cluster_pores = self.toindices((self['pore.cluster_final']==maxCluster) + (self['pore.cluster_final']==curCluster))
445
+ cluster_throats = self.toindices((self['throat.cluster_final']==maxCluster) + (self['throat.cluster_final']==curCluster))
446
+ if emptyCluster == -1:
447
+ cluster_int_throats = list(zip(*self._tpoints[curCluster-1]))[1] + list(zip(*self._tpoints[maxCluster-1]))[1]
448
+ else:
449
+ cluster_int_throats = list(zip(*self._tpoints[fullCluster-1]))[1]
450
+ self._cluster_data['flow_rate'][curCluster-1] += self._cluster_data['flow_rate'][maxCluster-1]
451
+ self.cluster_update(curCluster,cluster_pores,cluster_throats,cluster_int_throats,tinvade)
452
+ logger.info('making cluster ')
453
+ logger.info(maxCluster)
454
+ logger.info('inactive due to merge')
455
+ # update the old cluster's activity and time
456
+ self.cluster_remove(maxCluster)
457
+
458
+
459
+ else:
460
+ # label invaded throat with current cluster
461
+ self['throat.cluster_final'][tinvade] = self._current_cluster
462
+ # find univaded pore, NewPore
463
+ self._NewPore = Pores[self['pore.cluster_final'][Pores]==0][0]
464
+ logger.debug( ' ')
465
+ logger.debug( 'INVADING PORE: ')
466
+ logger.debug(self._NewPore)
467
+ logger.debug('the other pore is one of: ')
468
+ logger.debug(Pores)
469
+ logger.debug( 'position: ')
470
+ logger.debug(self._net['pore.coords'][self._NewPore])
471
+ # label that pore as invaded
472
+ self['pore.cluster_final'][self._NewPore] = self._current_cluster
473
+ self['pore.cluster_original'][self._NewPore] = self._current_cluster
474
+ if self._timing:
475
+ self['pore.inv_time'][self._NewPore] = self._sim_time
476
+ self['pore.inv_seq'][self._NewPore] = self._tseq
477
+ self['pore.inv_pres'][self._NewPore] = max(self['throat.inv_pres'])
478
+ if self._timing:
479
+ # update self._cluster_data.['pore_volume']
480
+ self._cluster_data['pore_volume'][self._current_cluster-1] += self._pore_volumes[self._NewPore]
481
+ # Make a list of all throats neighboring pores in the cluster
482
+ # Update interface list
483
+ neighbors = self._net.find_neighbor_throats(self._NewPore)
484
+ for j in neighbors:
485
+ # If a throat is not labelled as invaded by the cluster, it must be an interfacial throat
486
+ if (j not in self._tlists[self._current_cluster-1]):
487
+ logger.debug( 'new throat:')
488
+ logger.debug(j)
489
+ logger.debug('connecting pores:')
490
+ logger.debug(self._net.find_connected_pores(j))
491
+ # Add this throat data (pressure, number) to this cluster's "heap" of throat data.
492
+ heapq.heappush(self._tpoints[self._current_cluster-1],(self._phase['throat.'+self._capillary_pressure_name][j],j))
493
+ # Add new throat number to throat list for this cluster
494
+ self._tlists[self._current_cluster-1].append(j)
495
+ if self._timing:
496
+ # Update the cluster's vol_coef
497
+ self._cluster_data['vol_coef'][self._current_cluster-1] = self._cluster_data['vol_coef'][self._current_cluster-1]+self._Tvol_coef[j]
498
+ if self._tpoints[self._current_cluster-1] != []:
499
+ # Make sure you are not re-invading a throat in the next step (might never happen with new cluster routines)
500
+ while self['throat.cluster_final'][self._tpoints[self._current_cluster-1][0][1]] > 0:
501
+ tremove = heapq.heappop(self._tpoints[self._current_cluster-1])[1]
502
+ if self._tpoints[self._current_cluster-1] == []:
503
+ logger.debug( 'making cluster ')
504
+ logger.debug(self._current_cluster)
505
+ logger.debug('inactive due to tpoints = [] ')
506
+ self.cluster_remove(self._current_cluster)
507
+ print('still happening!')
508
+ break
509
+ # Find next Haines Jump info
510
+ if self._tpoints[self._current_cluster-1] != []:
511
+ next_throat = self._tpoints[self._current_cluster-1][0][1]
512
+ self._cluster_data['haines_throat'][self._current_cluster-1] = next_throat
513
+ if self._timing:
514
+ self._cluster_data['haines_pressure'][self._current_cluster-1] = self._tpoints[self._current_cluster-1][0][0]
515
+ self._cluster_data['cap_volume'][self._current_cluster-1] = self._cluster_data['haines_pressure'][self._current_cluster-1]*self._cluster_data['vol_coef'][self._current_cluster-1]
516
+ # Calculate the new Haines jump time
517
+ logger.debug( 'haines time before last stage:')
518
+ logger.debug( self._cluster_data['haines_time'])
519
+ if self._tpoints[self._current_cluster-1] == []:
520
+ logger.debug('making cluster ')
521
+ logger.debug(self._current_cluster)
522
+ logger.debug('inactive due to self._tpoints being empty for that cluster')
523
+ self.cluster_remove(self._current_cluster)
524
+ if self._timing:
525
+ if self._cluster_data['active'][self._current_cluster-1] == 1:
526
+ self._cluster_data['haines_time'][self._current_cluster-1] = (self._cluster_data['pore_volume'][self._current_cluster-1]+self._cluster_data['throat_volume'][self._current_cluster-1]+self._cluster_data['cap_volume'][self._current_cluster-1])/self._cluster_data['flow_rate'][self._current_cluster-1]
527
+ if self._cluster_data['haines_time'][self._current_cluster-1] < self._sim_time:
528
+ self._cluster_data['haines_time'][self._current_cluster-1] = self._sim_time
529
+ logger.debug('haines time at the end of the throat stuff')
530
+ logger.debug(self._cluster_data['haines_time'])
531
+
532
+ def _condition_update(self):
533
+ # Calculate the distance between the new pore and outlet pores
534
+ if self._end_condition == 'breakthrough':
535
+ newpore_position = self._net['pore.coords'][self._NewPore]
536
+ dist_sqrd = (self._outlet_position-newpore_position)*(self._outlet_position-newpore_position)
537
+ if dist_sqrd[0].shape==(3,): # need to do this for MatFile networks because newpore_position is a nested array, not a vector (?)
538
+ dist_sqrd = dist_sqrd[0]
539
+ newpore_distance = np.sqrt(dist_sqrd[0]+dist_sqrd[1]+dist_sqrd[2])
540
+ logger.debug( 'newpore distance')
541
+ logger.debug( newpore_distance)
542
+ if newpore_distance < self._current_distance:
543
+ self._percent_complete = np.round((self._initial_distance-newpore_distance)/self._initial_distance*100, decimals = 1)
544
+ logger.info( 'percent complete')
545
+ logger.info( self._percent_complete)
546
+ self._current_distance = newpore_distance
547
+ elif self._end_condition == 'total':
548
+ self._percent_complete = np.round((np.sum(self['pore.cluster_final']>0)/self._net.num_pores())*100, decimals = 1)
549
+ if self._percent_complete > self._rough_complete + self._rough_increment:
550
+ self._rough_complete = np.floor(self._percent_complete/self._rough_increment)*self._rough_increment
551
+ print(' IP algorithm at',np.int(self._rough_complete),'% completion at',np.round(misc.toc(quiet=True)),'seconds')
552
+
553
+ # Determine if a new breakthrough position has occured
554
+ if self._NewPore in self._outlets:
555
+ logger.info( ' ')
556
+ logger.info( 'BREAKTHROUGH AT PORE: ')
557
+ logger.info(self._NewPore)
558
+ logger.info('in cluster ')
559
+ logger.info(self._current_cluster)
560
+ if self._timing:
561
+ logger.info('at time')
562
+ logger.info(self._sim_time)
563
+ pass
564
+ if self._end_condition == 'breakthrough':
565
+ self.cluster_remove(self._current_cluster)
566
+ elif self._end_condition == 'total':
567
+ self._brkevent.append(self._NewPore)
568
+ if np.sum(self._cluster_data['active']) == 0:
569
+ logger.info( ' ')
570
+ logger.info( 'SIMULATION FINISHED; no more active clusters')
571
+ if self._timing:
572
+ logger.info('at time')
573
+ logger.info(self._sim_time)
574
+ pass
575
+ self._condition = 0
576
+ print(' IP algorithm at 100% completion at ',np.round(misc.toc(quiet=True)),' seconds')
577
+
578
+ def cluster_update(self,cl_num,pores,throats,int_throats,bad_throat=-1):
579
+ r'''
580
+ '''
581
+ int_throats = sp.unique(int_throats)
582
+ int_throats = int_throats[int_throats!=bad_throat]
583
+ pores = sp.unique(pores)
584
+ throats = sp.unique(throats)
585
+ #label all pores as invaded
586
+ self['pore.cluster_final'][pores] = cl_num
587
+ if sp.shape(throats) != (0,):
588
+ self['throat.cluster_final'][throats] = cl_num
589
+ if self._timing:
590
+ # Calculate total volume in all invaded pores
591
+ self._cluster_data['pore_volume'][cl_num-1] = np.sum(self._pore_volumes[pores])
592
+ # Calculate total volume in all invaded throats
593
+ if sp.shape(throats) != (0,):
594
+ self._cluster_data['throat_volume'][cl_num-1] = np.sum(self._throat_volumes[throats])
595
+ # Sum all interfacial throats' volume coeffients for throat cap volume calculation
596
+ self._cluster_data['vol_coef'][cl_num-1] = np.sum(self._Tvol_coef[int_throats])
597
+ # Make a list of all entry pressures of the interfacial throats
598
+ interface_throat_pressures = self['throat.inv_Pc'][int_throats]#[0]
599
+ # Zip pressures and numbers together so that HeapQ can work its magic
600
+ Interface= list(zip(interface_throat_pressures,int_throats))
601
+ # Turn the zipped throat interfaces object into a heap
602
+ heapq.heapify(Interface)
603
+ # Add to the total list of interface throats in the system
604
+ self._tlists[cl_num-1] = int_throats.tolist()
605
+ # Add to the total list of invaded interface throats in the system
606
+ self._tpoints[cl_num-1] = Interface
607
+ # Pop off the first entry (lowest pressure) on the throat info list
608
+ invaded_throat_info = Interface[0]
609
+ if self._timing:
610
+ # Determine pressure at Haines Jump
611
+ self._cluster_data['haines_pressure'][cl_num-1] = invaded_throat_info[0]
612
+ # Calculate cap_volume at Haines Jump
613
+ self._cluster_data['cap_volume'][cl_num-1] = self._cluster_data['haines_pressure'][cl_num-1]*self._cluster_data['vol_coef'][cl_num-1]
614
+ # Calculate throat_volume at Haines Jump
615
+ self._cluster_data['throat_volume'][cl_num-1] = self._cluster_data['throat_volume'][cl_num-1]+self._throat_volumes[invaded_throat_info[1]]
616
+ # Calculate time at Haines Jump
617
+ self._cluster_data['haines_time'][cl_num-1] = (self._cluster_data['pore_volume'][cl_num-1]+self._cluster_data['throat_volume'][cl_num-1]+
618
+ self._cluster_data['cap_volume'][cl_num-1])/self._cluster_data['flow_rate'][cl_num-1]
619
+ # Record invaded throat
620
+ self._cluster_data['haines_throat'][cl_num-1] = invaded_throat_info[1]
621
+
622
+ def cluster_remove(self,cl_num):
623
+ if self._timing:
624
+ self._cluster_data['haines_time'][cl_num-1] = 1e32
625
+ self._cluster_data['active'][cl_num-1] = 0
626
+ self._tpoints[cl_num-1] = []
627
+
628
+
629
+ def return_results(self,occupancy='occupancy',IPseq=None,IPsat=None,IPpres=None):
630
+ r"""
631
+
632
+ Returns
633
+ -------
634
+ The invading phase will aquire the following pore data ::
635
+
636
+ occupancy : 0. for univaded, 1. for invaded
637
+ IP_cluster_final : 0 for uninvaded, merged cluster number for invaded
638
+ IP_cluster_original : 0 for uninvaded, original cluster number for invaded
639
+ IP_inv_seq : 0 for uninvaded, simulation step for invaded
640
+ IP_inv_time : 0 for uninvaded, simulation time for invaded
641
+
642
+ and throat data ::
643
+
644
+ occupancy : 0 for univaded, 1 for invaded
645
+ IP_cluster_final : 0 for uninvaded, merged cluster number for invaded
646
+ IP_inv_seq : 0 for uninvaded, simulation step for invaded
647
+ IP_inv_time : 0 for uninvaded, simulation time for invaded
648
+
649
+ """
650
+ self._phase['pore.IP_cluster_final']=self['pore.cluster_final']
651
+ self._phase['pore.IP_cluster_original']=self['pore.cluster_original']
652
+ self._phase['throat.IP_cluster_final']=self['throat.cluster_final']
653
+ self._phase['pore.IP_inv_seq']=self['pore.inv_seq']
654
+ self._phase['throat.IP_inv_seq']=self['throat.inv_seq']
655
+ if self._timing:
656
+ self._phase['pore.IP_inv_time']=self['pore.inv_time']
657
+ self._phase['throat.IP_inv_time']=self['throat.inv_time']
658
+
659
+ if IPseq==None:
660
+ if IPsat is not None:
661
+ sat_pores = self['pore.inv_sat']<=IPsat
662
+ sat_throats = self['throat.inv_sat']<=IPsat
663
+ if sum(sat_pores) == 0:
664
+ IPseq = 0
665
+ else:
666
+ IPseq = max([max(self['throat.inv_seq'][sat_throats]),max(self['pore.inv_seq'][sat_pores])])
667
+ else:
668
+ if IPpres != None:
669
+ sat_pores = self['pore.inv_pres']<=IPpres
670
+ sat_throats = self['throat.inv_pres']<=IPpres
671
+ if sum(sat_pores) == 0:
672
+ IPseq = 0
673
+ else:
674
+ IPseq = max([max(self['throat.inv_seq'][sat_throats]),max(self['pore.inv_seq'][sat_pores])])
675
+ else:
676
+ IPseq = self._tseq
677
+
678
+ try:
679
+ self._phase['pore.'+occupancy] = 0.
680
+ inv_pores = (self['pore.inv_seq']>0)&(self['pore.inv_seq']<=IPseq)
681
+ self._phase['pore.'+occupancy][inv_pores] = 1.
682
+ self['pore.invaded'] = inv_pores
683
+ self._phase['throat.'+occupancy] = 0.
684
+ inv_throats = (self['throat.inv_seq']>0)&(self['throat.inv_seq']<=IPseq)
685
+ self._phase['throat.'+occupancy][inv_throats] = 1.
686
+ self['throat.invaded'] = inv_throats
687
+ self.sat = max(self['throat.inv_sat'][inv_throats])
688
+
689
+ except:
690
+ print('Something bad happened while trying to update phase',self._phase.name)
691
+ try:
692
+ self._phase_def['pore.'+occupancy]=sp.array(~inv_pores,dtype='float')
693
+ self['pore.defended']=sp.array(~inv_pores, dtype='float')
694
+ self._phase_def['throat.'+occupancy]=sp.array(~inv_throats, dtype='float')
695
+ self['throat.defended']=sp.array(~inv_throats, dtype='float')
696
+ except:
697
+ print('A partner phase has not been set so inverse occupancy cannot be set')
698
+
699
+
700
+ if __name__ == '__main__':
701
+ import doctest
702
+ doctest.testmod(verbose=True)