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