openpnm 1.0.0__zip
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- OpenPNM-1.1/MANIFEST.in +2 -0
- OpenPNM-1.1/OpenPNM/Algorithms/__FickianDiffusion__.py +67 -0
- OpenPNM-1.1/OpenPNM/Algorithms/__FourierConduction__.py +63 -0
- OpenPNM-1.1/OpenPNM/Algorithms/__GenericAlgorithm__.py +235 -0
- OpenPNM-1.1/OpenPNM/Algorithms/__GenericLinearTransport__.py +641 -0
- OpenPNM-1.1/OpenPNM/Algorithms/__InvasionPercolationForImbibition__.py +703 -0
- OpenPNM-1.1/OpenPNM/Algorithms/__InvasionPercolationTimed__.py +702 -0
- OpenPNM-1.1/OpenPNM/Algorithms/__InvasionPercolation__.py +156 -0
- OpenPNM-1.1/OpenPNM/Algorithms/__OhmicConduction__.py +64 -0
- OpenPNM-1.1/OpenPNM/Algorithms/__OrdinaryPercolation__.py +402 -0
- OpenPNM-1.1/OpenPNM/Algorithms/__StokesFlow__.py +64 -0
- OpenPNM-1.1/OpenPNM/Algorithms/__Tortuosity__.py +91 -0
- OpenPNM-1.1/OpenPNM/Algorithms/__init__.py +48 -0
- OpenPNM-1.1/OpenPNM/Base/__Controller__.py +480 -0
- OpenPNM-1.1/OpenPNM/Base/__Core__.py +1522 -0
- OpenPNM-1.1/OpenPNM/Base/__ModelsDict__.py +345 -0
- OpenPNM-1.1/OpenPNM/Base/__Tools__.py +72 -0
- OpenPNM-1.1/OpenPNM/Base/__init__.py +32 -0
- OpenPNM-1.1/OpenPNM/Geometry/__Boundary__.py +80 -0
- OpenPNM-1.1/OpenPNM/Geometry/__Cube_and_Cuboid__.py +64 -0
- OpenPNM-1.1/OpenPNM/Geometry/__GenericGeometry__.py +106 -0
- OpenPNM-1.1/OpenPNM/Geometry/__SGL10__.py +67 -0
- OpenPNM-1.1/OpenPNM/Geometry/__Stick_and_Ball__.py +68 -0
- OpenPNM-1.1/OpenPNM/Geometry/__TestGeometry__.py +51 -0
- OpenPNM-1.1/OpenPNM/Geometry/__Toray090__.py +68 -0
- OpenPNM-1.1/OpenPNM/Geometry/__Voronoi__.py +98 -0
- OpenPNM-1.1/OpenPNM/Geometry/__init__.py +47 -0
- OpenPNM-1.1/OpenPNM/Geometry/models/__init__.py +33 -0
- OpenPNM-1.1/OpenPNM/Geometry/models/pore_area.py +27 -0
- OpenPNM-1.1/OpenPNM/Geometry/models/pore_centroid.py +35 -0
- OpenPNM-1.1/OpenPNM/Geometry/models/pore_diameter.py +127 -0
- OpenPNM-1.1/OpenPNM/Geometry/models/pore_misc.py +55 -0
- OpenPNM-1.1/OpenPNM/Geometry/models/pore_seed.py +212 -0
- OpenPNM-1.1/OpenPNM/Geometry/models/pore_surface_area.py +28 -0
- OpenPNM-1.1/OpenPNM/Geometry/models/pore_vertices.py +19 -0
- OpenPNM-1.1/OpenPNM/Geometry/models/pore_volume.py +133 -0
- OpenPNM-1.1/OpenPNM/Geometry/models/throat_area.py +47 -0
- OpenPNM-1.1/OpenPNM/Geometry/models/throat_centroid.py +80 -0
- OpenPNM-1.1/OpenPNM/Geometry/models/throat_diameter.py +106 -0
- OpenPNM-1.1/OpenPNM/Geometry/models/throat_length.py +95 -0
- OpenPNM-1.1/OpenPNM/Geometry/models/throat_misc.py +42 -0
- OpenPNM-1.1/OpenPNM/Geometry/models/throat_normal.py +31 -0
- OpenPNM-1.1/OpenPNM/Geometry/models/throat_offset_vertices.py +191 -0
- OpenPNM-1.1/OpenPNM/Geometry/models/throat_perimeter.py +26 -0
- OpenPNM-1.1/OpenPNM/Geometry/models/throat_seed.py +12 -0
- OpenPNM-1.1/OpenPNM/Geometry/models/throat_shape_factor.py +37 -0
- OpenPNM-1.1/OpenPNM/Geometry/models/throat_surface_area.py +44 -0
- OpenPNM-1.1/OpenPNM/Geometry/models/throat_vector.py +27 -0
- OpenPNM-1.1/OpenPNM/Geometry/models/throat_vertices.py +19 -0
- OpenPNM-1.1/OpenPNM/Geometry/models/throat_volume.py +45 -0
- OpenPNM-1.1/OpenPNM/Network/__Cubic__.py +316 -0
- OpenPNM-1.1/OpenPNM/Network/__DelaunayCubic__.py +127 -0
- OpenPNM-1.1/OpenPNM/Network/__Delaunay__.py +600 -0
- OpenPNM-1.1/OpenPNM/Network/__GenericNetwork__.py +1184 -0
- OpenPNM-1.1/OpenPNM/Network/__MatFile__.py +331 -0
- OpenPNM-1.1/OpenPNM/Network/__TestNet__.py +109 -0
- OpenPNM-1.1/OpenPNM/Network/__init__.py +40 -0
- OpenPNM-1.1/OpenPNM/Network/models/__init__.py +12 -0
- OpenPNM-1.1/OpenPNM/Network/models/pore_topology.py +106 -0
- OpenPNM-1.1/OpenPNM/Phases/__Air__.py +63 -0
- OpenPNM-1.1/OpenPNM/Phases/__GenericPhase__.py +146 -0
- OpenPNM-1.1/OpenPNM/Phases/__Mercury__.py +71 -0
- OpenPNM-1.1/OpenPNM/Phases/__TestPhase__.py +46 -0
- OpenPNM-1.1/OpenPNM/Phases/__Water__.py +56 -0
- OpenPNM-1.1/OpenPNM/Phases/__init__.py +38 -0
- OpenPNM-1.1/OpenPNM/Phases/models/__init__.py +22 -0
- OpenPNM-1.1/OpenPNM/Phases/models/contact_angle.py +34 -0
- OpenPNM-1.1/OpenPNM/Phases/models/density.py +81 -0
- OpenPNM-1.1/OpenPNM/Phases/models/diffusivity.py +95 -0
- OpenPNM-1.1/OpenPNM/Phases/models/electrical_conductivity.py +10 -0
- OpenPNM-1.1/OpenPNM/Phases/models/misc.py +125 -0
- OpenPNM-1.1/OpenPNM/Phases/models/molar_density.py +69 -0
- OpenPNM-1.1/OpenPNM/Phases/models/molar_mass.py +31 -0
- OpenPNM-1.1/OpenPNM/Phases/models/surface_tension.py +104 -0
- OpenPNM-1.1/OpenPNM/Phases/models/thermal_conductivity.py +98 -0
- OpenPNM-1.1/OpenPNM/Phases/models/vapor_pressure.py +69 -0
- OpenPNM-1.1/OpenPNM/Phases/models/viscosity.py +103 -0
- OpenPNM-1.1/OpenPNM/Physics/__GenericPhysics__.py +111 -0
- OpenPNM-1.1/OpenPNM/Physics/__Standard__.py +51 -0
- OpenPNM-1.1/OpenPNM/Physics/__TestPhysics__.py +50 -0
- OpenPNM-1.1/OpenPNM/Physics/__init__.py +30 -0
- OpenPNM-1.1/OpenPNM/Physics/models/__init__.py +18 -0
- OpenPNM-1.1/OpenPNM/Physics/models/capillary_pressure.py +122 -0
- OpenPNM-1.1/OpenPNM/Physics/models/diffusive_conductance.py +82 -0
- OpenPNM-1.1/OpenPNM/Physics/models/electrical_conductance.py +59 -0
- OpenPNM-1.1/OpenPNM/Physics/models/generic_source_term.py +564 -0
- OpenPNM-1.1/OpenPNM/Physics/models/hydraulic_conductance.py +76 -0
- OpenPNM-1.1/OpenPNM/Physics/models/multiphase.py +133 -0
- OpenPNM-1.1/OpenPNM/Physics/models/thermal_conductance.py +67 -0
- OpenPNM-1.1/OpenPNM/Postprocessing/Graphics.py +251 -0
- OpenPNM-1.1/OpenPNM/Postprocessing/Plots.py +369 -0
- OpenPNM-1.1/OpenPNM/Postprocessing/__init__.py +10 -0
- OpenPNM-1.1/OpenPNM/Utilities/IO.py +277 -0
- OpenPNM-1.1/OpenPNM/Utilities/Shortcuts.py +17 -0
- OpenPNM-1.1/OpenPNM/Utilities/__init__.py +16 -0
- OpenPNM-1.1/OpenPNM/Utilities/misc.py +226 -0
- OpenPNM-1.1/OpenPNM/Utilities/transformations.py +1923 -0
- OpenPNM-1.1/OpenPNM/Utilities/vertexops.py +824 -0
- OpenPNM-1.1/OpenPNM/__init__.py +56 -0
- OpenPNM-1.1/OpenPNM.egg-info/PKG-INFO +11 -0
- OpenPNM-1.1/OpenPNM.egg-info/SOURCES.txt +107 -0
- OpenPNM-1.1/OpenPNM.egg-info/dependency_links.txt +1 -0
- OpenPNM-1.1/OpenPNM.egg-info/requires.txt +1 -0
- OpenPNM-1.1/OpenPNM.egg-info/top_level.txt +1 -0
- OpenPNM-1.1/PKG-INFO +11 -0
- OpenPNM-1.1/README.txt +88 -0
- OpenPNM-1.1/setup.cfg +7 -0
- OpenPNM-1.1/setup.py +39 -0
|
@@ -0,0 +1,703 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
===============================================================================
|
|
4
|
+
module __InvasionPercolationForImbibition__: Invasion Percolation Algorithm for Imbibition
|
|
5
|
+
===============================================================================
|
|
6
|
+
"""
|
|
7
|
+
import scipy as sp
|
|
8
|
+
import numpy as np
|
|
9
|
+
import scipy.sparse as sprs
|
|
10
|
+
from time import clock
|
|
11
|
+
import heapq
|
|
12
|
+
import itertools
|
|
13
|
+
from OpenPNM.Algorithms import InvasionPercolation
|
|
14
|
+
from OpenPNM.Base import logging
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
class InvasionPercolationForImbibition(InvasionPercolation):
|
|
18
|
+
r"""
|
|
19
|
+
|
|
20
|
+
Invasion Percolation (Imbibition) with cluster growth timing - Class to run IP algorithm on constructed networks
|
|
21
|
+
|
|
22
|
+
Parameters
|
|
23
|
+
----------
|
|
24
|
+
network : Descendent of OpenPNM.Network.GenericNetwork
|
|
25
|
+
A valid network for this algorithm
|
|
26
|
+
name : string
|
|
27
|
+
The name this algorithm will go by
|
|
28
|
+
|
|
29
|
+
"""
|
|
30
|
+
def __init__(self,**kwords):
|
|
31
|
+
r"""
|
|
32
|
+
"""
|
|
33
|
+
super(InvasionPercolationForImbibition,self).__init__(**kwords)
|
|
34
|
+
logger.info("Create IP Imbibition Algorithm Object")
|
|
35
|
+
|
|
36
|
+
def run(self,invading_phase,
|
|
37
|
+
defending_phase,
|
|
38
|
+
inlets=[0],
|
|
39
|
+
outlets=[-1],
|
|
40
|
+
end_condition='breakthrough',
|
|
41
|
+
pore_volume_name='volume',
|
|
42
|
+
pore_diameter_name='diameter',
|
|
43
|
+
throat_volume_name='volume',
|
|
44
|
+
timing='ON',
|
|
45
|
+
report=20,
|
|
46
|
+
inlet_flow=1,
|
|
47
|
+
Psecond=False,
|
|
48
|
+
**params):
|
|
49
|
+
r"""
|
|
50
|
+
|
|
51
|
+
Invasion Percolation (Imbibition) with cluster growth timing - Class to run IP algorithm on constructed networks
|
|
52
|
+
|
|
53
|
+
Parameters
|
|
54
|
+
----------
|
|
55
|
+
invading_phase : OpenPNM Phase Object
|
|
56
|
+
phase which will displace defending phase
|
|
57
|
+
defending_phase : OpenPNM Phase Object
|
|
58
|
+
phase which will be displaced by invading phase
|
|
59
|
+
inlets : list of integers (default: [0])
|
|
60
|
+
list of inlet nodes
|
|
61
|
+
outlets : list of integers (default: [-1])
|
|
62
|
+
list of outlet nodes
|
|
63
|
+
end_condition : string('breakthrough')
|
|
64
|
+
choice between 'breakthrough' and 'total'
|
|
65
|
+
pore_volume_name : string('volume')
|
|
66
|
+
name given to pore volume property
|
|
67
|
+
pore_diameter_name : string('diameter')
|
|
68
|
+
name given to pore diameter property
|
|
69
|
+
throat_volume_name : string('volume')
|
|
70
|
+
name given to throat volume property
|
|
71
|
+
timing : string ('ON')
|
|
72
|
+
turns volume and flowrate calculations 'ON' or 'OFF'
|
|
73
|
+
report : int (20)
|
|
74
|
+
percentage multiple at which a progress report is printed
|
|
75
|
+
inlet_flow : float (1)
|
|
76
|
+
m3/s for each cluster (affects timestamp of pore filling)
|
|
77
|
+
Psecond : boul (False)
|
|
78
|
+
is this a secondary imbibition (after drainage)?
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
Input Phases
|
|
82
|
+
------------
|
|
83
|
+
The algorithm expects an invading phase with the following pore properties:
|
|
84
|
+
contact_angle, surface_tension
|
|
85
|
+
and some defending phase
|
|
86
|
+
|
|
87
|
+
Output
|
|
88
|
+
------
|
|
89
|
+
The invading phase automatically gains pore data ::
|
|
90
|
+
|
|
91
|
+
occupancy : 0 for univaded, 1 for invaded
|
|
92
|
+
IP_inv_final : 0 for uninvaded, merged cluster number for invaded
|
|
93
|
+
IP_inv_original : 0 for uninvaded, original cluster number for invaded
|
|
94
|
+
IP_inv_seq : 0 for uninvaded, simulation step for invaded
|
|
95
|
+
IP_inv_time : 0 for uninvaded, simulation time for invaded
|
|
96
|
+
|
|
97
|
+
and throat data ::
|
|
98
|
+
|
|
99
|
+
occupancy : 0 for univaded, 1 for invaded
|
|
100
|
+
IP_inv : 0 for uninvaded, merged cluster number for invaded
|
|
101
|
+
IP_inv_seq : 0 for uninvaded, simulation step for invaded
|
|
102
|
+
IP_inv_time : 0 for uninvaded, simulation time for invaded
|
|
103
|
+
|
|
104
|
+
Examples
|
|
105
|
+
--------
|
|
106
|
+
>>> import OpenPNM
|
|
107
|
+
>>> pn = OpenPNM.Network.TestNet()
|
|
108
|
+
>>> geo = OpenPNM.Geometry.TestGeometry(network=pn,pores=pn.pores(),throats=pn.throats())
|
|
109
|
+
>>> phase1 = OpenPNM.Phases.TestPhase(network=pn)
|
|
110
|
+
>>> phase1['pore.contact_angle'] = 20
|
|
111
|
+
>>> phase2 = OpenPNM.Phases.TestPhase(network=pn)
|
|
112
|
+
>>> phys1 = OpenPNM.Physics.TestPhysics(network=pn, phase=phase1,pores=pn.pores(),throats=pn.throats())
|
|
113
|
+
>>> phys2 = OpenPNM.Physics.TestPhysics(network=pn, phase=phase2,pores=pn.pores(),throats=pn.throats())
|
|
114
|
+
>>> from OpenPNM.Algorithms.__InvasionPercolationForImbibition__ import InvasionPercolationForImbibition
|
|
115
|
+
>>> IP = InvasionPercolationForImbibition(network=pn)
|
|
116
|
+
>>> IP.run(invading_phase=phase1, defending_phase=phase2, inlets=pn.pores('top'), outlets=pn.pores('bottom'))
|
|
117
|
+
IP algorithm at 0 % completion at 0 seconds
|
|
118
|
+
IP algorithm at 20 % completion at 0 seconds
|
|
119
|
+
IP algorithm at 40 % completion at 0 seconds
|
|
120
|
+
IP algorithm at 100% completion at 0 seconds
|
|
121
|
+
>>> IP.return_results()
|
|
122
|
+
>>> print(len(phase1.pores('occupancy')))
|
|
123
|
+
29
|
|
124
|
+
|
|
125
|
+
Suggested Improvements ::
|
|
126
|
+
|
|
127
|
+
a) Allow updating of cluster flow-rates (this will require a delta-t calculation at each step, instead of a total t calculation).
|
|
128
|
+
b) Allow for a non-linear relationship between pressure and throat-cap volume.
|
|
129
|
+
|
|
130
|
+
"""
|
|
131
|
+
|
|
132
|
+
# if Psecond > 0 then we are doing a secondary imbibition,
|
|
133
|
+
# so end_condition, inlets, are treated differently
|
|
134
|
+
self._Psecond = Psecond
|
|
135
|
+
if Psecond:
|
|
136
|
+
end_condition='secondary'
|
|
137
|
+
inlets = [self._net.pores()[self._phase['pore.occupancy']>0]]
|
|
138
|
+
self._timing = timing=='ON'
|
|
139
|
+
self._pore_diameter_name = pore_diameter_name
|
|
140
|
+
self._throat_volume_name = throat_volume_name
|
|
141
|
+
|
|
142
|
+
super(InvasionPercolationForImbibition,self).run(invading_phase=invading_phase,
|
|
143
|
+
defending_phase=defending_phase,
|
|
144
|
+
inlets=inlets,
|
|
145
|
+
outlets=outlets,
|
|
146
|
+
end_condition=end_condition,
|
|
147
|
+
pore_volume_name=pore_volume_name,
|
|
148
|
+
timing=timing,
|
|
149
|
+
report=report,
|
|
150
|
+
inlet_flow=inlet_flow)
|
|
151
|
+
|
|
152
|
+
def _setup_for_IP(self,**params):
|
|
153
|
+
r"""
|
|
154
|
+
Determines cluster labelling and condition for completion
|
|
155
|
+
|
|
156
|
+
This code is taken from _setup_for_IP from parent InvasionPercolation, but is modified for imbibition
|
|
157
|
+
|
|
158
|
+
"""
|
|
159
|
+
self._clock_start = clock()
|
|
160
|
+
logger.debug( '+='*25)
|
|
161
|
+
logger.debug( 'INITIAL SETUP (STEP 1)')
|
|
162
|
+
# if empty, add Pc_entry to pore_properties
|
|
163
|
+
# set the capillary pressure for pores, instead of throats
|
|
164
|
+
# don't need to interpolate for throats
|
|
165
|
+
# this is a Washburn setting, could use others
|
|
166
|
+
sigma = self._phase['pore.surface_tension'][0]
|
|
167
|
+
theta = self._phase['pore.contact_angle'][0]
|
|
168
|
+
if theta > 90:
|
|
169
|
+
print('WARNING!!!: The invading phase has a contact angle greater than 90deg, so it must be drainage. Use Invasion_Percolation for drainage.' )
|
|
170
|
+
pdia = self._net['pore.'+self._pore_diameter_name]
|
|
171
|
+
if self._Psecond:
|
|
172
|
+
pdia = pdia[self._phase['pore.occupancy']<=0]
|
|
173
|
+
# entry pressure for the pore
|
|
174
|
+
# should be -4.. but use +4 to make heapq work correclty (we want highest Pcap)
|
|
175
|
+
Pc_entry = -4*sigma*sp.cos(sp.radians(theta))/pdia # -4*sigma*sp.cos(sp.radians(theta))/pdia PCAP!!
|
|
176
|
+
# add to the IPforImb object
|
|
177
|
+
if ~self._Psecond:
|
|
178
|
+
self._phase['pore.Pc_entryImb']=Pc_entry
|
|
179
|
+
|
|
180
|
+
if self._timing:
|
|
181
|
+
# calculate Volume_coef for each pore, not throat, for imbib
|
|
182
|
+
# this is the volume of a half-sphere in the pore (so will work for irregular pores)
|
|
183
|
+
self._Pvol_coef = pdia**3*np.pi/12/(Pc_entry) # Pc_entry is stored -ve, so must be converted for vol_coeff PCAP!!
|
|
184
|
+
# Creating an array for invaded Pores(Np long, 0 for uninvaded, cluster number for inaveded)
|
|
185
|
+
if self._Psecond:
|
|
186
|
+
Np = sum(self._phase['pore.occupancy']<=0)
|
|
187
|
+
Nt = sum(self._phase['throat.occupancy']<=0)
|
|
188
|
+
else:
|
|
189
|
+
Np = self._net.num_pores()
|
|
190
|
+
Nt = self._net.num_throats()
|
|
191
|
+
self._Pinv = np.zeros((Np,1),dtype=np.int32)
|
|
192
|
+
self._Pinv_original = np.zeros((Np,1),dtype=np.int32)
|
|
193
|
+
# Creating an array for invaded throats(Nt long, 0 for uninvaded, cluster number for inaveded)
|
|
194
|
+
self._Tinv = np.zeros((Nt,1),dtype=np.int32)
|
|
195
|
+
self._Tinv_original = np.zeros((Nt,1),dtype=np.int32)
|
|
196
|
+
# Creating arrays for tracking invaded Pores(Np long, 0 for uninvaded, sequence for inaveded)
|
|
197
|
+
self._psequence = np.zeros((Np,1),dtype=np.int32)
|
|
198
|
+
if self._timing:
|
|
199
|
+
# Creating arrays for tracking invaded Pores(Np long, -1 for uninvaded, simulation time for inaveded)
|
|
200
|
+
self._Ptime = np.zeros((Np,1),dtype=np.float64)-1
|
|
201
|
+
# Creating arrays for tracking invaded throats(Nt long, 0 for uninvaded, sequence for inaveded)
|
|
202
|
+
self._tsequence = np.zeros((Nt,1),dtype=np.int32)
|
|
203
|
+
if self._timing:
|
|
204
|
+
# Creating arrays for tracking invaded Pores(Np long, -1 for uninvaded, simulation time for inaveded)
|
|
205
|
+
self._Ttime = np.zeros((Nt,1),dtype=np.float64)-1
|
|
206
|
+
# Creating an array for tracking the last invaded pore in each cluster.
|
|
207
|
+
# its length is equal to the maximum number of possible clusters.
|
|
208
|
+
#self.plists = np.zeros((len(self._inlets),1),dtype=np.int32)
|
|
209
|
+
# Iterator variables for sequences and cluster numbers
|
|
210
|
+
clusterNumber = 1
|
|
211
|
+
# Determine how many clusters there are
|
|
212
|
+
self._clusterCount = 0
|
|
213
|
+
# get boundary pores so they don't get included in cluster count
|
|
214
|
+
# bpores = self._net.pores(labels=['boundary','bottom'],mode='intersection')
|
|
215
|
+
for i in self._inlets:
|
|
216
|
+
# ignore boundary pores
|
|
217
|
+
# if ~sp.in1d(i,bpores): # don't need this anymore if all inlet pores are in the boundary
|
|
218
|
+
self._clusterCount += 1
|
|
219
|
+
# Storage for cluster information
|
|
220
|
+
self._cluster_data = {}
|
|
221
|
+
if self._timing:
|
|
222
|
+
self._cluster_data['flow_rate'] = np.ones((self._clusterCount),dtype=np.float64)*self._inlet_flow
|
|
223
|
+
self._cluster_data['haines_pressure'] = np.zeros((self._clusterCount),dtype=np.float64)
|
|
224
|
+
self._cluster_data['haines_time'] = np.zeros((self._clusterCount),dtype=np.float64)
|
|
225
|
+
self._cluster_data['vol_coef'] = np.zeros((self._clusterCount),dtype=np.float64)
|
|
226
|
+
self._cluster_data['cap_volume'] = np.zeros((self._clusterCount),dtype=np.float64)
|
|
227
|
+
self._cluster_data['throat_volume'] = np.zeros((self._clusterCount),dtype=np.float64)
|
|
228
|
+
self._cluster_data['haines_pore'] = np.zeros((self._clusterCount),dtype=np.int32)
|
|
229
|
+
self._cluster_data['active'] = np.ones((self._clusterCount),dtype=np.int8)
|
|
230
|
+
self._cluster_data['transform'] = np.zeros((self._clusterCount),dtype=np.int16)
|
|
231
|
+
for i in range(self._clusterCount):
|
|
232
|
+
self._cluster_data['transform'][i] = i+1
|
|
233
|
+
# Creating an empty list to store the list of potential pores for invasion in each cluster.
|
|
234
|
+
# its length is equal to the maximum number of possible clusters.
|
|
235
|
+
self._plists = []
|
|
236
|
+
# Creating a list for each cluster to store both potential pore and corresponding pore value
|
|
237
|
+
self._ppoints = []
|
|
238
|
+
# Initializing invasion percolation for each possible cluster
|
|
239
|
+
for i in self._inlets:
|
|
240
|
+
if self._timing:
|
|
241
|
+
# # Calculate total volume in all inlet pores
|
|
242
|
+
self._cluster_data['throat_volume'][clusterNumber-1] = np.sum(self._net['throat.'+self._throat_volume_name][i])
|
|
243
|
+
# # Label all invaded pores with their cluster
|
|
244
|
+
# get the throats connecting the boundary pores to the inlet pores, then those are the first filled clusters
|
|
245
|
+
# if sp.in1d(i,bpores): #if ~sp.in1d(i,bpores): # don't need this if all inlet pores are boundary pores
|
|
246
|
+
bthroat = self._net.find_neighbor_throats(i) # throat entering inlet pore from boundary pore
|
|
247
|
+
self._Tinv[bthroat] = clusterNumber
|
|
248
|
+
self._Tinv_original[bthroat] = clusterNumber #self._Pinv_original[i] = clusterNumber
|
|
249
|
+
# Label all boundary-->inlet throats as invaded
|
|
250
|
+
self._tsequence[bthroat] = self._tseq
|
|
251
|
+
if self._timing:
|
|
252
|
+
self._Ttime[bthroat] = self._sim_time
|
|
253
|
+
mask_bpores = sp.vstack([sp.in1d(self._net.find_connected_pores(bthroat)[:,0],i),sp.in1d(self._net.find_connected_pores(bthroat)[:,1],i)])
|
|
254
|
+
interface_pore_numbers = self._net.find_connected_pores(bthroat)[~mask_bpores.T] # i #self._net.find_connected_pores(np.where(self._Tinv==clusterNumber)[0])
|
|
255
|
+
if self._timing:
|
|
256
|
+
# Sum all interfacial throats' volume coeffients for throat cap volume calculation
|
|
257
|
+
self._cluster_data['vol_coef'][clusterNumber-1] = np.sum(self._Pvol_coef[interface_pore_numbers]) # make -ve because heapq only grabs smallest value, so all Pc are -ve for imbibition
|
|
258
|
+
# Make a list of all entry pressures of the connected pores
|
|
259
|
+
# heapq only allows grabbing the smallest number from the heap, but we need the largest Pcap (smallest pore)
|
|
260
|
+
# python reccomends makign the heap -ve, opping the most negative number, then making it posotive (http://stackoverflow.com/questions/2501457/what-do-i-use-for-a-max-heap-implementation-in-python)
|
|
261
|
+
''' Pc_entry as -ve (when calculated above)'''
|
|
262
|
+
''' interface_pore_pressure, heap values, ['haines_pressure'], and ppoints stored as -ve '''
|
|
263
|
+
''' Pvol_coeff, ['vol_coeff'], ['cap_volume'] are stored as +ve (ie multiply ['haines_pressure'] by -1 when calculating ie ['cap_volume'], etc.)'''
|
|
264
|
+
interface_pore_pressures = Pc_entry[interface_pore_numbers]
|
|
265
|
+
# Zip pressures and numbers together so that HeapQ can work its magic
|
|
266
|
+
logger.debug('interface pores(s) found:')
|
|
267
|
+
logger.debug(interface_pore_numbers)
|
|
268
|
+
logger.debug( 'interface pore pressure(s):')
|
|
269
|
+
logger.debug(interface_pore_pressures)
|
|
270
|
+
logger.debug( ' Negative Pcap is required for imbibition to work right')
|
|
271
|
+
Interface= list(zip(interface_pore_pressures,interface_pore_numbers))
|
|
272
|
+
# Interface= list(zip([interface_pore_pressures],[interface_pore_numbers]))
|
|
273
|
+
# Turn the zipped throat interfaces object into a heap
|
|
274
|
+
heapq.heapify(Interface)
|
|
275
|
+
# Add to the total list of interface throats in the system
|
|
276
|
+
self._plists.append(interface_pore_numbers.tolist())
|
|
277
|
+
# Add to the total list of invaded interface pores in the system
|
|
278
|
+
self._ppoints.append(Interface)
|
|
279
|
+
# Pop off the first entry (lowest pressure) on the throat info list
|
|
280
|
+
invaded_pore_info = Interface[0]
|
|
281
|
+
if self._timing:
|
|
282
|
+
# Determine pressure at Haines Jump
|
|
283
|
+
self._cluster_data['haines_pressure'][clusterNumber-1] = invaded_pore_info[0]
|
|
284
|
+
# Calculate cap_volume at Haines Jump (need Haines_pressure negative, to recover correct volume)
|
|
285
|
+
self._cluster_data['cap_volume'][clusterNumber-1] = -self._cluster_data['haines_pressure'][clusterNumber-1]*self._cluster_data['vol_coef'][clusterNumber-1]
|
|
286
|
+
# Calculate time at Haines Jump
|
|
287
|
+
self._cluster_data['haines_time'][clusterNumber-1] = (self._cluster_data['throat_volume'][clusterNumber-1]+
|
|
288
|
+
self._cluster_data['cap_volume'][clusterNumber-1])/self._cluster_data['flow_rate'][clusterNumber-1]
|
|
289
|
+
# Record invaded throat
|
|
290
|
+
self._cluster_data['haines_pore'][clusterNumber-1] = invaded_pore_info[1]
|
|
291
|
+
clusterNumber += 1
|
|
292
|
+
if self._timing:
|
|
293
|
+
logger.debug( 'throat volumes')
|
|
294
|
+
logger.debug(self._cluster_data['throat_volume'])
|
|
295
|
+
logger.debug( 'cap volumes')
|
|
296
|
+
logger.debug(self._cluster_data['cap_volume'])
|
|
297
|
+
logger.debug( 'max throat cap volumes')
|
|
298
|
+
logger.debug( self._Tvol_coef*self._phase.throat_conditions["Pc_entry"])
|
|
299
|
+
logger.debug( 'haines_pore')
|
|
300
|
+
logger.debug( self._cluster_data['haines_pore'])
|
|
301
|
+
# if self._timing:
|
|
302
|
+
# logger.debug( 'max throat cap volumes')
|
|
303
|
+
# logger.debug( self._Tvol_coef*self._phase.throat_conditions["Pc_entry"])
|
|
304
|
+
# self._tseq += 1
|
|
305
|
+
# self._pseq += 1
|
|
306
|
+
self._current_cluster = 0
|
|
307
|
+
# Calculate the distance between the inlet and outlet pores
|
|
308
|
+
self._outlet_position = np.average(self._net['pore.coords'][self._outlets],0)
|
|
309
|
+
# TODO for calculating the inlet position - should we use distance between invaded pore and outlet, or invading throat?
|
|
310
|
+
# for simplicity for now, using pore coords (averaged anyways)
|
|
311
|
+
inlet_position = np.average(self._net['pore.coords'][self._inlets],0)
|
|
312
|
+
dist_sqrd = (self._outlet_position-inlet_position)*(self._outlet_position-inlet_position)
|
|
313
|
+
self._initial_distance = np.sqrt(dist_sqrd[0]+dist_sqrd[1]+dist_sqrd[2])
|
|
314
|
+
logger.debug( 'initial distance')
|
|
315
|
+
logger.debug( self._initial_distance)
|
|
316
|
+
self._current_distance = self._initial_distance
|
|
317
|
+
self._percent_complete = np.round((self._initial_distance-self._current_distance)/self._initial_distance*100, decimals = 1)
|
|
318
|
+
logger.info( 'percent complete')
|
|
319
|
+
logger.info( self._percent_complete)
|
|
320
|
+
self._rough_complete = 0
|
|
321
|
+
print(' IP algorithm at',np.int(self._rough_complete),'% completion at',np.int(np.round(clock())),'seconds')
|
|
322
|
+
logger.debug( '+='*25)
|
|
323
|
+
|
|
324
|
+
def _do_outer_iteration_stage(self):
|
|
325
|
+
r"""
|
|
326
|
+
Executes the outer iteration stage
|
|
327
|
+
"""
|
|
328
|
+
logger.info("Outer Iteration Stage ")
|
|
329
|
+
self._pseq = 1
|
|
330
|
+
self._tseq = 1
|
|
331
|
+
self._NewPore = -1
|
|
332
|
+
# Time keeper
|
|
333
|
+
self._sim_time = 0
|
|
334
|
+
self._setup_for_IP()
|
|
335
|
+
self._condition_update()
|
|
336
|
+
#self._Tinv = np.zeros(self._net.num_throats())
|
|
337
|
+
while self._condition:
|
|
338
|
+
self._do_one_outer_iteration()
|
|
339
|
+
self['pore.IP_inv_final']=np.ravel(np.array(self._Pinv,dtype=np.int))
|
|
340
|
+
self['pore.IP_inv_original']=np.ravel(np.array(self._Pinv_original,dtype=np.int))
|
|
341
|
+
self['throat.IP_inv']=np.ravel(np.array(self._Tinv,dtype=np.int))
|
|
342
|
+
self['pore.IP_inv_seq']=np.ravel(np.array(self._psequence,dtype=np.int))
|
|
343
|
+
self['throat.IP_inv_seq']=np.ravel(np.array(self._tsequence,dtype=np.int))
|
|
344
|
+
if self._timing:
|
|
345
|
+
self['pore.IP_inv_time']=np.ravel(np.array(self._Ptime,dtype=np.float))
|
|
346
|
+
self['throat.IP_inv_time']=np.ravel(np.array(self._Ttime,dtype=np.float))
|
|
347
|
+
|
|
348
|
+
def _do_one_outer_iteration(self):
|
|
349
|
+
r"""
|
|
350
|
+
One iteration of an outer iteration loop for an algorithm
|
|
351
|
+
(e.g. time or parametric study)
|
|
352
|
+
"""
|
|
353
|
+
if (sp.mod(self._counter,500)==False):
|
|
354
|
+
logger.info("Outer Iteration (counter = "+str(self._counter)+")")
|
|
355
|
+
pass
|
|
356
|
+
self._do_inner_iteration_stage()
|
|
357
|
+
self._condition_update()
|
|
358
|
+
self._counter += 1
|
|
359
|
+
|
|
360
|
+
def _do_inner_iteration_stage(self):
|
|
361
|
+
r"""
|
|
362
|
+
Executes the inner iteration stage
|
|
363
|
+
"""
|
|
364
|
+
logger.debug(" Inner Iteration Stage: ")
|
|
365
|
+
|
|
366
|
+
self._plast = len(np.nonzero(self._Pinv)[0])
|
|
367
|
+
if self._timing:
|
|
368
|
+
# determine the cluster with the earliest Haines time
|
|
369
|
+
self._current_cluster = 1 + self._cluster_data['haines_time'].tolist().index(min(self._cluster_data['haines_time']))
|
|
370
|
+
# update simulation clock
|
|
371
|
+
logger.debug( 'sim time = ')
|
|
372
|
+
logger.debug(self._sim_time)
|
|
373
|
+
logger.debug(' haines time:')
|
|
374
|
+
logger.debug( self._cluster_data['haines_time'])
|
|
375
|
+
# 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
|
|
376
|
+
self._sim_time = min(self._cluster_data['haines_time'])
|
|
377
|
+
logger.debug( 'sim time after update= ')
|
|
378
|
+
logger.debug(self._sim_time)
|
|
379
|
+
else:
|
|
380
|
+
# Cycle to the next active cluster
|
|
381
|
+
condition = 0
|
|
382
|
+
loop_count = 0
|
|
383
|
+
original_cluster = self._current_cluster
|
|
384
|
+
cnum = original_cluster+1
|
|
385
|
+
while condition == 0:
|
|
386
|
+
if cnum > self._clusterCount:
|
|
387
|
+
cnum = 1
|
|
388
|
+
if self._cluster_data['active'][cnum-1] == 1:
|
|
389
|
+
condition = 1
|
|
390
|
+
self._current_cluster = cnum
|
|
391
|
+
if cnum == original_cluster:
|
|
392
|
+
loop_count = loop_count+1
|
|
393
|
+
if loop_count > 1:
|
|
394
|
+
logger.error('No clusters active. Stuck in infinite loop.')
|
|
395
|
+
pass
|
|
396
|
+
cnum = cnum + 1
|
|
397
|
+
|
|
398
|
+
# run through the Haines Jump steps
|
|
399
|
+
self._do_one_inner_iteration()
|
|
400
|
+
self._pnew = len(np.nonzero(self._Pinv)[0])
|
|
401
|
+
self._tseq += 1
|
|
402
|
+
if self._pnew>self._plast:
|
|
403
|
+
self._pseq += 1
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
def _do_one_inner_iteration(self):
|
|
407
|
+
r"""
|
|
408
|
+
Executes one inner iteration
|
|
409
|
+
"""
|
|
410
|
+
logger.debug(" Inner Iteration")
|
|
411
|
+
# Fill throat and connecting pore
|
|
412
|
+
# Pop out the largest pore (lowest Pcap) in the list, read the pore number
|
|
413
|
+
try:
|
|
414
|
+
pinvade = heapq.heappop(self._ppoints[self._current_cluster-1])[1]
|
|
415
|
+
except:
|
|
416
|
+
print('Something bad happened trying to invade')
|
|
417
|
+
import pdb
|
|
418
|
+
# pdb.set_trace()
|
|
419
|
+
logger.debug( ' ')
|
|
420
|
+
logger.debug( '--------------------------------------------------')
|
|
421
|
+
logger.debug( 'STEP')
|
|
422
|
+
logger.debug(self._tseq)
|
|
423
|
+
logger.debug( 'trying to access cluster: ')
|
|
424
|
+
logger.debug(self._current_cluster)
|
|
425
|
+
logger.debug( 'when these clusters are active active: ')
|
|
426
|
+
logger.debug(sp.nonzero(self._cluster_data['active'])[0])
|
|
427
|
+
logger.debug( 'Haines at pore,time: ')
|
|
428
|
+
logger.debug(pinvade)
|
|
429
|
+
if self._timing:
|
|
430
|
+
logger.debug(self._sim_time)
|
|
431
|
+
pass
|
|
432
|
+
|
|
433
|
+
# Mark pore as invaded
|
|
434
|
+
self._psequence[pinvade] = self._pseq
|
|
435
|
+
if self._timing:
|
|
436
|
+
self._Ptime[pinvade] = self._sim_time
|
|
437
|
+
# Remove throat's contribution to the vol_coef
|
|
438
|
+
self._cluster_data['vol_coef'][self._current_cluster-1] = self._cluster_data['vol_coef'][self._current_cluster-1]-self._Pvol_coef[pinvade]
|
|
439
|
+
# Mark throat as filled
|
|
440
|
+
AllThroats = self._net.find_neighbor_throats(pinvade) # this finds all connected throats, need to ignore the ones that are already filled
|
|
441
|
+
# 1. remove invading throat and any throat already in this cluster (already invaded and part of same cluster)
|
|
442
|
+
Throats = AllThroats[~sp.in1d(AllThroats,np.where(sp.in1d(self._Tinv,self._current_cluster))[0])]
|
|
443
|
+
# 2. fidn other throats with an interface
|
|
444
|
+
# TODO if there is another interface, then start or continue co-operative filling
|
|
445
|
+
# TODO need to add a cooperative filling method
|
|
446
|
+
ThroatsInv = Throats[sp.in1d(Throats,np.where(self._Tinv>0)[0])]
|
|
447
|
+
# if a throat is already invaded (the pore that was just invaded had another interface in it, so now we have cooperative filling)
|
|
448
|
+
# for each already invaded throat
|
|
449
|
+
if len(ThroatsInv):
|
|
450
|
+
# if this pore has multiple interfaces
|
|
451
|
+
self._NewPore = -1
|
|
452
|
+
self._NewThroat = -1
|
|
453
|
+
# Label invaded pore with smallest cluster number
|
|
454
|
+
# find all clusters connected to the newly invaded pore
|
|
455
|
+
clusters = self._cluster_data['transform'][self._Tinv[AllThroats][self._Tinv[AllThroats]>0]-1] # clusters = self._cluster_data['transform'][self._Tinv[ThroatsInv]-1]
|
|
456
|
+
logger.debug('clusters = ')
|
|
457
|
+
logger.debug(clusters)
|
|
458
|
+
# if some throats are from different clusters - ie if len(clusters)>1
|
|
459
|
+
if len(clusters)>1: #if self._Pinv[Pores[0]]!=self._Pinv[Pores[1]] :
|
|
460
|
+
csize = 0
|
|
461
|
+
maxCluster = []
|
|
462
|
+
for c in clusters: # count occurrences of each value in cluster, in Pinv to find largest cluster
|
|
463
|
+
if len(self._plists[c-1]) > csize:
|
|
464
|
+
csize = sum(clusters==c)
|
|
465
|
+
maxCluster = c
|
|
466
|
+
# find the largest cluster -- FOR IMBIBITION, MERGING ALL SMALLER CLUSTERS INTO THE LARGEST. DIFFERENT FROM DRAININAGE SO BE CAREFUL
|
|
467
|
+
# update the cluster transform to name all clusters as the max
|
|
468
|
+
self._current_cluster = maxCluster #[0]
|
|
469
|
+
self._Pinv[pinvade] = self._current_cluster
|
|
470
|
+
logger.info(' ')
|
|
471
|
+
logger.info('CLUSTERS COMBINING:')
|
|
472
|
+
logger.info(clusters)
|
|
473
|
+
logger.info('into')
|
|
474
|
+
logger.info(maxCluster)
|
|
475
|
+
if self._timing:
|
|
476
|
+
logger.info('at time')
|
|
477
|
+
logger.info(self._sim_time)
|
|
478
|
+
pass
|
|
479
|
+
for c in clusters[clusters!=self._current_cluster]: # go through the clusters as they are moved into the largest cluster
|
|
480
|
+
self._cluster_data['transform'][self._cluster_data['transform']==c] = self._current_cluster
|
|
481
|
+
# relabel all pores and throats from cluster c to largest
|
|
482
|
+
self._Pinv[np.where(self._Pinv==c)[0]] = self._current_cluster
|
|
483
|
+
self._Tinv[np.where(self._Tinv==c)[0]] = self._current_cluster
|
|
484
|
+
# append the list of throats for the other cluster to the current cluster
|
|
485
|
+
self._plists[self._current_cluster-1] = self._plists[self._current_cluster-1] + self._plists[c-1]
|
|
486
|
+
# delete the throat lists on the other cluster
|
|
487
|
+
self._plists[c-1] = []
|
|
488
|
+
# merge the heaps of throat information
|
|
489
|
+
self._ppoints[self._current_cluster-1] = list(heapq.merge(self._ppoints[self._current_cluster-1],self._ppoints[c-1]))
|
|
490
|
+
if self._timing:
|
|
491
|
+
# update the clusters' vol_coefs
|
|
492
|
+
self._cluster_data['vol_coef'][self._current_cluster-1] += self._cluster_data['vol_coef'][c-1]
|
|
493
|
+
self._cluster_data['vol_coef'][c-1] = 0
|
|
494
|
+
# update the clusters' pore volume
|
|
495
|
+
self._cluster_data['throat_volume'][self._current_cluster-1] += self._cluster_data['throat_volume'][c-1]
|
|
496
|
+
self._cluster_data['throat_volume'][c-1] = 0
|
|
497
|
+
# update the clusters' flowrates
|
|
498
|
+
self._cluster_data['flow_rate'][self._current_cluster-1] += self._cluster_data['flow_rate'][c-1]
|
|
499
|
+
self._cluster_data['flow_rate'][c-1] = 0
|
|
500
|
+
logger.debug( 'new flowrate for cluster ')
|
|
501
|
+
logger.debug(self._current_cluster)
|
|
502
|
+
logger.debug('is')
|
|
503
|
+
logger.debug(self._cluster_data['flow_rate'][self._current_cluster-1])
|
|
504
|
+
# check if either was inactive (broke through already)
|
|
505
|
+
if self._cluster_data['active'][c-1] + self._cluster_data['active'][self._current_cluster-1]<2:
|
|
506
|
+
logger.debug('making clusters ')
|
|
507
|
+
logger.debug(c)
|
|
508
|
+
logger.debug('and')
|
|
509
|
+
logger.debug(self._current_cluster)
|
|
510
|
+
logger.debug('inactive due to one being inactive already')
|
|
511
|
+
logger.debug(self._cluster_data['active'][c-1])
|
|
512
|
+
logger.debug(self._cluster_data['active'][self._current_cluster-1])
|
|
513
|
+
# self._cluster_data['active'][self._current_cluster-1] = 0
|
|
514
|
+
self._cluster_data['active'][c-1] = 0
|
|
515
|
+
if self._timing:
|
|
516
|
+
self._cluster_data['haines_time'][c-1] = 100000000000000000000000000000000
|
|
517
|
+
logger.info(' ')
|
|
518
|
+
logger.info('CLUSTER MERGED WITH A BREAKTHROUGH CLUSTER')
|
|
519
|
+
logger.info('making cluster ')
|
|
520
|
+
logger.info(c)
|
|
521
|
+
logger.info('inactive due to merge')
|
|
522
|
+
# update the old cluster's activity and time
|
|
523
|
+
if self._timing:
|
|
524
|
+
self._cluster_data['haines_time'][c-1] = 100000000000000000000000000000000
|
|
525
|
+
self._cluster_data['active'][c-1] = 0
|
|
526
|
+
# NO IDEA WHAT THIS LINE DOES PLEASE HELP MAHMOUD
|
|
527
|
+
#self._tpoints[self._current_cluster-1] = list(k for k,v in itertools.groupby(self._tpoints[self._current_cluster-1]))
|
|
528
|
+
self._ppoints[c-1] = []
|
|
529
|
+
|
|
530
|
+
# remove else and do over the length of throats that are not filled:
|
|
531
|
+
# label invaded pore with current cluster
|
|
532
|
+
self._Pinv[pinvade] = self._current_cluster
|
|
533
|
+
self._NewPore = pinvade
|
|
534
|
+
|
|
535
|
+
# go through list of new invaded throats and assign to this cluster
|
|
536
|
+
# first remove all ThroatsInv
|
|
537
|
+
Throats = Throats[~sp.in1d(Throats,ThroatsInv)]
|
|
538
|
+
for i in Throats:
|
|
539
|
+
# set univaded throats, NewThroats
|
|
540
|
+
self._NewThroat = i # self._NewPore = Pores[self._Pinv[Pores][:,0]==0][0]
|
|
541
|
+
Pores = self._net.find_connected_pores(i)
|
|
542
|
+
pneighbor = Pores[Pores!=pinvade][0]
|
|
543
|
+
# if it's a boundary throat/pore, print a warning and skip to next i in for loop
|
|
544
|
+
if 'pore.boundary' in self._net.labels(pores=[pneighbor]):
|
|
545
|
+
logger.debug( ' ')
|
|
546
|
+
logger.debug( 'Throat: ')
|
|
547
|
+
logger.debug(self._NewThroat)
|
|
548
|
+
logger.debug('connected to pore: ')
|
|
549
|
+
logger.debug(pneighbor)
|
|
550
|
+
logger.debug(' is a boundary throat. Ignoring and moving on...')
|
|
551
|
+
continue
|
|
552
|
+
logger.debug( ' ')
|
|
553
|
+
logger.debug( 'INVADING THROATS: ')
|
|
554
|
+
logger.debug(self._NewThroat)
|
|
555
|
+
logger.debug('connected to Pore: ')
|
|
556
|
+
logger.debug(pneighbor)
|
|
557
|
+
# label that throat as invaded
|
|
558
|
+
self._Tinv[self._NewThroat] = self._current_cluster
|
|
559
|
+
self._Tinv_original[self._NewThroat] = self._current_cluster
|
|
560
|
+
if self._timing:
|
|
561
|
+
self._Ttime[self._NewThroat] = self._sim_time
|
|
562
|
+
self._tsequence[self._NewThroat] = self._tseq
|
|
563
|
+
if self._timing:
|
|
564
|
+
# update self._cluster_data.['throat_volume']
|
|
565
|
+
self._cluster_data['throat_volume'][self._current_cluster-1] += self._net['throat.'+self._throat_volume_name][self._NewThroat]
|
|
566
|
+
# Get the pore that this throat connects to
|
|
567
|
+
# Update interface list
|
|
568
|
+
# If the pore is not labelled as invaded by the cluster, it must be an interfacial pore
|
|
569
|
+
if (pneighbor not in self._plists[self._current_cluster-1]): # need extra [] to make a list if only one entry but screw up if multiple entries!!!
|
|
570
|
+
logger.debug( 'new pore:')
|
|
571
|
+
logger.debug(pneighbor)
|
|
572
|
+
logger.debug('connected throats:')
|
|
573
|
+
logger.debug(self._net.find_neighbor_throats(pneighbor))
|
|
574
|
+
# Add this pore data (pressure, number) to this cluster's "heap" of throat data.
|
|
575
|
+
# TODO --> eventually, generalize to capillary_pressure_name
|
|
576
|
+
heapq.heappush(self._ppoints[self._current_cluster-1],(self._phase['pore.Pc_entryImb'][pneighbor],pneighbor))
|
|
577
|
+
# Add new pore number to throat list for this cluster
|
|
578
|
+
# TODO for now, a pore can be in multiple plists (ie not yet invaded, but ready and willing) -- need to watch this
|
|
579
|
+
self._plists[self._current_cluster-1].append(pneighbor)
|
|
580
|
+
if self._timing:
|
|
581
|
+
# Update the cluster's vol_coef
|
|
582
|
+
self._cluster_data['vol_coef'][self._current_cluster-1] = self._cluster_data['vol_coef'][self._current_cluster-1]+self._Pvol_coef[pneighbor]
|
|
583
|
+
# Find next Haines Jump info
|
|
584
|
+
# Make sure you are not re-invading a throat
|
|
585
|
+
if self._ppoints[self._current_cluster-1] != []:
|
|
586
|
+
while self._Pinv[self._ppoints[self._current_cluster-1][0][1]] > 0:
|
|
587
|
+
premove = heapq.heappop(self._ppoints[self._current_cluster-1])[1]
|
|
588
|
+
if self._timing:
|
|
589
|
+
self._cluster_data['vol_coef'][self._current_cluster-1] = self._cluster_data['vol_coef'][self._current_cluster-1]-self._Pvol_coef[premove]
|
|
590
|
+
if self._ppoints[self._current_cluster-1] == []:
|
|
591
|
+
logger.debug( 'making cluster ')
|
|
592
|
+
logger.debug(self._current_cluster)
|
|
593
|
+
logger.debug('inactive due to ppoints = [] ')
|
|
594
|
+
self._cluster_data['active'][self._current_cluster-1] = 0
|
|
595
|
+
break
|
|
596
|
+
if self._ppoints[self._current_cluster-1] != []:
|
|
597
|
+
next_pore = self._ppoints[self._current_cluster-1][0][1]
|
|
598
|
+
self._cluster_data['haines_pore'][self._current_cluster-1] = next_pore
|
|
599
|
+
if self._timing:
|
|
600
|
+
self._cluster_data['haines_pressure'][self._current_cluster-1] = self._ppoints[self._current_cluster-1][0][0]
|
|
601
|
+
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] # PCAP!!
|
|
602
|
+
|
|
603
|
+
# Calculate the new Haines jump time
|
|
604
|
+
logger.debug( 'haines time before last stage:')
|
|
605
|
+
logger.debug( self._cluster_data['haines_time'])
|
|
606
|
+
if self._ppoints[self._current_cluster-1] == []:
|
|
607
|
+
logger.debug('making cluster ')
|
|
608
|
+
logger.debug(self._current_cluster)
|
|
609
|
+
logger.debug('inactive due to self._ppoints being empty for that cluster')
|
|
610
|
+
self._cluster_data['active'][self._current_cluster-1] = 0
|
|
611
|
+
if self._timing:
|
|
612
|
+
self._cluster_data['haines_time'][self._current_cluster-1] = 100000000000000000000000000000000
|
|
613
|
+
if self._timing:
|
|
614
|
+
if self._cluster_data['active'][self._current_cluster-1] == 1:
|
|
615
|
+
self._cluster_data['haines_time'][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]
|
|
616
|
+
if self._cluster_data['haines_time'][self._current_cluster-1] < self._sim_time:
|
|
617
|
+
self._cluster_data['haines_time'][self._current_cluster-1] = self._sim_time
|
|
618
|
+
logger.debug('haines time at the end of the pore stuff')
|
|
619
|
+
logger.debug(self._cluster_data['haines_time'])
|
|
620
|
+
|
|
621
|
+
def _condition_update(self):
|
|
622
|
+
# Calculate the distance between the new pore and outlet pores
|
|
623
|
+
if self._end_condition == 'breakthrough':
|
|
624
|
+
newpore_position = self._net['pore.coords'][self._NewPore]
|
|
625
|
+
dist_sqrd = (self._outlet_position-newpore_position)*(self._outlet_position-newpore_position)
|
|
626
|
+
if dist_sqrd[0].shape==(3,): # need to do this for MatFile networks because newpore_position is a nested array, not a vector (?)
|
|
627
|
+
dist_sqrd = dist_sqrd[0]
|
|
628
|
+
newpore_distance = np.sqrt(dist_sqrd[0]+dist_sqrd[1]+dist_sqrd[2])
|
|
629
|
+
logger.debug( 'newpore distance')
|
|
630
|
+
logger.debug( newpore_distance)
|
|
631
|
+
if newpore_distance < self._current_distance:
|
|
632
|
+
self._percent_complete = np.round((self._initial_distance-newpore_distance)/self._initial_distance*100, decimals = 1)
|
|
633
|
+
logger.info( 'percent complete')
|
|
634
|
+
logger.info( self._percent_complete)
|
|
635
|
+
self._current_distance = newpore_distance
|
|
636
|
+
elif self._end_condition == 'total':
|
|
637
|
+
self._percent_complete = np.round((np.sum(self._Pinv>0)/self._net.num_pores())*100, decimals = 1)
|
|
638
|
+
if self._percent_complete > self._rough_complete + self._rough_increment:
|
|
639
|
+
self._rough_complete = np.floor(self._percent_complete/self._rough_increment)*self._rough_increment
|
|
640
|
+
print(' IP algorithm at',np.int(self._rough_complete),'% completion at',np.int(np.round(clock())),'seconds')
|
|
641
|
+
|
|
642
|
+
|
|
643
|
+
# Determine if a new breakthrough position has occured
|
|
644
|
+
if self._NewPore in self._outlets:
|
|
645
|
+
logger.info( ' ')
|
|
646
|
+
logger.info( 'BREAKTHROUGH AT PORE: ')
|
|
647
|
+
logger.info(self._NewPore)
|
|
648
|
+
logger.info('in cluster ')
|
|
649
|
+
logger.info(self._current_cluster)
|
|
650
|
+
if self._timing:
|
|
651
|
+
logger.info('at time')
|
|
652
|
+
logger.info(self._sim_time)
|
|
653
|
+
pass
|
|
654
|
+
if self._end_condition == 'breakthrough':
|
|
655
|
+
self._cluster_data['active'][self._current_cluster-1] = 0
|
|
656
|
+
if self._timing:
|
|
657
|
+
self._cluster_data['haines_time'][self._current_cluster-1] = 100000000000000000000000000000000
|
|
658
|
+
elif self._end_condition == 'total':
|
|
659
|
+
self._brkevent.append(self._NewPore)
|
|
660
|
+
# if self._end_condition == 'total':
|
|
661
|
+
if np.sum(self._cluster_data['active']) == 0:
|
|
662
|
+
logger.info( ' ')
|
|
663
|
+
logger.info( 'SIMULATION FINISHED; no more active clusters')
|
|
664
|
+
if self._timing:
|
|
665
|
+
logger.info('at time')
|
|
666
|
+
logger.info(self._sim_time)
|
|
667
|
+
pass
|
|
668
|
+
self._condition = 0
|
|
669
|
+
print(' IP algorithm at 100% completion at ',np.int(np.round(clock())),' seconds')
|
|
670
|
+
# TODO Need to check how total condition will work, and end. All pores or all throats?
|
|
671
|
+
# self._condition = not self._Tinv.all()
|
|
672
|
+
|
|
673
|
+
def return_results(self,occupancy='occupancy',IPseq='None'):
|
|
674
|
+
r"""
|
|
675
|
+
"""
|
|
676
|
+
if IPseq=='None':
|
|
677
|
+
IPseq = self._pseq
|
|
678
|
+
|
|
679
|
+
try:
|
|
680
|
+
self._phase['pore.'+occupancy]=np.ravel(np.array(((self._psequence>0)&(self._psequence<=IPseq)),dtype=np.float))
|
|
681
|
+
self._phase['throat.'+occupancy]=np.ravel(np.array(((self._tsequence>0)&(self._tsequence<=IPseq)),dtype=np.float))
|
|
682
|
+
except:
|
|
683
|
+
print('Something bad happened while trying to update phase',self._phase.name)
|
|
684
|
+
try:
|
|
685
|
+
self._phase_def['pore.'+occupancy]=np.ravel(np.array(~((self._psequence>0)&(self._psequence<=IPseq)),dtype=np.float))
|
|
686
|
+
self._phase_def['throat.'+occupancy]=np.ravel(np.array(~((self._tsequence>0)&(self._tsequence<=IPseq)),dtype=np.float))
|
|
687
|
+
except:
|
|
688
|
+
print('A partner phase has not been set so inverse occupancy cannot be set')
|
|
689
|
+
|
|
690
|
+
if IPseq==self._pseq:
|
|
691
|
+
self._phase['pore.IP_inv_final']=np.ravel(np.array(self._Pinv,dtype=np.int))
|
|
692
|
+
self._phase['pore.IP_inv_original']=np.ravel(np.array(self._Pinv_original,dtype=np.int))
|
|
693
|
+
self._phase['throat.IP_inv']=np.ravel(np.array(self._Tinv,dtype=np.int))
|
|
694
|
+
self._phase['pore.IP_inv_seq']=np.ravel(np.array(self._psequence,dtype=np.int))
|
|
695
|
+
self._phase['throat.IP_inv_seq']=np.ravel(np.array(self._tsequence,dtype=np.int))
|
|
696
|
+
if self._timing:
|
|
697
|
+
self._phase['pore.IP_inv_time']=np.ravel(np.array(self._Ptime,dtype=np.float))
|
|
698
|
+
self._phase['throat.IP_inv_time']=np.ravel(np.array(self._Ttime,dtype=np.float))
|
|
699
|
+
|
|
700
|
+
if __name__ == '__main__':
|
|
701
|
+
import doctest
|
|
702
|
+
doctest.testmod(verbose=True)
|
|
703
|
+
|