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,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
+