Skip to content

Commit 7697003

Browse files
author
Thomas Baumann
committed
Switched the method for the scipy reference solution to the implicit BDF method
1 parent ce75465 commit 7697003

File tree

3 files changed

+74
-34
lines changed

3 files changed

+74
-34
lines changed

pySDC/core/Problem.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,13 +70,17 @@ def apply_mass_matrix(self, u):
7070
"""
7171
raise NotImplementedError('ERROR: if you want a mass matrix, implement apply_mass_matrix(u)')
7272

73-
def generate_scipy_reference_solution(self, eval_rhs, t, u_init=None, t_init=None):
73+
def generate_scipy_reference_solution(self, eval_rhs, t, u_init=None, t_init=None, **kwargs):
7474
"""
7575
Compute a reference solution using `scipy.solve_ivp` with very small tolerances.
7676
Keep in mind that scipy needs the solution to be a one dimensional array. If you are solving something higher
7777
dimensional, you need to make sure the function `eval_rhs` takes a flattened one-dimensional version as an input
7878
and output, but reshapes to whatever the problem needs for evaluation.
7979
80+
The keyword arguments will be passed to `scipy.solve_ivp`. You should consider passing `method='BDF'` for stiff
81+
problems and to accelerate that you can pass a function that evaluates the Jacobian with arguments `jac(t, u)`
82+
as `jac=jac`.
83+
8084
Args:
8185
eval_rhs (function): Function evaluate the full right hand side. Must have signature `eval_rhs(float: t, numpy.1darray: u)`
8286
t (float): current time
@@ -94,4 +98,6 @@ def generate_scipy_reference_solution(self, eval_rhs, t, u_init=None, t_init=Non
9498
t_init = 0 if t_init is None else t_init
9599

96100
u_shape = u_init.shape
97-
return solve_ivp(eval_rhs, (t_init, t), u_init.flatten(), rtol=tol, atol=tol).y[:, -1].reshape(u_shape)
101+
return (
102+
solve_ivp(eval_rhs, (t_init, t), u_init.flatten(), rtol=tol, atol=tol, **kwargs).y[:, -1].reshape(u_shape)
103+
)

pySDC/implementations/problem_classes/LeakySuperconductor.py

Lines changed: 57 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -145,47 +145,47 @@ def eval_f(self, u, t):
145145
self.work_counters['rhs']()
146146
return f
147147

148-
def solve_system(self, rhs, factor, u0, t):
148+
def get_non_linear_Jacobian(self, u):
149149
"""
150-
Simple Newton solver for (I-factor*f)(u) = rhs
150+
Evaluate the non-linear part of the Jacobian only
151151
152152
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)
153+
u (dtype_u): Current solution
157154
158155
Returns:
159-
dtype_u: solution as mesh
156+
scipy.sparse.csc: The derivative of the non-linear part of the solution w.r.t. to the solution.
160157
"""
158+
u_thresh = self.params.u_thresh
159+
u_max = self.params.u_max
160+
Q_max = self.params.Q_max
161+
me = self.dtype_u(self.init)
161162

162-
def get_non_linear_Jacobian(u):
163-
"""
164-
Evaluate the non-linear part of the Jacobian only
163+
me[:] = Q_max / (u_max - u_thresh)
164+
me[u < u_thresh] = 0
165+
me[u > u_max] = 0
166+
me[self.leak] = 0
165167

166-
Args:
167-
u (dtype_u): Current solution
168+
# boundary conditions
169+
me[0] = 0.0
170+
me[-1] = 0.0
168171

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)
172+
me[:] /= self.params.Cv
176173

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
174+
return sp.diags(me, format='csc')
181175

182-
# boundary conditions
183-
me[0] = 0.0
184-
me[-1] = 0.0
176+
def solve_system(self, rhs, factor, u0, t):
177+
"""
178+
Simple Newton solver for (I-factor*f)(u) = rhs
185179
186-
me[:] /= self.params.Cv
180+
Args:
181+
rhs (dtype_f): right-hand side
182+
factor (float): abbrev. for the local stepsize (or any other factor required)
183+
u0 (dtype_u): initial guess for the iterative solver
184+
t (float): current time (e.g. for time-dependent BCs)
187185
188-
return sp.diags(me, format='csc')
186+
Returns:
187+
dtype_u: solution as mesh
188+
"""
189189

190190
u = self.dtype_u(u0)
191191
res = np.inf
@@ -209,7 +209,7 @@ def get_non_linear_Jacobian(u):
209209
break
210210

211211
# assemble Jacobian J of G
212-
J = self.Id - factor * (self.A + get_non_linear_Jacobian(u))
212+
J = self.Id - factor * (self.A + self.get_non_linear_Jacobian(u))
213213

214214
# solve the linear system
215215
if self.params.direct_solver:
@@ -251,6 +251,19 @@ def u_exact(self, t, u_init=None, t_init=None):
251251

252252
if t > 0:
253253

254+
def jac(t, u):
255+
"""
256+
Get the Jacobian for the implicit BDF method to use in `scipy.odeint`
257+
258+
Args:
259+
t (float): The current time
260+
u (dtype_u): Current solution
261+
262+
Returns:
263+
scipy.sparse.csc: The derivative of the non-linear part of the solution w.r.t. to the solution.
264+
"""
265+
return self.A + self.get_non_linear_Jacobian(u)
266+
254267
def eval_rhs(t, u):
255268
"""
256269
Function to pass to `scipy.solve_ivp` to evaluate the full RHS
@@ -264,7 +277,7 @@ def eval_rhs(t, u):
264277
"""
265278
return self.eval_f(u.reshape(self.init[0]), t).flatten()
266279

267-
me[:] = self.generate_scipy_reference_solution(eval_rhs, t, u_init, t_init)
280+
me[:] = self.generate_scipy_reference_solution(eval_rhs, t, u_init, t_init, method='BDF', jac=jac)
268281
return me
269282

270283

@@ -326,6 +339,19 @@ def u_exact(self, t, u_init=None, t_init=None):
326339

327340
if t > 0:
328341

342+
def jac(t, u):
343+
"""
344+
Get the Jacobian for the implicit BDF method to use in `scipy.odeint`
345+
346+
Args:
347+
t (float): The current time
348+
u (dtype_u): Current solution
349+
350+
Returns:
351+
scipy.sparse.csc: The derivative of the non-linear part of the solution w.r.t. to the solution.
352+
"""
353+
return self.A
354+
329355
def eval_rhs(t, u):
330356
"""
331357
Function to pass to `scipy.solve_ivp` to evaluate the full RHS
@@ -340,5 +366,5 @@ def eval_rhs(t, u):
340366
f = self.eval_f(u.reshape(self.init[0]), t)
341367
return (f.impl + f.expl).flatten()
342368

343-
me[:] = self.generate_scipy_reference_solution(eval_rhs, t, u_init, t_init)
369+
me[:] = self.generate_scipy_reference_solution(eval_rhs, t, u_init, t_init, method='BDF', jac=jac)
344370
return me

pySDC/projects/Resilience/leaky_superconductor.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -203,13 +203,15 @@ def compare_imex_full(plotting=False):
203203
"""
204204
from pySDC.implementations.convergence_controller_classes.adaptivity import Adaptivity
205205
from pySDC.implementations.hooks.log_work import LogWork
206+
from pySDC.implementations.hooks.log_errors import LogGlobalErrorPostRun
206207

207208
maxiter = 5
208209
num_nodes = 3
209210
newton_iter_max = 20
210211

211212
res = {}
212213
rhs = {}
214+
error = {}
213215

214216
custom_description = {}
215217
custom_description['problem_params'] = {
@@ -228,12 +230,13 @@ def compare_imex_full(plotting=False):
228230
custom_controller_params=custom_controller_params,
229231
imex=imex,
230232
Tend=5e2,
231-
hook_class=LogWork,
233+
hook_class=[LogWork, LogGlobalErrorPostRun],
232234
)
233235

234236
res[imex] = get_sorted(stats, type='u')[-1][1]
235237
newton_iter = [me[1] for me in get_sorted(stats, type='work_newton')]
236238
rhs[imex] = np.mean([me[1] for me in get_sorted(stats, type='work_rhs')]) // 1
239+
error[imex] = get_sorted(stats, type='e_global_post_run')[-1][1]
237240

238241
if imex:
239242
assert all([me == 0 for me in newton_iter]), "IMEX is not supposed to do Newton iterations!"
@@ -258,6 +261,11 @@ def compare_imex_full(plotting=False):
258261
rhs[True] == rhs[False]
259262
), f"Expected IMEX and fully implicit schemes to take the same number of right hand side evaluations per step, but got {rhs[True]} and {rhs[False]}!"
260263

264+
assert (
265+
error[True] > error[False]
266+
), f"Expected IMEX to be less accurate at the same precision settings than unsplit version, got for IMEX: e={error[True]:.2e} and fully implicit: e={error[False]:.2e}"
267+
assert error[True] < 1.1e-4, f'Expected error of IMEX version to be less than 1.1e-4, but got e={error[True]:.2e}!'
268+
261269

262270
if __name__ == '__main__':
263271
compare_imex_full(plotting=True)

0 commit comments

Comments
 (0)