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,156 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
===============================================================================
|
|
4
|
+
InvasionPercolationBasic: Simple IP
|
|
5
|
+
===============================================================================
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import scipy as sp
|
|
10
|
+
import bisect
|
|
11
|
+
from collections import deque
|
|
12
|
+
from OpenPNM.Algorithms import GenericAlgorithm
|
|
13
|
+
from OpenPNM.Base import logging
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
class InvasionPercolation(GenericAlgorithm):
|
|
17
|
+
r"""
|
|
18
|
+
A classic/basic invasion percolation algorithm optimized for speed.
|
|
19
|
+
|
|
20
|
+
Parameters
|
|
21
|
+
----------
|
|
22
|
+
network : OpenPNM Network object
|
|
23
|
+
The Network upon which the invasion should occur.
|
|
24
|
+
|
|
25
|
+
Notes
|
|
26
|
+
----
|
|
27
|
+
n/a
|
|
28
|
+
|
|
29
|
+
"""
|
|
30
|
+
|
|
31
|
+
def __init__(self,**kwargs):
|
|
32
|
+
r'''
|
|
33
|
+
|
|
34
|
+
'''
|
|
35
|
+
super(InvasionPercolation,self).__init__(**kwargs)
|
|
36
|
+
|
|
37
|
+
def run(self,phase,inlets,throat_prop='throat.capillary_pressure'):
|
|
38
|
+
r'''
|
|
39
|
+
Perform the algorithm
|
|
40
|
+
|
|
41
|
+
Parameters
|
|
42
|
+
----------
|
|
43
|
+
phase : OpenPNM Phase object
|
|
44
|
+
The phase to be injected into the Network. The Phase must have the
|
|
45
|
+
capillary entry pressure values for the system.
|
|
46
|
+
|
|
47
|
+
inlets : array_like
|
|
48
|
+
The list of inlet pores from which the Phase can enter the Network
|
|
49
|
+
|
|
50
|
+
throat_prop : string
|
|
51
|
+
The name of the throat property containing the capillary entry
|
|
52
|
+
pressure. The default is 'throat.capillary_pressure'.
|
|
53
|
+
|
|
54
|
+
'''
|
|
55
|
+
import heapq as hq
|
|
56
|
+
queue = []
|
|
57
|
+
hq.heapify(queue)
|
|
58
|
+
self._phase = phase
|
|
59
|
+
net = self._net
|
|
60
|
+
# Setup arrays and info
|
|
61
|
+
t_entry = phase[throat_prop]
|
|
62
|
+
t_sorted = sp.argsort(t_entry,axis=0) # Indices into t_entry giving a sorted list
|
|
63
|
+
t_order = sp.zeros_like(t_sorted)
|
|
64
|
+
t_order[t_sorted] = sp.arange(0,net.Nt) # Location in sorted list
|
|
65
|
+
t_inv = -sp.ones_like(net.Ts) # List for tracking throat invasion order
|
|
66
|
+
p_inv = -sp.ones_like(net.Ps) # List for tracking pore invasion order
|
|
67
|
+
p_inv[inlets] = 0 # Set inlet pores to invaded
|
|
68
|
+
# Perform initial analysis on input pores
|
|
69
|
+
Ts = net.find_neighbor_throats(pores=inlets)
|
|
70
|
+
[hq.heappush(queue,T) for T in t_order[Ts]] # Push the new throats to the heap
|
|
71
|
+
tcount = 1
|
|
72
|
+
while len(queue) > 0:
|
|
73
|
+
t = hq.heappop(queue) # Find throat at the top of the queue
|
|
74
|
+
t_next = t_sorted[t] # Extract actual throat number
|
|
75
|
+
t_inv[t_next] = tcount # Note invasion sequence
|
|
76
|
+
while (len(queue)>0) and (queue[0] == t): # If throat is duplicated
|
|
77
|
+
t = hq.heappop(queue) # Note: Preventing duplicate entries below might save some time here
|
|
78
|
+
Ps = net['throat.conns'][t_next] # Find pores connected to newly invaded throat
|
|
79
|
+
Ps = Ps[p_inv[Ps]<0] # Remove already invaded pores from Ps
|
|
80
|
+
if len(Ps)>0:
|
|
81
|
+
p_inv[Ps] = tcount # Note invasion sequence
|
|
82
|
+
Ts = net.find_neighbor_throats(pores=Ps) # Find connected throats
|
|
83
|
+
Ts = Ts[t_inv[Ts]<0] # Remove already invaded throats from Ts
|
|
84
|
+
[hq.heappush(queue,T) for T in t_order[Ts]] # Add new throats to queue
|
|
85
|
+
tcount += 1
|
|
86
|
+
self['throat.invasion_sequence'] = t_inv
|
|
87
|
+
self['pore.invasion_sequence'] = p_inv
|
|
88
|
+
|
|
89
|
+
def return_results(self,pores=[],throats=[]):
|
|
90
|
+
r'''
|
|
91
|
+
Places the results of the IP simulation into the Phase object.
|
|
92
|
+
|
|
93
|
+
Parameters
|
|
94
|
+
----------
|
|
95
|
+
pores and throats : array_like
|
|
96
|
+
The list of pores and throats whose values should be returned to
|
|
97
|
+
the Phase object. Default is all of them.
|
|
98
|
+
|
|
99
|
+
Returns
|
|
100
|
+
-------
|
|
101
|
+
invasion_sequence : array_like
|
|
102
|
+
The sequence in which each pore and throat is invaded This depends
|
|
103
|
+
on the inlet locations. All inlets are invaded at step 0. It is
|
|
104
|
+
possible to recontruct an animation of the invasion process, in
|
|
105
|
+
Paraview for instance, using this sequence information.
|
|
106
|
+
|
|
107
|
+
'''
|
|
108
|
+
pores = sp.array(pores,ndmin=1)
|
|
109
|
+
throats = sp.array(throats,ndmin=1)
|
|
110
|
+
if len(pores) == 0:
|
|
111
|
+
pores = self.Ps
|
|
112
|
+
if len(throats) == 0:
|
|
113
|
+
throats = self.Ts
|
|
114
|
+
self._phase['throat.invasion_sequence'] = sp.nan
|
|
115
|
+
self._phase['pore.invasion_sequence'] = sp.nan
|
|
116
|
+
self._phase['throat.invasion_sequence'][throats] = self['throat.invasion_sequence'][throats]
|
|
117
|
+
self._phase['pore.invasion_sequence'][pores] = self['pore.invasion_sequence'][pores]
|
|
118
|
+
|
|
119
|
+
def apply_flow(self,flowrate):
|
|
120
|
+
r'''
|
|
121
|
+
Convert the invaded sequence into an invaded time for a given flow rate
|
|
122
|
+
considering the volume of invaded pores and throats.
|
|
123
|
+
|
|
124
|
+
Parameters
|
|
125
|
+
----------
|
|
126
|
+
flowrate : float
|
|
127
|
+
The flow rate of the injected fluid
|
|
128
|
+
|
|
129
|
+
Returns
|
|
130
|
+
-------
|
|
131
|
+
Creates a throat array called 'invasion_time' in the Algorithm
|
|
132
|
+
dictionary
|
|
133
|
+
|
|
134
|
+
'''
|
|
135
|
+
P12 = self._net['throat.conns'] # List of throats conns
|
|
136
|
+
a = self['throat.invasion_sequence'] # Invasion sequence
|
|
137
|
+
b = sp.argsort(self['throat.invasion_sequence'])
|
|
138
|
+
P12_inv = self['pore.invasion_sequence'][P12] # Pore invasion sequence
|
|
139
|
+
# Find if the connected pores were invaded with or before each throat
|
|
140
|
+
P1_inv = P12_inv[:,0] == a
|
|
141
|
+
P2_inv = P12_inv[:,1] == a
|
|
142
|
+
c = sp.column_stack((P1_inv,P2_inv))
|
|
143
|
+
d = sp.sum(c,axis=1,dtype=bool) # List of Pores invaded with each throat
|
|
144
|
+
# Find volume of these pores
|
|
145
|
+
P12_vol = sp.zeros((self.Nt,))
|
|
146
|
+
P12_vol[d] = self._net['pore.volume'][P12[c]]
|
|
147
|
+
# Add invaded throat volume to pore volume (if invaded)
|
|
148
|
+
T_vol = P12_vol + self._net['throat.volume']
|
|
149
|
+
# Cumulative sum on the sorted throats gives cumulated inject volume
|
|
150
|
+
e = sp.cumsum(T_vol[b]/flowrate)
|
|
151
|
+
t = sp.zeros((self.Nt,))
|
|
152
|
+
t[b] = e # Convert back to original order
|
|
153
|
+
self._phase['throat.invasion_time'] = t
|
|
154
|
+
|
|
155
|
+
if __name__ == '__main__':
|
|
156
|
+
print('no tests yet')
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
===============================================================================
|
|
4
|
+
module __OhmicConduction__: Electronic or ionic conduction
|
|
5
|
+
===============================================================================
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
import scipy as sp
|
|
9
|
+
from OpenPNM.Algorithms import GenericLinearTransport
|
|
10
|
+
from OpenPNM.Base import logging
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
class OhmicConduction(GenericLinearTransport):
|
|
14
|
+
r'''
|
|
15
|
+
A subclass of GenericLinearTransport to simulate electron and ionic
|
|
16
|
+
conduction. The 2 main roles of this subclass are to set the default
|
|
17
|
+
property names and to implement a method for calculating the effective
|
|
18
|
+
conductivity of the network.
|
|
19
|
+
|
|
20
|
+
Examples
|
|
21
|
+
--------
|
|
22
|
+
>>> import OpenPNM
|
|
23
|
+
>>> pn = OpenPNM.Network.TestNet()
|
|
24
|
+
>>> geo = OpenPNM.Geometry.TestGeometry(network=pn,pores=pn.pores(),throats=pn.throats())
|
|
25
|
+
>>> phase1 = OpenPNM.Phases.TestPhase(network=pn)
|
|
26
|
+
>>> phys1 = OpenPNM.Physics.TestPhysics(network=pn, phase=phase1,pores=pn.pores(),throats=pn.throats())
|
|
27
|
+
>>> phys1['throat.electrical_conductance'] = 1
|
|
28
|
+
>>> alg = OpenPNM.Algorithms.OhmicConduction(network=pn, phase=phase1)
|
|
29
|
+
>>> BC1_pores = pn.pores('top')
|
|
30
|
+
>>> alg.set_boundary_conditions(bctype='Dirichlet', bcvalue=0.6, pores=BC1_pores)
|
|
31
|
+
>>> BC2_pores = pn.pores('bottom')
|
|
32
|
+
>>> alg.set_boundary_conditions(bctype='Dirichlet', bcvalue=0.4, pores=BC2_pores)
|
|
33
|
+
>>> alg.run()
|
|
34
|
+
>>> alg.return_results()
|
|
35
|
+
>>> Ceff = round(alg.calc_effective_conductivity(), 3)
|
|
36
|
+
>>> print(Ceff)
|
|
37
|
+
1.012
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
'''
|
|
41
|
+
|
|
42
|
+
def __init__(self,**kwargs):
|
|
43
|
+
r'''
|
|
44
|
+
'''
|
|
45
|
+
super(OhmicConduction,self).__init__(**kwargs)
|
|
46
|
+
logger.info('Create '+self.__class__.__name__+' Object')
|
|
47
|
+
|
|
48
|
+
def setup(self,conductance='electrical_conductance',quantity='voltage',super_pore_conductance=None,**params):
|
|
49
|
+
r'''
|
|
50
|
+
This setup provides the initial requirements for the solver setup.
|
|
51
|
+
'''
|
|
52
|
+
logger.info("Setup "+self.__class__.__name__)
|
|
53
|
+
super(OhmicConduction,self).setup(conductance=conductance,quantity=quantity,super_pore_conductance=super_pore_conductance)
|
|
54
|
+
|
|
55
|
+
def calc_effective_conductivity(self):
|
|
56
|
+
r'''
|
|
57
|
+
This calculates the effective electrical conductivity in this linear transport algorithm.
|
|
58
|
+
'''
|
|
59
|
+
return self._calc_eff_prop()
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
if __name__ == '__main__':
|
|
63
|
+
import doctest
|
|
64
|
+
doctest.testmod(verbose=True)
|
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
===============================================================================
|
|
4
|
+
module __OrdinaryPercolation__: Ordinary Percolation Algorithm
|
|
5
|
+
===============================================================================
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import scipy as sp
|
|
10
|
+
import numpy as np
|
|
11
|
+
import matplotlib.pyplot as plt
|
|
12
|
+
from OpenPNM.Algorithms import GenericAlgorithm
|
|
13
|
+
from OpenPNM.Base import logging
|
|
14
|
+
logger = logging.getLogger(__name__)
|
|
15
|
+
|
|
16
|
+
class OrdinaryPercolation(GenericAlgorithm):
|
|
17
|
+
r"""
|
|
18
|
+
Simulates a capillary drainage experiment by looping through a list of
|
|
19
|
+
capillary pressures.
|
|
20
|
+
|
|
21
|
+
Parameters
|
|
22
|
+
----------
|
|
23
|
+
network : OpenPNM Network Object
|
|
24
|
+
The network upon which the simulation will be run
|
|
25
|
+
|
|
26
|
+
invading_phase : OpenPNM Phase Object
|
|
27
|
+
The phase to be forced into the network at increasingly high pressures
|
|
28
|
+
|
|
29
|
+
defending_phase : OpenPNM Phase Object, optional
|
|
30
|
+
The phase originally residing in the network prior to invasion. This
|
|
31
|
+
is only necessary so that the pressure at which the phase is drained
|
|
32
|
+
can be stored on the phase.
|
|
33
|
+
|
|
34
|
+
name : string, optional
|
|
35
|
+
The name to assign to the Algorithm Object
|
|
36
|
+
|
|
37
|
+
Examples
|
|
38
|
+
--------
|
|
39
|
+
>>> import OpenPNM
|
|
40
|
+
>>> pn = OpenPNM.Network.TestNet()
|
|
41
|
+
>>> geo = OpenPNM.Geometry.TestGeometry(network=pn,pores=pn.pores(),throats=pn.throats())
|
|
42
|
+
>>> phase1 = OpenPNM.Phases.TestPhase(network=pn)
|
|
43
|
+
>>> phase2 = OpenPNM.Phases.TestPhase(network=pn)
|
|
44
|
+
>>> phys1 = OpenPNM.Physics.TestPhysics(network=pn, phase=phase1,pores=pn.pores(),throats=pn.throats())
|
|
45
|
+
>>> phys2 = OpenPNM.Physics.TestPhysics(network=pn, phase=phase2,pores=pn.pores(),throats=pn.throats())
|
|
46
|
+
>>> OP = OpenPNM.Algorithms.OrdinaryPercolation(network=pn,invading_phase=phase1, defending_phase=phase2)
|
|
47
|
+
>>> OP.run(inlets=pn.pores('top'))
|
|
48
|
+
>>> med_Pc = sp.median(OP['pore.inv_Pc'])
|
|
49
|
+
>>> OP.return_results(med_Pc)
|
|
50
|
+
>>> print(len(phase1.pores('occupancy'))) #should return '71' filled pores if everything is working normally
|
|
51
|
+
71
|
|
52
|
+
|
|
53
|
+
To run this algorithm, use 'setup()' to provide the necessary simulation
|
|
54
|
+
"""
|
|
55
|
+
|
|
56
|
+
def __init__(self,invading_phase=None,defending_phase=None,residual_pores=None,residual_throats=None,**kwargs):
|
|
57
|
+
r"""
|
|
58
|
+
|
|
59
|
+
"""
|
|
60
|
+
super(OrdinaryPercolation,self).__init__(**kwargs)
|
|
61
|
+
self._phase_inv = invading_phase
|
|
62
|
+
self._phase_def = defending_phase
|
|
63
|
+
self._residual_pores = residual_pores
|
|
64
|
+
self._residual_throats = residual_throats
|
|
65
|
+
|
|
66
|
+
logger.debug("Create Drainage Percolation Algorithm Object")
|
|
67
|
+
|
|
68
|
+
def run(self,
|
|
69
|
+
inlets,
|
|
70
|
+
npts=25,
|
|
71
|
+
inv_points=None,
|
|
72
|
+
capillary_pressure='capillary_pressure',
|
|
73
|
+
access_limited=True,
|
|
74
|
+
trapping=False,
|
|
75
|
+
**kwargs):
|
|
76
|
+
r'''
|
|
77
|
+
Parameters
|
|
78
|
+
----------
|
|
79
|
+
inlets : array_like
|
|
80
|
+
The list of pores which are the injection sources
|
|
81
|
+
|
|
82
|
+
npts : int, optional
|
|
83
|
+
The number of pressure points to apply. The list of pressures
|
|
84
|
+
is logarithmically spaced between the lowest and highest throat
|
|
85
|
+
entry pressures in the network.
|
|
86
|
+
|
|
87
|
+
inv_points : array_like, optional
|
|
88
|
+
A list of specific pressure points to apply.
|
|
89
|
+
|
|
90
|
+
access_limited : boolean
|
|
91
|
+
Only pores and throats connected to the inlet sites can be invaded
|
|
92
|
+
|
|
93
|
+
trapping : boolean
|
|
94
|
+
Wetting phase that is cut-off from the outlets becomes immobile.
|
|
95
|
+
If outlet pores have not been provided then this argument is
|
|
96
|
+
ignored.
|
|
97
|
+
|
|
98
|
+
Notes
|
|
99
|
+
-----
|
|
100
|
+
The 'inlet' pores are initially filled with invading fluid to start the
|
|
101
|
+
simulation. To avoid the capillary pressure curve showing a non-zero
|
|
102
|
+
starting saturation at low pressures, it is necessary to apply boundary
|
|
103
|
+
pores that have 0 volume, and set these as the inlets.
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
'''
|
|
107
|
+
# Parse params
|
|
108
|
+
self._inv_sites = inlets
|
|
109
|
+
self._npts = npts
|
|
110
|
+
self._p_cap = capillary_pressure # Name of throat entry pressure prop
|
|
111
|
+
self._AL = access_limited
|
|
112
|
+
self._TR = trapping
|
|
113
|
+
|
|
114
|
+
#Create pore and throat conditions lists to store inv_val at which each is invaded
|
|
115
|
+
self._p_inv = sp.zeros((self._net.num_pores(),),dtype=float)
|
|
116
|
+
self._p_inv.fill(sp.inf)
|
|
117
|
+
self._p_seq = sp.zeros_like(self._p_inv,dtype=int)
|
|
118
|
+
self._t_inv = sp.zeros((self._net.num_throats(),),dtype=float)
|
|
119
|
+
self._t_inv.fill(sp.inf)
|
|
120
|
+
self._t_seq = sp.zeros_like(self._t_inv,dtype=int)
|
|
121
|
+
#Determine the invasion pressures to apply
|
|
122
|
+
try:
|
|
123
|
+
self._t_cap = self._phase_inv['throat.'+self._p_cap]
|
|
124
|
+
except:
|
|
125
|
+
logger.error('Capillary pressure not assigned to invading phase '+self._phase_inv.name
|
|
126
|
+
+', check for capillary pressure in defending phase '+self._phase_def.name +' instead')
|
|
127
|
+
try:
|
|
128
|
+
self._t_cap = self._phase_def['throat.'+self._p_cap]
|
|
129
|
+
except:
|
|
130
|
+
pass
|
|
131
|
+
logger.error('Capillary pressure neither assigned to defending phase '+self._phase_def.name
|
|
132
|
+
+' nor to invading phase '+self._phase_inv.name)
|
|
133
|
+
if inv_points is None:
|
|
134
|
+
min_p = sp.amin(self._t_cap)*0.98 # nudge min_p down slightly
|
|
135
|
+
max_p = sp.amax(self._t_cap)*1.02 # bump max_p up slightly
|
|
136
|
+
logger.info('Generating list of invasion pressures')
|
|
137
|
+
if min_p == 0:
|
|
138
|
+
min_p = sp.linspace(min_p,max_p,self._npts)[1]
|
|
139
|
+
self._inv_points = sp.logspace(sp.log10(min_p),sp.log10(max_p),self._npts)
|
|
140
|
+
else:
|
|
141
|
+
self._inv_points = inv_points
|
|
142
|
+
self._do_outer_iteration_stage()
|
|
143
|
+
|
|
144
|
+
def _do_outer_iteration_stage(self):
|
|
145
|
+
#Generate curve from points
|
|
146
|
+
for inv_val in self._inv_points:
|
|
147
|
+
#Apply one applied pressure and determine invaded pores
|
|
148
|
+
logger.info('Applying capillary pressure: '+str(inv_val))
|
|
149
|
+
self._do_one_inner_iteration(inv_val)
|
|
150
|
+
#Store results using networks' get/set method
|
|
151
|
+
self['pore.inv_Pc'] = self._p_inv
|
|
152
|
+
self['throat.inv_Pc'] = self._t_inv
|
|
153
|
+
#Find invasion sequence values (to correspond with IP algorithm)
|
|
154
|
+
self._p_seq = sp.searchsorted(sp.unique(self._p_inv),self._p_inv)
|
|
155
|
+
self._t_seq = sp.searchsorted(sp.unique(self._t_inv),self._t_inv)
|
|
156
|
+
self['pore.inv_seq'] = self._p_seq
|
|
157
|
+
self['throat.inv_seq'] = self._t_seq
|
|
158
|
+
#Calculate Saturations
|
|
159
|
+
v_total = sp.sum(self._net['pore.volume'])+sp.sum(self._net['throat.volume'])
|
|
160
|
+
sat = 0.
|
|
161
|
+
self['pore.inv_sat'] = 1.
|
|
162
|
+
self['throat.inv_sat'] = 1.
|
|
163
|
+
for i in range(self._npts):
|
|
164
|
+
inv_pores = sp.where(self._p_seq==i)[0]
|
|
165
|
+
inv_throats = sp.where(self._t_seq==i)[0]
|
|
166
|
+
new_sat = (sum(self._net['pore.volume'][inv_pores])+sum(self._net['throat.volume'][inv_throats]))/v_total
|
|
167
|
+
sat += new_sat
|
|
168
|
+
self['pore.inv_sat'][inv_pores] = sat
|
|
169
|
+
self['throat.inv_sat'][inv_throats] = sat
|
|
170
|
+
|
|
171
|
+
def _do_one_inner_iteration(self,inv_val):
|
|
172
|
+
r"""
|
|
173
|
+
Determine which throats are invaded at a given applied capillary pressure
|
|
174
|
+
|
|
175
|
+
This function uses the scipy.csgraph module for the cluster labeling algorithm (connected_components)
|
|
176
|
+
|
|
177
|
+
"""
|
|
178
|
+
#Generate a tlist containing boolean values for throat state
|
|
179
|
+
Tinvaded = self._t_cap<=inv_val
|
|
180
|
+
#if self._residual_throats is not None:
|
|
181
|
+
# Tinvaded[self._residual_throats]=True
|
|
182
|
+
#Finding all pores that can be invaded at specified pressure
|
|
183
|
+
clusters = self._net.find_clusters(Tinvaded)
|
|
184
|
+
#Find all pores with at least 1 invaded throat (invaded)
|
|
185
|
+
Pinvaded = sp.zeros_like(clusters,dtype=bool)
|
|
186
|
+
Ts = self._net.throats()
|
|
187
|
+
P12 = self._net.find_connected_pores(Ts)
|
|
188
|
+
temp = P12[Tinvaded]
|
|
189
|
+
temp = sp.hstack((temp[:,0],temp[:,1]))
|
|
190
|
+
Pinvaded[temp] = True
|
|
191
|
+
#if self._residual_pores is not None:
|
|
192
|
+
# Pinvaded[self._residual_pores]=True
|
|
193
|
+
if self._AL:
|
|
194
|
+
#Add injection sites to Pinvaded
|
|
195
|
+
Pinvaded[self._inv_sites] = True
|
|
196
|
+
#Clean up clusters (not invaded = -1, invaded >=0)
|
|
197
|
+
clusters = clusters*(Pinvaded) - (~Pinvaded)
|
|
198
|
+
#Identify clusters connected to invasion sites
|
|
199
|
+
inv_clusters = sp.unique(clusters[self._inv_sites])
|
|
200
|
+
else:
|
|
201
|
+
#Clean up clusters (not invaded = -1, invaded >=0)
|
|
202
|
+
clusters = clusters*(Pinvaded) - (~Pinvaded)
|
|
203
|
+
#All clusters are invasion sites
|
|
204
|
+
inv_clusters = sp.r_[0:self._net.num_pores()]
|
|
205
|
+
#Store invasion pressure in pores and throats
|
|
206
|
+
pmask = np.in1d(clusters,inv_clusters)
|
|
207
|
+
#if self._residual_pores is not None:
|
|
208
|
+
# pmask[self._residual_pores]==True
|
|
209
|
+
#Store result of invasion step
|
|
210
|
+
self._p_inv[(self._p_inv==sp.inf)*(pmask)] = inv_val
|
|
211
|
+
#Determine Pc_invaded for throats as well
|
|
212
|
+
temp = self._net['throat.conns']
|
|
213
|
+
tmask = (pmask[temp[:,0]] + pmask[temp[:,1]])*(Tinvaded)
|
|
214
|
+
self._t_inv[(self._t_inv==sp.inf)*(tmask)] = inv_val
|
|
215
|
+
|
|
216
|
+
def evaluate_trapping(self, outlets):
|
|
217
|
+
r"""
|
|
218
|
+
Finds trapped pores and throats after a full ordinary
|
|
219
|
+
percolation drainage has been run
|
|
220
|
+
|
|
221
|
+
Parameters
|
|
222
|
+
----------
|
|
223
|
+
outlets : array_like
|
|
224
|
+
A list of pores that define the wetting phase outlets.
|
|
225
|
+
Disconnection from these outlets results in trapping.
|
|
226
|
+
|
|
227
|
+
"""
|
|
228
|
+
self._p_trap = sp.zeros_like(self._p_inv, dtype=float)
|
|
229
|
+
self._t_trap = sp.zeros_like(self._t_inv, dtype=float)
|
|
230
|
+
try:
|
|
231
|
+
inv_points = sp.unique(self._p_inv) # Get points used in OP
|
|
232
|
+
except:
|
|
233
|
+
logger.error('Orindary percolation has not been run!')
|
|
234
|
+
raise Exception('Aborting algorithm')
|
|
235
|
+
tind = self._net.throats()
|
|
236
|
+
conns = self._net.find_connected_pores(tind)
|
|
237
|
+
for inv_val in inv_points[0:-1]:
|
|
238
|
+
#Find clusters of defender pores
|
|
239
|
+
Pinvaded = self._p_inv <= inv_val
|
|
240
|
+
Cstate = sp.sum(Pinvaded[conns], axis=1)
|
|
241
|
+
Tinvaded = self._t_inv <= inv_val
|
|
242
|
+
Cstate = Cstate + Tinvaded #0 = all open, 1=1 pore filled, 2=2 pores filled 3=2 pores + 1 throat filled
|
|
243
|
+
clusters = self._net.find_clusters(Cstate==0)
|
|
244
|
+
##Clean up clusters (invaded = -1, defended >=0)
|
|
245
|
+
clusters = clusters*(~Pinvaded) - (Pinvaded)
|
|
246
|
+
#Identify clusters connected to outlet sites
|
|
247
|
+
out_clusters = sp.unique(clusters[outlets])
|
|
248
|
+
trapped_pores = ~sp.in1d(clusters, out_clusters)
|
|
249
|
+
trapped_pores[Pinvaded]=False
|
|
250
|
+
if sum(trapped_pores) > 0:
|
|
251
|
+
self._p_trap[(self._p_trap == 0)*trapped_pores] = inv_val
|
|
252
|
+
trapped_throats = self._net.find_neighbor_throats(trapped_pores)
|
|
253
|
+
trapped_throat_array=np.asarray([False]*len(Cstate))
|
|
254
|
+
trapped_throat_array[trapped_throats]=True
|
|
255
|
+
self._t_trap[(self._t_trap == 0)*trapped_throat_array] = inv_val
|
|
256
|
+
self._t_trap[(self._t_trap == 0)*(Cstate==2)] = inv_val
|
|
257
|
+
self._p_inv[self._p_trap > 0] = sp.inf
|
|
258
|
+
self._t_inv[self._t_trap > 0] = sp.inf
|
|
259
|
+
self['pore.inv_Pc']=self._p_inv
|
|
260
|
+
self['throat.inv_Pc']=self._t_inv
|
|
261
|
+
|
|
262
|
+
def return_results(self, Pc=0, seq=None, sat=None, occupancy='occupancy'):
|
|
263
|
+
r"""
|
|
264
|
+
Updates the occupancy status of invading and defending phases
|
|
265
|
+
as determined by the OP algorithm
|
|
266
|
+
|
|
267
|
+
"""
|
|
268
|
+
p_inv = self['pore.inv_Pc']
|
|
269
|
+
self._phase_inv['pore.inv_Pc']=p_inv
|
|
270
|
+
t_inv = self['throat.inv_Pc']
|
|
271
|
+
self._phase_inv['throat.inv_Pc']=t_inv
|
|
272
|
+
#Apply invasion sequence values (to correspond with IP algorithm)
|
|
273
|
+
p_seq = self['pore.inv_seq']
|
|
274
|
+
self._phase_inv['pore.inv_seq']=p_seq
|
|
275
|
+
t_seq = self['throat.inv_seq']
|
|
276
|
+
self._phase_inv['throat.inv_seq']=t_seq
|
|
277
|
+
#Apply saturation to pores and throats
|
|
278
|
+
self._phase_inv['pore.inv_sat']=self['pore.inv_sat']
|
|
279
|
+
self._phase_inv['throat.inv_sat']=self['throat.inv_sat']
|
|
280
|
+
|
|
281
|
+
|
|
282
|
+
if(sat is not None):
|
|
283
|
+
p_inv = self['pore.inv_sat']<=sat
|
|
284
|
+
t_inv = self['throat.inv_sat']<=sat
|
|
285
|
+
#Apply occupancy to invading phase
|
|
286
|
+
temp = sp.array(p_inv,dtype=sp.float_,ndmin=1)
|
|
287
|
+
self._phase_inv['pore.'+occupancy]=temp
|
|
288
|
+
temp = sp.array(t_inv,dtype=sp.float_,ndmin=1)
|
|
289
|
+
self._phase_inv['throat.'+occupancy]=temp
|
|
290
|
+
#Apply occupancy to defending phase
|
|
291
|
+
if self._phase_def is not None:
|
|
292
|
+
temp = sp.array(~p_inv,dtype=sp.float_,ndmin=1)
|
|
293
|
+
self._phase_def['pore.'+occupancy]=temp
|
|
294
|
+
temp = sp.array(~t_inv,dtype=sp.float_,ndmin=1)
|
|
295
|
+
self._phase_def['throat.'+occupancy]=temp
|
|
296
|
+
elif(seq is not None):
|
|
297
|
+
p_seq = self['pore.inv_seq']<=seq
|
|
298
|
+
t_seq = self['throat.inv_seq']<=seq
|
|
299
|
+
#Apply occupancy to invading phase
|
|
300
|
+
temp = sp.array(p_seq,dtype=sp.float_,ndmin=1)
|
|
301
|
+
self._phase_inv['pore.'+occupancy]=temp
|
|
302
|
+
temp = sp.array(t_seq,dtype=sp.float_,ndmin=1)
|
|
303
|
+
self._phase_inv['throat.'+occupancy]=temp
|
|
304
|
+
#Apply occupancy to defending phase
|
|
305
|
+
if self._phase_def is not None:
|
|
306
|
+
temp = sp.array(~p_seq,dtype=sp.float_,ndmin=1)
|
|
307
|
+
self._phase_def['pore.'+occupancy]=temp
|
|
308
|
+
temp = sp.array(~t_seq,dtype=sp.float_,ndmin=1)
|
|
309
|
+
self._phase_def['throat.'+occupancy]=temp
|
|
310
|
+
else:
|
|
311
|
+
p_inv = self['pore.inv_Pc']<=Pc
|
|
312
|
+
t_inv = self['throat.inv_Pc']<=Pc
|
|
313
|
+
#Apply occupancy to invading phase
|
|
314
|
+
temp = sp.array(p_inv,dtype=sp.float_,ndmin=1)
|
|
315
|
+
self._phase_inv['pore.'+occupancy]=temp
|
|
316
|
+
temp = sp.array(t_inv,dtype=sp.float_,ndmin=1)
|
|
317
|
+
self._phase_inv['throat.'+occupancy]=temp
|
|
318
|
+
#Apply occupancy to defending phase
|
|
319
|
+
if self._phase_def is not None:
|
|
320
|
+
temp = sp.array(~p_inv,dtype=sp.float_,ndmin=1)
|
|
321
|
+
self._phase_def['pore.'+occupancy]=temp
|
|
322
|
+
temp = sp.array(~t_inv,dtype=sp.float_,ndmin=1)
|
|
323
|
+
self._phase_def['throat.'+occupancy]=temp
|
|
324
|
+
|
|
325
|
+
|
|
326
|
+
|
|
327
|
+
def plot_drainage_curve(self,
|
|
328
|
+
pore_volume='volume',
|
|
329
|
+
throat_volume='volume',pore_label='all',throat_label='all'):
|
|
330
|
+
r"""
|
|
331
|
+
Plot drainage capillary pressure curve
|
|
332
|
+
"""
|
|
333
|
+
try:
|
|
334
|
+
PcPoints = sp.unique(self['pore.inv_Pc'])
|
|
335
|
+
except:
|
|
336
|
+
raise Exception('Cannot print drainage curve: ordinary percolation simulation has not been run')
|
|
337
|
+
pores=self._net.pores(labels=pore_label)
|
|
338
|
+
throats = self._net.throats(labels=throat_label)
|
|
339
|
+
Snwp_t = sp.zeros_like(PcPoints)
|
|
340
|
+
Snwp_p = sp.zeros_like(PcPoints)
|
|
341
|
+
Pvol = self._net['pore.'+pore_volume]
|
|
342
|
+
Tvol = self._net['throat.'+throat_volume]
|
|
343
|
+
Pvol_tot = sum(Pvol)
|
|
344
|
+
Tvol_tot = sum(Tvol)
|
|
345
|
+
for i in range(0,sp.size(PcPoints)):
|
|
346
|
+
Pc = PcPoints[i]
|
|
347
|
+
Snwp_p[i] = sum(Pvol[self._p_inv[pores]<=Pc])/Pvol_tot
|
|
348
|
+
Snwp_t[i] = sum(Tvol[self._t_inv[throats]<=Pc])/Tvol_tot
|
|
349
|
+
if sp.mean(self._phase_inv["pore.contact_angle"]) < 90:
|
|
350
|
+
Snwp_p = 1 - Snwp_p
|
|
351
|
+
Snwp_t = 1 - Snwp_t
|
|
352
|
+
PcPoints *= -1
|
|
353
|
+
plt.plot(PcPoints,Snwp_p,'r.-')
|
|
354
|
+
plt.plot(PcPoints,Snwp_t,'b.-')
|
|
355
|
+
r'''
|
|
356
|
+
TODO: Add legend to distinguish the pore and throat curves
|
|
357
|
+
'''
|
|
358
|
+
#plt.xlim(xmin=0)
|
|
359
|
+
plt.show()
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
def plot_primary_drainage_curve(self,
|
|
363
|
+
pore_volume='volume',
|
|
364
|
+
throat_volume='volume',
|
|
365
|
+
pore_label='all',
|
|
366
|
+
throat_label='all'):
|
|
367
|
+
r"""
|
|
368
|
+
Plot the primary drainage curve as the capillary pressure on ordinate
|
|
369
|
+
and total saturation of the wetting phase on the abscissa.
|
|
370
|
+
This is the preffered style in the petroleum engineering
|
|
371
|
+
"""
|
|
372
|
+
try:
|
|
373
|
+
PcPoints = sp.unique(self['pore.inv_Pc'])
|
|
374
|
+
except:
|
|
375
|
+
raise Exception('Cannot print drainage curve: ordinary percolation simulation has not been run')
|
|
376
|
+
pores=self._net.pores(labels=pore_label)
|
|
377
|
+
throats = self._net.throats(labels=throat_label)
|
|
378
|
+
Snwp_t = sp.zeros_like(PcPoints)
|
|
379
|
+
Snwp_p = sp.zeros_like(PcPoints)
|
|
380
|
+
Snwp_all = sp.zeros_like(PcPoints)
|
|
381
|
+
Swp_all = sp.zeros_like(PcPoints)
|
|
382
|
+
Pvol = self._net['pore.'+pore_volume]
|
|
383
|
+
Tvol = self._net['throat.'+throat_volume]
|
|
384
|
+
Pvol_tot = sum(Pvol)
|
|
385
|
+
Tvol_tot = sum(Tvol)
|
|
386
|
+
for i in range(0,sp.size(PcPoints)):
|
|
387
|
+
Pc = PcPoints[i]
|
|
388
|
+
Snwp_p[i] = sum(Pvol[self._p_inv[pores]<=Pc])/Pvol_tot
|
|
389
|
+
Snwp_t[i] = sum(Tvol[self._t_inv[throats]<=Pc])/Tvol_tot
|
|
390
|
+
Snwp_all[i] = (sum(Tvol[self._t_inv[throats]<=Pc])+sum(Pvol[self._p_inv[pores]<=Pc]))/(Tvol_tot+Pvol_tot)
|
|
391
|
+
Swp_all[i] = 1 - Snwp_all[i]
|
|
392
|
+
plt.plot(Swp_all,PcPoints,'k.-')
|
|
393
|
+
plt.xlim(xmin=0)
|
|
394
|
+
plt.xlabel('Saturation of wetting phase')
|
|
395
|
+
plt.ylabel('Capillary Pressure [Pa]')
|
|
396
|
+
plt.title('Primay Drainage Curve')
|
|
397
|
+
plt.grid(True)
|
|
398
|
+
plt.show()
|
|
399
|
+
|
|
400
|
+
if __name__ == '__main__':
|
|
401
|
+
import doctest
|
|
402
|
+
doctest.testmod(verbose=True)
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
===============================================================================
|
|
4
|
+
module __StokesFlow__: Viscous fluid flow
|
|
5
|
+
===============================================================================
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
import scipy as sp
|
|
9
|
+
from OpenPNM.Algorithms import GenericLinearTransport
|
|
10
|
+
from OpenPNM.Base import logging
|
|
11
|
+
logger = logging.getLogger(__name__)
|
|
12
|
+
|
|
13
|
+
class StokesFlow(GenericLinearTransport):
|
|
14
|
+
r'''
|
|
15
|
+
A subclass of GenericLinearTransport to simulate viscous flow. The 2
|
|
16
|
+
main roles of this subclass are to set the default property names and to
|
|
17
|
+
implement a method for calculating the hydraulic permeability of the network.
|
|
18
|
+
|
|
19
|
+
Examples
|
|
20
|
+
--------
|
|
21
|
+
>>> import OpenPNM
|
|
22
|
+
>>> pn = OpenPNM.Network.TestNet()
|
|
23
|
+
>>> geo = OpenPNM.Geometry.TestGeometry(network=pn,pores=pn.pores(),throats=pn.throats())
|
|
24
|
+
>>> phase1 = OpenPNM.Phases.TestPhase(network=pn)
|
|
25
|
+
>>> phys1 = OpenPNM.Physics.TestPhysics(network=pn, phase=phase1,pores=pn.pores(),throats=pn.throats())
|
|
26
|
+
>>> alg = OpenPNM.Algorithms.StokesFlow(network=pn, phase=phase1)
|
|
27
|
+
>>> BC1_pores = pn.pores('top')
|
|
28
|
+
>>> alg.set_boundary_conditions(bctype='Dirichlet', bcvalue=0.6, pores=BC1_pores)
|
|
29
|
+
>>> BC2_pores = pn.pores('bottom')
|
|
30
|
+
>>> alg.set_boundary_conditions(bctype='Dirichlet', bcvalue=0.4, pores=BC2_pores)
|
|
31
|
+
>>> alg.run()
|
|
32
|
+
>>> alg.return_results()
|
|
33
|
+
>>> Peff = round(alg.calc_eff_permeability(), 10)
|
|
34
|
+
>>> print(Peff) #unless something changed with our test objects, this should print "1.8663e-05"
|
|
35
|
+
1.8663e-05
|
|
36
|
+
|
|
37
|
+
'''
|
|
38
|
+
|
|
39
|
+
def __init__(self,**kwargs):
|
|
40
|
+
r'''
|
|
41
|
+
'''
|
|
42
|
+
super(StokesFlow,self).__init__(**kwargs)
|
|
43
|
+
logger.info('Create '+self.__class__.__name__+' Object')
|
|
44
|
+
|
|
45
|
+
def setup(self,conductance='hydraulic_conductance',quantity='pressure',super_pore_conductance=None,**params):
|
|
46
|
+
r'''
|
|
47
|
+
This setup provides the initial requirements for the solver setup.
|
|
48
|
+
'''
|
|
49
|
+
logger.info("Setup "+self.__class__.__name__)
|
|
50
|
+
super(StokesFlow,self).setup(conductance=conductance,quantity=quantity,super_pore_conductance=super_pore_conductance)
|
|
51
|
+
|
|
52
|
+
def calc_eff_permeability(self):
|
|
53
|
+
r'''
|
|
54
|
+
This calculates the effective permeability in this linear transport algorithm.
|
|
55
|
+
'''
|
|
56
|
+
D_normal = self._calc_eff_prop()
|
|
57
|
+
self._eff_property = D_normal*sp.mean(self._phase['pore.viscosity'])
|
|
58
|
+
return self._eff_property
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
if __name__ == '__main__':
|
|
62
|
+
import doctest
|
|
63
|
+
doctest.testmod(verbose=True)
|
|
64
|
+
|