@@ -40,6 +40,7 @@ def __init__(self, problem_params, dtype_u=mesh, dtype_f=mesh):
4040 'u_max' : 2e-2 ,
4141 'Q_max' : 1.0 ,
4242 'leak_range' : (0.45 , 0.55 ),
43+ 'leak_type' : 'linear' ,
4344 'order' : 2 ,
4445 'stencil_type' : 'center' ,
4546 'bc' : 'neumann-zero' ,
@@ -116,7 +117,13 @@ def eval_f_non_linear(self, u, t):
116117 Q_max = self .params .Q_max
117118 me = self .dtype_u (self .init )
118119
119- me [:] = (u - u_thresh ) / (u_max - u_thresh ) * Q_max
120+ if self .params .leak_type == 'linear' :
121+ me [:] = (u - u_thresh ) / (u_max - u_thresh ) * Q_max
122+ elif self .params .leak_type == 'exponential' :
123+ me [:] = Q_max * (np .exp (u ) - np .exp (u_thresh )) / (np .exp (u_max ) - np .exp (u_thresh ))
124+ else :
125+ raise NotImplementedError (f'Leak type { self .params .leak_type } not implemented!' )
126+
120127 me [u < u_thresh ] = 0
121128 me [self .leak ] = Q_max
122129 me [u >= u_max ] = Q_max
@@ -145,47 +152,53 @@ def eval_f(self, u, t):
145152 self .work_counters ['rhs' ]()
146153 return f
147154
148- def solve_system (self , rhs , factor , u0 , t ):
155+ def get_non_linear_Jacobian (self , u ):
149156 """
150- Simple Newton solver for (I-factor*f)(u) = rhs
157+ Evaluate the non-linear part of the Jacobian only
151158
152159 Args:
153- rhs (dtype_f): right-hand side
154- factor (float): abbrev. for the local stepsize (or any other factor required)
155- u0 (dtype_u): initial guess for the iterative solver
156- t (float): current time (e.g. for time-dependent BCs)
160+ u (dtype_u): Current solution
157161
158162 Returns:
159- dtype_u: solution as mesh
163+ scipy.sparse.csc: The derivative of the non-linear part of the solution w.r.t. to the solution.
160164 """
165+ u_thresh = self .params .u_thresh
166+ u_max = self .params .u_max
167+ Q_max = self .params .Q_max
168+ me = self .dtype_u (self .init )
169+
170+ if self .params .leak_type == 'linear' :
171+ me [:] = Q_max / (u_max - u_thresh )
172+ elif self .params .leak_type == 'exponential' :
173+ me [:] = Q_max * np .exp (u ) / (np .exp (u_max ) - np .exp (u_thresh ))
174+ else :
175+ raise NotImplementedError (f'Leak type { self .params .leak_type } not implemented!' )
161176
162- def get_non_linear_Jacobian ( u ):
163- """
164- Evaluate the non-linear part of the Jacobian only
177+ me [ u < u_thresh ] = 0
178+ me [ u > u_max ] = 0
179+ me [ self . leak ] = 0
165180
166- Args:
167- u (dtype_u): Current solution
181+ # boundary conditions
182+ me [0 ] = 0.0
183+ me [- 1 ] = 0.0
168184
169- Returns:
170- scipy.sparse.csc: The derivative of the non-linear part of the solution w.r.t. to the solution.
171- """
172- u_thresh = self .params .u_thresh
173- u_max = self .params .u_max
174- Q_max = self .params .Q_max
175- me = self .dtype_u (self .init )
185+ me [:] /= self .params .Cv
176186
177- me [:] = Q_max / (u_max - u_thresh )
178- me [u < u_thresh ] = 0
179- me [u > u_max ] = 0
180- me [self .leak ] = 0
187+ return sp .diags (me , format = 'csc' )
181188
182- # boundary conditions
183- me [ 0 ] = 0.0
184- me [ - 1 ] = 0.0
189+ def solve_system ( self , rhs , factor , u0 , t ):
190+ """
191+ Simple Newton solver for (I-factor*f)(u) = rhs
185192
186- me [:] /= self .params .Cv
193+ Args:
194+ rhs (dtype_f): right-hand side
195+ factor (float): abbrev. for the local stepsize (or any other factor required)
196+ u0 (dtype_u): initial guess for the iterative solver
197+ t (float): current time (e.g. for time-dependent BCs)
187198
188- return sp .diags (me , format = 'csc' )
199+ Returns:
200+ dtype_u: solution as mesh
201+ """
189202
190203 u = self .dtype_u (u0 )
191204 res = np .inf
@@ -209,7 +222,7 @@ def get_non_linear_Jacobian(u):
209222 break
210223
211224 # assemble Jacobian J of G
212- J = self .Id - factor * (self .A + get_non_linear_Jacobian (u ))
225+ J = self .Id - factor * (self .A + self . get_non_linear_Jacobian (u ))
213226
214227 # solve the linear system
215228 if self .params .direct_solver :
@@ -251,6 +264,19 @@ def u_exact(self, t, u_init=None, t_init=None):
251264
252265 if t > 0 :
253266
267+ def jac (t , u ):
268+ """
269+ Get the Jacobian for the implicit BDF method to use in `scipy.odeint`
270+
271+ Args:
272+ t (float): The current time
273+ u (dtype_u): Current solution
274+
275+ Returns:
276+ scipy.sparse.csc: The derivative of the non-linear part of the solution w.r.t. to the solution.
277+ """
278+ return self .A + self .get_non_linear_Jacobian (u )
279+
254280 def eval_rhs (t , u ):
255281 """
256282 Function to pass to `scipy.solve_ivp` to evaluate the full RHS
@@ -264,7 +290,7 @@ def eval_rhs(t, u):
264290 """
265291 return self .eval_f (u .reshape (self .init [0 ]), t ).flatten ()
266292
267- me [:] = self .generate_scipy_reference_solution (eval_rhs , t , u_init , t_init )
293+ me [:] = self .generate_scipy_reference_solution (eval_rhs , t , u_init , t_init , method = 'BDF' , jac = jac )
268294 return me
269295
270296
@@ -326,6 +352,19 @@ def u_exact(self, t, u_init=None, t_init=None):
326352
327353 if t > 0 :
328354
355+ def jac (t , u ):
356+ """
357+ Get the Jacobian for the implicit BDF method to use in `scipy.odeint`
358+
359+ Args:
360+ t (float): The current time
361+ u (dtype_u): Current solution
362+
363+ Returns:
364+ scipy.sparse.csc: The derivative of the non-linear part of the solution w.r.t. to the solution.
365+ """
366+ return self .A
367+
329368 def eval_rhs (t , u ):
330369 """
331370 Function to pass to `scipy.solve_ivp` to evaluate the full RHS
@@ -340,5 +379,5 @@ def eval_rhs(t, u):
340379 f = self .eval_f (u .reshape (self .init [0 ]), t )
341380 return (f .impl + f .expl ).flatten ()
342381
343- me [:] = self .generate_scipy_reference_solution (eval_rhs , t , u_init , t_init )
382+ me [:] = self .generate_scipy_reference_solution (eval_rhs , t , u_init , t_init , method = 'BDF' , jac = jac )
344383 return me
0 commit comments