Skip to content

Commit fa524d2

Browse files
authored
Merge pull request #283 from brownbaerchen/getdp
Reference solution for Quench problem
2 parents ce75465 + c194c78 commit fa524d2

File tree

5 files changed

+95
-39
lines changed

5 files changed

+95
-39
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: 71 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -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

pySDC/projects/Resilience/leaky_superconductor.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -194,7 +194,7 @@ def plot_solution(stats, controller): # pragma: no cover
194194
dt_ax.set_ylabel(r'$\Delta t$')
195195

196196

197-
def compare_imex_full(plotting=False):
197+
def compare_imex_full(plotting=False, leak_type='linear'):
198198
"""
199199
Compare the results of IMEX and fully implicit runs. For IMEX we need to limit the step size in order to achieve convergence, but for fully implicit, adaptivity can handle itself better.
200200
@@ -203,19 +203,22 @@ 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'] = {
216218
'newton_tol': 1e-10,
217219
'newton_iter': newton_iter_max,
218220
'nvars': 2**9,
221+
'leak_type': leak_type,
219222
}
220223
custom_description['step_params'] = {'maxiter': maxiter}
221224
custom_description['sweeper_params'] = {'num_nodes': num_nodes}
@@ -227,13 +230,14 @@ def compare_imex_full(plotting=False):
227230
custom_description=custom_description,
228231
custom_controller_params=custom_controller_params,
229232
imex=imex,
230-
Tend=5e2,
231-
hook_class=LogWork,
233+
Tend=4.3e2,
234+
hook_class=[LogWork, LogGlobalErrorPostRun],
232235
)
233236

234237
res[imex] = get_sorted(stats, type='u')[-1][1]
235238
newton_iter = [me[1] for me in get_sorted(stats, type='work_newton')]
236239
rhs[imex] = np.mean([me[1] for me in get_sorted(stats, type='work_rhs')]) // 1
240+
error[imex] = get_sorted(stats, type='e_global_post_run')[-1][1]
237241

238242
if imex:
239243
assert all([me == 0 for me in newton_iter]), "IMEX is not supposed to do Newton iterations!"
@@ -258,6 +262,11 @@ def compare_imex_full(plotting=False):
258262
rhs[True] == rhs[False]
259263
), 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]}!"
260264

265+
assert (
266+
error[True] > error[False]
267+
), 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}"
268+
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}!'
269+
261270

262271
if __name__ == '__main__':
263272
compare_imex_full(plotting=True)

pySDC/tests/test_convergence_controllers/test_InterpolateBetweenRestarts.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ def run_vdp(hook, adaptivity=True):
155155
'newton_tol': 1e-9,
156156
'newton_maxiter': 99,
157157
'u0': np.array([2.0, 0.0]),
158+
'crash_at_maxiter': False,
158159
}
159160

160161
# initialize step parameters

pySDC/tests/test_projects/test_resilience/test_leaky_superconductor.py

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,11 @@
22

33

44
@pytest.mark.base
5-
def test_imex_vs_fully_implicit_leaky_superconductor():
5+
@pytest.mark.parametrize('leak_type', ['linear', 'exponential'])
6+
def test_imex_vs_fully_implicit_leaky_superconductor(leak_type):
67
"""
78
Test if the IMEX and fully implicit schemes get the same solution and that the runaway process has started.
89
"""
910
from pySDC.projects.Resilience.leaky_superconductor import compare_imex_full
1011

11-
compare_imex_full()
12+
compare_imex_full(plotting=False, leak_type=leak_type)

0 commit comments

Comments
 (0)