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,641 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""
|
|
3
|
+
===============================================================================
|
|
4
|
+
module __GenericLinearTransport__: Class for solving linear transport processes
|
|
5
|
+
===============================================================================
|
|
6
|
+
|
|
7
|
+
"""
|
|
8
|
+
import scipy as sp
|
|
9
|
+
import scipy.sparse as sprs
|
|
10
|
+
import scipy.sparse.linalg as sprslin
|
|
11
|
+
from OpenPNM.Algorithms import GenericAlgorithm
|
|
12
|
+
from OpenPNM.Phases import GenericPhase
|
|
13
|
+
import OpenPNM.Utilities.vertexops as vo
|
|
14
|
+
from OpenPNM.Base import logging
|
|
15
|
+
logger = logging.getLogger(__name__)
|
|
16
|
+
|
|
17
|
+
class GenericLinearTransport(GenericAlgorithm):
|
|
18
|
+
r"""
|
|
19
|
+
This class provides essential methods for building and solving matrices
|
|
20
|
+
in a transport process. It is inherited by FickianDiffusion,
|
|
21
|
+
FourierConduction, StokesFlow and OhmicConduction.
|
|
22
|
+
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def __init__(self,phase=None,**kwargs):
|
|
26
|
+
r'''
|
|
27
|
+
Initializing the class
|
|
28
|
+
'''
|
|
29
|
+
super(GenericLinearTransport,self).__init__(**kwargs)
|
|
30
|
+
if phase is None:
|
|
31
|
+
self._phase = GenericPhase()
|
|
32
|
+
else:
|
|
33
|
+
self._phase = phase # Register phase with self
|
|
34
|
+
if sp.size(phase)!=1: self._phases = phase
|
|
35
|
+
else: self._phases.append(phase)
|
|
36
|
+
|
|
37
|
+
def setup(self,conductance,quantity,super_pore_conductance):
|
|
38
|
+
r'''
|
|
39
|
+
This setup provides the initial data for the solver from the provided properties.
|
|
40
|
+
It also creates the matrices A and b.
|
|
41
|
+
'''
|
|
42
|
+
# For each group of pores with Neumann_group BC, user can send a value for the conductance between that group and its corresponding super pore
|
|
43
|
+
if super_pore_conductance is None: self.super_pore_conductance = []
|
|
44
|
+
else: self.super_pore_conductance = super_pore_conductance
|
|
45
|
+
# Providing conductance values for the algorithm from the Physics name
|
|
46
|
+
if sp.size(self._phase)==1:
|
|
47
|
+
self._conductance = 'throat.'+conductance.split('.')[-1]
|
|
48
|
+
self._quantity = 'pore.'+self._phase.name+'_'+quantity.split('.')[-1]
|
|
49
|
+
#Check health of conductance vector
|
|
50
|
+
if self._phase.check_data_health(props=self._conductance).health:
|
|
51
|
+
self['throat.conductance'] = self._phase[self._conductance]
|
|
52
|
+
else:
|
|
53
|
+
raise Exception('The provided throat conductance has problems')
|
|
54
|
+
else:
|
|
55
|
+
raise Exception('The linear transport solver accepts just one phase.')
|
|
56
|
+
|
|
57
|
+
# Checking for the values from the linear terms which might be added to the coeff diagonal or RHS
|
|
58
|
+
diag_added_data = sp.zeros(self.Np)
|
|
59
|
+
RHS_added_data = sp.zeros(self.Np)
|
|
60
|
+
for label in self.labels():
|
|
61
|
+
if 'pore.source_' in label:
|
|
62
|
+
source_name = 'pore.'+(label.split('.')[-1]).replace('source_',"")
|
|
63
|
+
matching_physics = [phys for phys in self._phase._physics if source_name in phys.models.keys()]
|
|
64
|
+
for phys in matching_physics:
|
|
65
|
+
x = phys.models[source_name]['x']
|
|
66
|
+
if x!='' and type(x)==str:
|
|
67
|
+
if x.split('.')[-1]!=quantity.split('.')[-1]:
|
|
68
|
+
raise Exception('The quantity(pore.'+x.split('.')[-1]+'), provided by source term('+source_name+'), is different from the main quantity(pore.'+quantity.split('.')[-1]+') in '+self.name+' algorithm.')
|
|
69
|
+
source_name = label.replace('pore.source_',"")
|
|
70
|
+
if 'pore.source_linear_s1_'+source_name in self.props():
|
|
71
|
+
prop1 = 'pore.source_linear_s1_'+source_name
|
|
72
|
+
pores = -sp.isnan(self[prop1])
|
|
73
|
+
diag_added_data[pores] = diag_added_data[pores] + self[prop1][pores]
|
|
74
|
+
prop2 = 'pore.source_linear_s2_'+source_name
|
|
75
|
+
pores = -sp.isnan(self[prop2])
|
|
76
|
+
RHS_added_data[pores] = RHS_added_data[pores] + self[prop2][pores]
|
|
77
|
+
# Creating A and b based on the conductance values and new linear terms
|
|
78
|
+
logger.info("Creating Coefficient matrix for the algorithm")
|
|
79
|
+
self.A = self._build_coefficient_matrix(modified_diag_pores = self.Ps, diag_added_data = diag_added_data)
|
|
80
|
+
logger.info("Creating RHS matrix for the algorithm")
|
|
81
|
+
self.b = self._build_RHS_matrix(modified_RHS_pores = self.Ps,RHS_added_data = -RHS_added_data)
|
|
82
|
+
|
|
83
|
+
def set_source_term(self,source_name=None,pores=None,x0=None,tol=None,maxiter=None,mode='merge'):
|
|
84
|
+
r'''
|
|
85
|
+
Apply source terms to specified pores
|
|
86
|
+
|
|
87
|
+
Parameters
|
|
88
|
+
----------
|
|
89
|
+
source_name : string
|
|
90
|
+
Specifies the name of source term from a Physics object to apply.
|
|
91
|
+
pores : array_like
|
|
92
|
+
The pores where the boundary conditions should be applied
|
|
93
|
+
x0 : array_like, optional
|
|
94
|
+
By sending guess values for the quantity, the method calculates the source terms and stores them in the algorithm
|
|
95
|
+
tol : float, optional
|
|
96
|
+
Tolerance for the iterative method. (if maxiter>0)
|
|
97
|
+
mode : string, optional
|
|
98
|
+
Controls how the source terms should be applied. Options are:
|
|
99
|
+
- 'merge': Inserts specified values, leaving existing values elsewhere
|
|
100
|
+
- 'overwrite': Inserts specified values, clearing all other values
|
|
101
|
+
- 'remove': Removes boundary conditions from specified locations
|
|
102
|
+
- 'update': Allows to insert specified values to new locations, updating existing ones
|
|
103
|
+
maxiter: integer
|
|
104
|
+
Maximum number of iterations for this source term. Iteration will stop after maxiter steps.
|
|
105
|
+
Notes
|
|
106
|
+
-----
|
|
107
|
+
Difference between 'merge' and 'update' modes: in the merge, a new value cannot be applied to a pore with existing one, but in the 'update' it is possible.
|
|
108
|
+
'''
|
|
109
|
+
if mode not in ['merge','overwrite','remove','update']:
|
|
110
|
+
raise Exception('The mode ('+mode+') cannot be applied to the set_source_term!')
|
|
111
|
+
# Checking for existance of source_name
|
|
112
|
+
if source_name is not None:
|
|
113
|
+
s_group= sp.array(source_name,ndmin=1)
|
|
114
|
+
for source_name in s_group:
|
|
115
|
+
source_name = 'pore.'+source_name.split('.')[-1]
|
|
116
|
+
prop = source_name.split('.')[-1]
|
|
117
|
+
try: self._phase[source_name]
|
|
118
|
+
except KeyError: Exception('The attached phase in the algorithm '+self.name+', does not have the source property '+source_name+' in its physics!')
|
|
119
|
+
except ValueError: pass
|
|
120
|
+
if mode=='remove':
|
|
121
|
+
s_mode = ['linear','nonlinear']
|
|
122
|
+
if source_name is None:
|
|
123
|
+
if pores is not None:
|
|
124
|
+
if pores is 'all':
|
|
125
|
+
for item in self.labels():
|
|
126
|
+
if 'pore.source_' in item:
|
|
127
|
+
prop = (item.split('.')[-1]).replace('source_',"")
|
|
128
|
+
del self['pore.source_'+prop]
|
|
129
|
+
for s in s_mode:
|
|
130
|
+
try: del self['pore.source_'+s+'_s1_'+prop]
|
|
131
|
+
except: pass
|
|
132
|
+
try: del self['pore.source_'+s+'_s2_'+prop]
|
|
133
|
+
except: pass
|
|
134
|
+
else:
|
|
135
|
+
for item in self.labels():
|
|
136
|
+
if 'pore.source_' in item:
|
|
137
|
+
prop = (item.split('.')[-1]).replace('source_',"")
|
|
138
|
+
self['pore.source_'+prop][pores] = False
|
|
139
|
+
for s in s_mode:
|
|
140
|
+
try: self['pore.source_'+s+'_s1_'+prop][pores] = sp.nan
|
|
141
|
+
except: pass
|
|
142
|
+
try: self['pore.source_'+s+'_s2_'+prop][pores] = sp.nan
|
|
143
|
+
except: pass
|
|
144
|
+
else: raise Exception('No pores/source_name are sent to the set_term_method!')
|
|
145
|
+
else:
|
|
146
|
+
if pores is None:
|
|
147
|
+
try: del self['pore.source_'+prop]
|
|
148
|
+
except: pass
|
|
149
|
+
for s in s_mode:
|
|
150
|
+
try: del self['pore.source_'+s+'_s1_'+prop]
|
|
151
|
+
except: pass
|
|
152
|
+
try: del self['pore.source_'+s+'_s2_'+prop]
|
|
153
|
+
except: pass
|
|
154
|
+
else:
|
|
155
|
+
try: self['pore.source_'+prop][pores] = False
|
|
156
|
+
except: pass
|
|
157
|
+
for s in s_mode:
|
|
158
|
+
try: self['pore.source_'+s+'_s1_'+prop][pores] = sp.nan
|
|
159
|
+
except: pass
|
|
160
|
+
try: self['pore.source_'+s+'_s2_'+prop][pores] = sp.nan
|
|
161
|
+
except: pass
|
|
162
|
+
else:
|
|
163
|
+
# Handle tol, x0 and maxiter for the Picard algorithm
|
|
164
|
+
if 'pore.source_tol' not in self.props():
|
|
165
|
+
self['pore.source_tol'] = sp.ones((self.Np,),dtype=float)*sp.nan
|
|
166
|
+
if 'pore.source_maxiter' not in self.props():
|
|
167
|
+
self['pore.source_maxiter'] = sp.ones((self.Np,),dtype=float)*sp.nan
|
|
168
|
+
|
|
169
|
+
if x0 is None : x0 = 0
|
|
170
|
+
self._guess = x0
|
|
171
|
+
# Check value of maxiter
|
|
172
|
+
if maxiter is None:
|
|
173
|
+
maxiter = int(100)
|
|
174
|
+
source_mode = 'nonlinear'
|
|
175
|
+
else:
|
|
176
|
+
try:
|
|
177
|
+
maxiter = int(maxiter)
|
|
178
|
+
except:
|
|
179
|
+
raise Exception("input for maxiter is not an integer!")
|
|
180
|
+
if maxiter>0: source_mode = 'nonlinear'
|
|
181
|
+
elif maxiter==0: source_mode = 'linear'
|
|
182
|
+
# Check value of tol
|
|
183
|
+
if tol is None: tol = 1e-5
|
|
184
|
+
else:
|
|
185
|
+
try:
|
|
186
|
+
tol = float(tol)
|
|
187
|
+
except:
|
|
188
|
+
raise Exception("input for tol is not a float!")
|
|
189
|
+
|
|
190
|
+
if 'pore.source_'+prop not in self.labels() or mode=='overwrite':
|
|
191
|
+
self['pore.source_'+prop]= sp.zeros((self.Np,),dtype=bool)
|
|
192
|
+
self['pore.source_'+source_mode+'_s1_'+prop] = sp.ones((self.Np,),dtype=float)*sp.nan
|
|
193
|
+
self['pore.source_'+source_mode+'_s2_'+prop] = sp.ones((self.Np,),dtype=float)*sp.nan
|
|
194
|
+
# Setting the source term for all the modes except 'remove'
|
|
195
|
+
matching_physics = [phys for phys in self._phase._physics if source_name in phys.models.keys()]
|
|
196
|
+
for phys in matching_physics:
|
|
197
|
+
x = phys.models[source_name]['x']
|
|
198
|
+
return_rate = phys.models[source_name]['return_rate']
|
|
199
|
+
regen_mode = phys.models[source_name]['regen_mode']
|
|
200
|
+
phys.models[source_name]['x'] = x0
|
|
201
|
+
phys.models[source_name]['return_rate'] = False
|
|
202
|
+
phys.models[source_name]['regen_mode'] = 'normal'
|
|
203
|
+
s_regen = phys.models[source_name].regenerate()
|
|
204
|
+
phys.models[source_name]['x'] = x
|
|
205
|
+
phys.models[source_name]['return_rate'] = return_rate
|
|
206
|
+
phys.models[source_name]['regen_mode'] = regen_mode
|
|
207
|
+
map_pores = phys.map_pores()
|
|
208
|
+
loc = pores[sp.in1d(pores,map_pores)]
|
|
209
|
+
if mode=='merge':
|
|
210
|
+
try:
|
|
211
|
+
if sp.sum(sp.in1d(loc,self.pores(source_name)))>0:
|
|
212
|
+
raise Exception('Because of the existing source term, the method cannot apply new source terms with the merge mode to the specified pores.')
|
|
213
|
+
except KeyError: pass
|
|
214
|
+
self['pore.source_'+prop][loc]= True
|
|
215
|
+
|
|
216
|
+
# for modes in ['update','merge','overwrite']
|
|
217
|
+
map_pores_loc = sp.in1d(map_pores,pores)
|
|
218
|
+
self['pore.source_'+source_mode+'_s1_'+prop][loc] = s_regen[:,0][map_pores_loc]
|
|
219
|
+
self['pore.source_'+source_mode+'_s2_'+prop][loc] = s_regen[:,1][map_pores_loc]
|
|
220
|
+
if not source_mode=='linear':
|
|
221
|
+
self['pore.source_maxiter'][loc] = maxiter
|
|
222
|
+
self['pore.source_tol'][loc] = tol
|
|
223
|
+
else: Exception('No source_name has been sent for set_source_term method in the algorithm '+self.name)
|
|
224
|
+
|
|
225
|
+
def run(self,**kwargs):
|
|
226
|
+
r'''
|
|
227
|
+
This calls the setup method in the algorithm and then runs the outer iteration stage.
|
|
228
|
+
All of the arguments used in setup and solve methods, can be sent here as kwargs.
|
|
229
|
+
'''
|
|
230
|
+
logger.info("Setup "+self.__class__.__name__)
|
|
231
|
+
self.setup(**kwargs)
|
|
232
|
+
self._do_outer_iteration_stage(**kwargs)
|
|
233
|
+
|
|
234
|
+
def _do_outer_iteration_stage(self,**kwargs):
|
|
235
|
+
r'''
|
|
236
|
+
This calls the solve method in the algorithm.
|
|
237
|
+
Many other outer loops can be added here as well, before or after calling solve method.
|
|
238
|
+
'''
|
|
239
|
+
self.solve(**kwargs)
|
|
240
|
+
|
|
241
|
+
def solve(self,A=None,
|
|
242
|
+
b=None,
|
|
243
|
+
iterative_solver = None,
|
|
244
|
+
**kwargs):
|
|
245
|
+
r"""
|
|
246
|
+
Executes the right algorithm for the solution: regular solution of a
|
|
247
|
+
linear system or iterative solution over the nonlinear source terms.
|
|
248
|
+
|
|
249
|
+
Parameters
|
|
250
|
+
----------
|
|
251
|
+
A : sparse matrix
|
|
252
|
+
2D Coefficient matrix
|
|
253
|
+
b : dense matrix
|
|
254
|
+
1D RHS vector
|
|
255
|
+
iterative_sovler : string
|
|
256
|
+
Name of solver to use. If not solve is specified, sp.solve is used
|
|
257
|
+
which is a direct solver (SuperLU on default Scipy installation)
|
|
258
|
+
kwargs : list of keyword arguments
|
|
259
|
+
These arguments and values are sent to the sparse solver, so read
|
|
260
|
+
the specific documentation for the solver chosen
|
|
261
|
+
"""
|
|
262
|
+
self._iterative_solver = iterative_solver
|
|
263
|
+
|
|
264
|
+
# Executes the right algorithm
|
|
265
|
+
if any("pore.source_nonlinear" in s for s in self.props()):
|
|
266
|
+
X = self._do_one_outer_iteration(**kwargs)
|
|
267
|
+
else:
|
|
268
|
+
X = self._do_one_inner_iteration(A,b,**kwargs)
|
|
269
|
+
self.X = X
|
|
270
|
+
self._Neumann_super_X = self.X[-sp.in1d(sp.arange(0,self._coeff_dimension),self.pores())]
|
|
271
|
+
#Removing the additional super pore variables from the results
|
|
272
|
+
self[self._quantity] = self.X[self.pores()]
|
|
273
|
+
logger.info('Writing the results to '+'[\''+self._quantity+'\'] in the '+self.name+' algorithm.')
|
|
274
|
+
|
|
275
|
+
def _do_one_inner_iteration(self,A,b,**kwargs):
|
|
276
|
+
r'''
|
|
277
|
+
This method solves AX = b and returns the result to the corresponding algorithm.
|
|
278
|
+
'''
|
|
279
|
+
logger.info("Solving AX = b for the sparse matrices")
|
|
280
|
+
|
|
281
|
+
if A is None: A = self.A
|
|
282
|
+
if b is None: b = self.b
|
|
283
|
+
|
|
284
|
+
if self._iterative_solver is None:
|
|
285
|
+
X = sprslin.spsolve(A,b)
|
|
286
|
+
else:
|
|
287
|
+
params = kwargs.copy()
|
|
288
|
+
solver_params = ['x0','tol','maxiter','xtype','M','callback']
|
|
289
|
+
[params.pop(item,None) for item in kwargs.keys() if item not in solver_params]
|
|
290
|
+
tol = kwargs.get('tol')
|
|
291
|
+
if tol is None: tol = 1e-20
|
|
292
|
+
params['tol'] = tol
|
|
293
|
+
if self._iterative_solver=='cg':
|
|
294
|
+
result = sprslin.cg(A,b,**params)
|
|
295
|
+
elif self._iterative_solver=='gmres':
|
|
296
|
+
result = sprslin.gmres(A,b,**params)
|
|
297
|
+
elif self._iterative_solver=='bicgstab':
|
|
298
|
+
result = sprslin.bicgstab(A,b,**params)
|
|
299
|
+
X = result[0]
|
|
300
|
+
self._iterative_solver_info = result[1]
|
|
301
|
+
return X
|
|
302
|
+
|
|
303
|
+
def _do_one_outer_iteration(self,**kwargs):
|
|
304
|
+
r"""
|
|
305
|
+
One iteration of an outer iteration loop for an algorithm
|
|
306
|
+
(e.g. time or parametric study)
|
|
307
|
+
"""
|
|
308
|
+
# Checking for the necessary values in Picard algorithm
|
|
309
|
+
self._tol_for_all = sp.amin(self['pore.source_tol'][-sp.isnan(self['pore.source_tol'])])
|
|
310
|
+
self._maxiter_for_all = sp.amax(self['pore.source_maxiter'][-sp.isnan(self['pore.source_maxiter'])])
|
|
311
|
+
if self._guess is None: self._guess = sp.zeros(self._coeff_dimension)
|
|
312
|
+
t = 1
|
|
313
|
+
step = 0
|
|
314
|
+
# The main Picard loop
|
|
315
|
+
while t>self._tol_for_all and step<=self._maxiter_for_all:
|
|
316
|
+
X,t,A,b = self._do_inner_iteration_stage(guess=self._guess,**kwargs)
|
|
317
|
+
logger.info("tol for Picard source_algorithm in step "+str(step)+" : "+str(t))
|
|
318
|
+
self._guess = X
|
|
319
|
+
step += 1
|
|
320
|
+
# Check for divergence
|
|
321
|
+
self._steps = step
|
|
322
|
+
if not t<self._tol_for_all and step>self._maxiter_for_all:
|
|
323
|
+
raise Exception("Iterative algorithm for the source term reached to the maxiter: "+str(self._maxiter_for_all)+" without achieving tol: "+str(self._tol_for_all))
|
|
324
|
+
logger.info("Picard algorithm for source term converged!")
|
|
325
|
+
self.A = A
|
|
326
|
+
self.b = b
|
|
327
|
+
self._tol_reached = t
|
|
328
|
+
return X
|
|
329
|
+
|
|
330
|
+
def _do_inner_iteration_stage(self,guess,**kwargs):
|
|
331
|
+
r'''
|
|
332
|
+
This inner loop updates the source terms based on the new values of the quantity, then modifies A and b matrices, solves AX = b and returns the result.
|
|
333
|
+
'''
|
|
334
|
+
# Updating the source terms
|
|
335
|
+
s1 = sp.zeros(self._coeff_dimension)
|
|
336
|
+
s2 = sp.zeros(self._coeff_dimension)
|
|
337
|
+
for label in self.labels():
|
|
338
|
+
if 'pore.source_' in label:
|
|
339
|
+
source_name = label.replace('pore.source_',"")
|
|
340
|
+
if 'pore.source_nonlinear_s1_'+source_name in self.props():
|
|
341
|
+
tol = min(sp.unique(self['pore.source_tol'][self.pores('source_'+source_name)]))
|
|
342
|
+
maxiter = max(sp.unique(self['pore.source_maxiter'][self.pores('source_'+source_name)]))
|
|
343
|
+
self.set_source_term(source_name=source_name,pores=self.pores(label),x0=guess,tol=tol,maxiter=maxiter,mode='update')
|
|
344
|
+
prop1 = 'pore.source_nonlinear_s1_'+source_name
|
|
345
|
+
s1[-sp.isnan(self[prop1])] = s1[-sp.isnan(self[prop1])]+self[prop1][-sp.isnan(self[prop1])]
|
|
346
|
+
prop2 = 'pore.source_nonlinear_s2_'+source_name
|
|
347
|
+
s2[-sp.isnan(self[prop2])] = s2[-sp.isnan(self[prop2])]+self[prop2][-sp.isnan(self[prop2])]
|
|
348
|
+
|
|
349
|
+
self.s1 = s1
|
|
350
|
+
self.s2 = s2
|
|
351
|
+
# Modifying A and b
|
|
352
|
+
pores = self.pores('source_*')
|
|
353
|
+
S1 = s1[pores]
|
|
354
|
+
S2 = s2[pores]
|
|
355
|
+
A = self._build_coefficient_matrix(modified_diag_pores = pores,
|
|
356
|
+
diag_added_data = S1,
|
|
357
|
+
mode='modify_diagonal')
|
|
358
|
+
b = self._build_RHS_matrix(modified_RHS_pores = pores,
|
|
359
|
+
RHS_added_data = -S2,
|
|
360
|
+
mode='modify_RHS')
|
|
361
|
+
# Solving AX = b
|
|
362
|
+
X = self._do_one_inner_iteration(A=A,b=b,**kwargs)
|
|
363
|
+
# Calculates absolute error
|
|
364
|
+
t = sp.amax(sp.absolute(guess-X))
|
|
365
|
+
return X,t,A,b
|
|
366
|
+
|
|
367
|
+
def return_results(self,pores=None,throats=None,**kwargs):
|
|
368
|
+
r'''
|
|
369
|
+
Send results of simulation out the the appropriate locations.
|
|
370
|
+
|
|
371
|
+
This is a basic version of the update that simply sends out the main
|
|
372
|
+
result (quantity). More elaborate updates should be subclassed.
|
|
373
|
+
'''
|
|
374
|
+
if pores is None:
|
|
375
|
+
pores = self.Ps
|
|
376
|
+
if throats is None:
|
|
377
|
+
throats = self.Ts
|
|
378
|
+
|
|
379
|
+
phase_quantity = self._quantity.replace(self._phase.name+'_',"")
|
|
380
|
+
if phase_quantity not in self._phase.props():
|
|
381
|
+
self._phase[phase_quantity] = sp.nan
|
|
382
|
+
self._phase[phase_quantity][pores] = self[self._quantity][pores]
|
|
383
|
+
|
|
384
|
+
dx = sp.squeeze(sp.diff(self[self._quantity][self._net.find_connected_pores(self.throats())],n=1,axis=1))
|
|
385
|
+
g = self['throat.conductance']
|
|
386
|
+
rate = sp.absolute(g*dx)
|
|
387
|
+
if 'throat.rate' not in self._phase.props():
|
|
388
|
+
self._phase['throat.rate'] = sp.nan
|
|
389
|
+
self._phase['throat.rate'][throats] = rate[throats]
|
|
390
|
+
logger.debug('Results of '+self.name+' algorithm have been added to '+self._phase.name)
|
|
391
|
+
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
def _build_coefficient_matrix(self,modified_diag_pores=None,
|
|
395
|
+
diag_added_data=None,
|
|
396
|
+
mode='overwrite'):
|
|
397
|
+
r'''
|
|
398
|
+
This builds the sparse coefficient matrix for the linear solver.
|
|
399
|
+
'''
|
|
400
|
+
if mode == 'overwrite':
|
|
401
|
+
|
|
402
|
+
# Filling coefficient matrix
|
|
403
|
+
tpore1 = self._net['throat.conns'][:,0]
|
|
404
|
+
tpore2 = self._net['throat.conns'][:,1]
|
|
405
|
+
|
|
406
|
+
#Identify Dirichlet pores
|
|
407
|
+
try:
|
|
408
|
+
temp = self.pores(self._phase.name+'_Dirichlet',mode='difference')
|
|
409
|
+
except:
|
|
410
|
+
temp = self.pores()
|
|
411
|
+
logger.warning('No direct Dirichlet boundary condition has been applied to the phase '+self._phase.name+' in the algorithm '+self.name)
|
|
412
|
+
loc1 = sp.in1d(tpore1,temp)
|
|
413
|
+
loc2 = sp.in1d(tpore2,temp)
|
|
414
|
+
modified_tpore1 = tpore1[loc1]
|
|
415
|
+
modified_tpore2 = tpore2[loc1]
|
|
416
|
+
row = modified_tpore1
|
|
417
|
+
col = modified_tpore2
|
|
418
|
+
|
|
419
|
+
#Expand the conductance to a vector if necessary
|
|
420
|
+
g = self['throat.conductance']
|
|
421
|
+
if sp.size(g) == 1:
|
|
422
|
+
g = g*sp.ones(self.num_throats())
|
|
423
|
+
data_main = g
|
|
424
|
+
data = data_main[loc1]
|
|
425
|
+
|
|
426
|
+
modified_tpore2 = tpore2[loc2]
|
|
427
|
+
modified_tpore1 = tpore1[loc2]
|
|
428
|
+
row = sp.append(row,modified_tpore2)
|
|
429
|
+
col = sp.append(col,modified_tpore1)
|
|
430
|
+
data = sp.append(data,data_main[loc2])
|
|
431
|
+
A_dim = self.num_pores()
|
|
432
|
+
|
|
433
|
+
#Check for Neuman_group BCs and add superpores if necessary
|
|
434
|
+
try:
|
|
435
|
+
self.pores(self._phase.name+'_Neumann_group')
|
|
436
|
+
self._extra_Neumann_size = len(getattr(self,'_pore_'+self._phase.name+'_Neumann_group_location'))
|
|
437
|
+
self._group_Neumann_vals = sp.zeros(self._extra_Neumann_size)
|
|
438
|
+
|
|
439
|
+
for N in sp.arange(0,self._extra_Neumann_size):
|
|
440
|
+
neu_tpore2 = getattr(self,'_pore_'+self._phase.name+'_Neumann_group_location')[N]
|
|
441
|
+
self._group_Neumann_vals[N] = sp.unique(self['pore.'+self._phase.name+'_bcval_Neumann_group'][neu_tpore2])
|
|
442
|
+
neighbor_throats = self._net.find_neighbor_throats(pores=neu_tpore2)
|
|
443
|
+
try: g_super = self.super_pore_conductance[N]
|
|
444
|
+
except:
|
|
445
|
+
g_super = 1e-3*min(data_main[neighbor_throats])
|
|
446
|
+
self.super_pore_conductance.append(g_super)
|
|
447
|
+
row = sp.append(row,neu_tpore2)
|
|
448
|
+
col = sp.append(col,len(neu_tpore2)*[A_dim+N])
|
|
449
|
+
data = sp.append(data,len(neu_tpore2)*[g_super])
|
|
450
|
+
row = sp.append(row,len(neu_tpore2)*[A_dim+N])
|
|
451
|
+
col = sp.append(col,neu_tpore2)
|
|
452
|
+
data = sp.append(data,len(neu_tpore2)*[g_super])
|
|
453
|
+
A_dim = A_dim + self._extra_Neumann_size
|
|
454
|
+
except:
|
|
455
|
+
pass
|
|
456
|
+
|
|
457
|
+
# Adding positions for diagonal
|
|
458
|
+
diag = sp.arange(0,A_dim)
|
|
459
|
+
try:
|
|
460
|
+
pores = self.pores(self._phase.name+'_Dirichlet')
|
|
461
|
+
row = sp.append(row,diag[pores])
|
|
462
|
+
col = sp.append(col,diag[pores])
|
|
463
|
+
data = sp.append(data,sp.ones_like(diag[pores]))
|
|
464
|
+
temp_data = sp.copy(data)
|
|
465
|
+
temp_data[sp.in1d(row,diag[pores])] = 0
|
|
466
|
+
non_Dir_diag = diag[-sp.in1d(diag,diag[pores])]
|
|
467
|
+
except:
|
|
468
|
+
temp_data = sp.copy(data)
|
|
469
|
+
non_Dir_diag = diag
|
|
470
|
+
S_temp = sp.zeros(A_dim)
|
|
471
|
+
for i in sp.arange(0,len(row)):
|
|
472
|
+
S_temp[row[i]] = S_temp[row[i]] - temp_data[i]
|
|
473
|
+
# Store the necessary values for modifying the diagonal in the mode='modify_diagonal'
|
|
474
|
+
self._non_source_row= row
|
|
475
|
+
self._non_source_col = col
|
|
476
|
+
self._non_source_data = data
|
|
477
|
+
self._non_Dir_diag = non_Dir_diag
|
|
478
|
+
self._diagonal_vals = S_temp
|
|
479
|
+
self._coeff_dimension = A_dim
|
|
480
|
+
|
|
481
|
+
if mode in ['overwrite','modify_diagonal']:
|
|
482
|
+
diagonal_vals = sp.copy(self._diagonal_vals)
|
|
483
|
+
# Adding necessary terms to the diagonal such as source terms
|
|
484
|
+
if modified_diag_pores is not None and diag_added_data is not None:
|
|
485
|
+
if sp.size(modified_diag_pores)==sp.size(diag_added_data):
|
|
486
|
+
diagonal_vals[modified_diag_pores] = self._diagonal_vals[modified_diag_pores] + diag_added_data
|
|
487
|
+
else: raise Exception('Provided data and pores for modifying coefficient matrix should have the same size!')
|
|
488
|
+
if mode=='overwrite': self._diagonal_vals = diagonal_vals
|
|
489
|
+
data = sp.append(self._non_source_data,diagonal_vals[self._non_Dir_diag])
|
|
490
|
+
row = sp.append(self._non_source_row,self._non_Dir_diag)
|
|
491
|
+
col = sp.append(self._non_source_col,self._non_Dir_diag)
|
|
492
|
+
#Convert the lists to the sparse matrix
|
|
493
|
+
a = sprs.coo.coo_matrix((data,(row,col)),(self._coeff_dimension,self._coeff_dimension))
|
|
494
|
+
A = a.tocsr()
|
|
495
|
+
A.eliminate_zeros()
|
|
496
|
+
return(A)
|
|
497
|
+
|
|
498
|
+
def _build_RHS_matrix(self,modified_RHS_pores=None,
|
|
499
|
+
RHS_added_data=None,
|
|
500
|
+
mode='overwrite'):
|
|
501
|
+
r'''
|
|
502
|
+
This builds the right-hand-side matrix for the linear solver.
|
|
503
|
+
'''
|
|
504
|
+
if mode=='overwrite':
|
|
505
|
+
A_dim = self._coeff_dimension
|
|
506
|
+
b = sp.zeros([A_dim,1])
|
|
507
|
+
try:
|
|
508
|
+
Dir_pores = self.pores(self._phase.name+'_Dirichlet')
|
|
509
|
+
Dir_pores_vals = self['pore.'+self._phase.name+'_bcval_Dirichlet'][Dir_pores]
|
|
510
|
+
b[Dir_pores] = sp.reshape(Dir_pores_vals,[len(Dir_pores),1])
|
|
511
|
+
except: pass
|
|
512
|
+
try:
|
|
513
|
+
individual_Neu_pores = self.pores(self._phase.name+'_Neumann')
|
|
514
|
+
individual_Neu_pores_vals = self['pore.'+self._phase.name+'_bcval_Neumann'][individual_Neu_pores]
|
|
515
|
+
b[individual_Neu_pores] = sp.reshape(individual_Neu_pores_vals,[len(individual_Neu_pores),1])
|
|
516
|
+
except: pass
|
|
517
|
+
try:
|
|
518
|
+
self.pores(self._phase.name+'_Neumann_group')
|
|
519
|
+
pnum = self._net.num_pores()
|
|
520
|
+
b[sp.r_[pnum:(pnum+len(self._group_Neumann_vals))]] = sp.reshape(self._group_Neumann_vals[sp.r_[0:len(self._group_Neumann_vals)]],[len(self._group_Neumann_vals),1])
|
|
521
|
+
except: pass
|
|
522
|
+
|
|
523
|
+
if mode in ['overwrite','modify_RHS']:
|
|
524
|
+
try: b = sp.copy(self.b)
|
|
525
|
+
except: pass
|
|
526
|
+
# Adding necessary terms such as source terms to the RHS for non-Dirichlet pores
|
|
527
|
+
if modified_RHS_pores is not None and RHS_added_data is not None:
|
|
528
|
+
if sp.size(modified_RHS_pores)==sp.size(RHS_added_data):
|
|
529
|
+
p = sp.in1d(modified_RHS_pores,self._non_Dir_diag)
|
|
530
|
+
data = RHS_added_data[p]
|
|
531
|
+
b[modified_RHS_pores[p]] = b[modified_RHS_pores[p]] + data.reshape([len(data),1])
|
|
532
|
+
else: raise Exception('Provided data and pores for modifying RHS matrix should have the same size!')
|
|
533
|
+
|
|
534
|
+
return(b)
|
|
535
|
+
|
|
536
|
+
def rate(self,pores=None,network=None,conductance=None,X_value=None,mode='group'):
|
|
537
|
+
r'''
|
|
538
|
+
Send a list of pores and receive the net rate
|
|
539
|
+
of material moving into them.
|
|
540
|
+
|
|
541
|
+
Parameters
|
|
542
|
+
----------
|
|
543
|
+
pores : array_like
|
|
544
|
+
The pores where the net rate will be calculated
|
|
545
|
+
network : OpenPNM Network Object
|
|
546
|
+
The network object to which this algorithm will apply.
|
|
547
|
+
If no network is sent, the rate will apply to the network which is attached to the algorithm.
|
|
548
|
+
conductance : array_like
|
|
549
|
+
The conductance which this algorithm will use to calculate the rate.
|
|
550
|
+
If no conductance is sent, the rate will use the 'throat.conductance' which is attached to the algorithm.
|
|
551
|
+
X_value : array_like
|
|
552
|
+
The values of the quantity (temperature, mole_fraction, voltage, ...), which this algorithm will use to calculate the rate.
|
|
553
|
+
If no X_value is sent, the rate will look at the '_quantity', which is attached to the algorithm.
|
|
554
|
+
mode : string, optional
|
|
555
|
+
Controls how to return the rate. Options are:
|
|
556
|
+
- 'group'(default): It returns the cumulative rate moving into them
|
|
557
|
+
- 'single': It calculates the rate for each pore individually.
|
|
558
|
+
|
|
559
|
+
'''
|
|
560
|
+
if network is None: network = self._net
|
|
561
|
+
if conductance is None: conductance = self['throat.conductance']
|
|
562
|
+
if X_value is None: X_value = self[self._quantity]
|
|
563
|
+
pores = sp.array(pores,ndmin=1)
|
|
564
|
+
R = []
|
|
565
|
+
if mode=='group':
|
|
566
|
+
t = network.find_neighbor_throats(pores,flatten=True,mode='not_intersection')
|
|
567
|
+
throat_group_num = 1
|
|
568
|
+
elif mode=='single':
|
|
569
|
+
t = network.find_neighbor_throats(pores,flatten=False,mode='not_intersection')
|
|
570
|
+
throat_group_num = sp.size(t)
|
|
571
|
+
|
|
572
|
+
for i in sp.r_[0:throat_group_num]:
|
|
573
|
+
if mode=='group':
|
|
574
|
+
throats = t
|
|
575
|
+
P = pores
|
|
576
|
+
elif mode=='single':
|
|
577
|
+
throats = t[i]
|
|
578
|
+
P = pores[i]
|
|
579
|
+
p1 = network.find_connected_pores(throats)[:,0]
|
|
580
|
+
p2 = network.find_connected_pores(throats)[:,1]
|
|
581
|
+
pores1 = sp.copy(p1)
|
|
582
|
+
pores2 = sp.copy(p2)
|
|
583
|
+
#Changes to pores1 and pores2 to make them as the inner and outer pores
|
|
584
|
+
pores1[-sp.in1d(p1,P)] = p2[-sp.in1d(p1,P)]
|
|
585
|
+
pores2[-sp.in1d(p1,P)] = p1[-sp.in1d(p1,P)]
|
|
586
|
+
X1 = X_value[pores1]
|
|
587
|
+
X2 = X_value[pores2]
|
|
588
|
+
g = conductance[throats]
|
|
589
|
+
R.append(sp.sum(sp.multiply(g,(X2-X1))))
|
|
590
|
+
return(sp.array(R,ndmin=1))
|
|
591
|
+
|
|
592
|
+
def _calc_eff_prop(self,check_health=False):
|
|
593
|
+
r'''
|
|
594
|
+
This returns the main parameters for calculating the effective property in a linear transport equation.
|
|
595
|
+
It also checks for the proper boundary conditions, inlets and outlets.
|
|
596
|
+
|
|
597
|
+
Parameters
|
|
598
|
+
----------
|
|
599
|
+
check_health : boolean(optional)
|
|
600
|
+
It analyzes the inlet and outlet pores to check their spatial positions
|
|
601
|
+
'''
|
|
602
|
+
try:
|
|
603
|
+
self[self._quantity]
|
|
604
|
+
except:
|
|
605
|
+
raise Exception('The algorithm has not been run yet. Cannot calculate effective property.')
|
|
606
|
+
#Determine boundary conditions by analyzing algorithm object
|
|
607
|
+
Ps = self.pores('pore.'+self._phase.name+'_Dirichlet')
|
|
608
|
+
BCs = sp.unique(self['pore.'+self._phase.name+'_bcval_Dirichlet'][Ps])
|
|
609
|
+
if sp.shape(BCs)[0] != 2:
|
|
610
|
+
raise Exception('The supplied algorithm did not have appropriate BCs')
|
|
611
|
+
inlets = sp.where(self['pore.'+self._phase.name+'_bcval_Dirichlet']==sp.amax(BCs))[0]
|
|
612
|
+
outlets = sp.where(self['pore.'+self._phase.name+'_bcval_Dirichlet']==sp.amin(BCs))[0]
|
|
613
|
+
|
|
614
|
+
#Analyze input and output pores
|
|
615
|
+
if check_health:
|
|
616
|
+
#Check for coplanarity
|
|
617
|
+
if self._net.iscoplanar(inlets) == False:
|
|
618
|
+
raise Exception('The inlet pores do not define a plane. Effective property will be approximation')
|
|
619
|
+
if self._net.iscoplanar(outlets) == False:
|
|
620
|
+
raise Exception('The outlet pores do not define a plane. Effective property will be approximation')
|
|
621
|
+
#Ensure pores are on a face of domain (only 1 non-self neighbor each)
|
|
622
|
+
PnI = self._net.find_neighbor_pores(pores=inlets,mode='not_intersection',excl_self=True)
|
|
623
|
+
if sp.shape(PnI) != sp.shape(inlets):
|
|
624
|
+
logger.warning('The inlet pores have too many neighbors. Internal pores appear to be selected.')
|
|
625
|
+
pass
|
|
626
|
+
PnO = self._net.find_neighbor_pores(pores=outlets,mode='not_intersection',excl_self=True)
|
|
627
|
+
if sp.shape(PnO) != sp.shape(outlets):
|
|
628
|
+
logger.warning('The outlet pores have too many neighbors. Internal pores appear to be selected.')
|
|
629
|
+
pass
|
|
630
|
+
|
|
631
|
+
#Fetch area and length of domain
|
|
632
|
+
if "pore.vert_index" in self._net.props():
|
|
633
|
+
A = vo.vertex_dimension(network = self._net,face1=inlets, parm='area')
|
|
634
|
+
L = vo.vertex_dimension(network = self._net,face1=inlets,face2=outlets,parm='length')
|
|
635
|
+
else:
|
|
636
|
+
A = self._net.domain_area(face=inlets)
|
|
637
|
+
L = self._net.domain_length(face_1=inlets,face_2=outlets)
|
|
638
|
+
flow = self.rate(pores=inlets)
|
|
639
|
+
D = sp.sum(flow)*L/A/(BCs[0]-BCs[1])
|
|
640
|
+
return D
|
|
641
|
+
|