Skip to content

Commit be8c826

Browse files
authored
Merge pull request #278 from brownbaerchen/resilience_quench
Bugfixes in Quench problem
2 parents 95caedb + 439d098 commit be8c826

File tree

11 files changed

+522
-74
lines changed

11 files changed

+522
-74
lines changed

pySDC/helpers/plot_helper.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,17 @@
66

77

88
def figsize(textwidth, scale, ratio):
9+
"""
10+
Get figsize.
11+
12+
Args:
13+
textwidth (str): Textwdith in your LaTeX file in points
14+
scale (float): The width of the figure relative to the textwidth
15+
ratio (float): The height of the figure relative to its width
16+
17+
Returns:
18+
list: Width and height of the figure to be passed to matplotlib
19+
"""
920
fig_width_pt = textwidth # Get this from LaTeX using \the\textwidth
1021
inches_per_pt = 1.0 / 72.27 # Convert pt to inch
1122
fig_width = fig_width_pt * inches_per_pt * scale # width in inches
@@ -14,6 +25,43 @@ def figsize(textwidth, scale, ratio):
1425
return fig_size
1526

1627

28+
def figsize_by_journal(journal, scale, ratio): # pragma no cover
29+
"""
30+
Get figsize for specific journal. If you supply a text height, we will rescale the figure to fit on the page instead
31+
of the parameters supplied.
32+
33+
Args:
34+
journal (str): Name of journal
35+
scale (float): The width of the figure relative to the textwidth
36+
ratio (float): The height of the figure relative to its width
37+
38+
Returns:
39+
list: Width and height of the figure to be passed to matplotlib
40+
"""
41+
# store text width in points here, get this from LaTeX using \the\textwidth
42+
textwidths = {
43+
'JSC_beamer': 426.79135,
44+
'Springer_Numerical_Algorithms': 338.58778,
45+
}
46+
# store text height in points here, get this from LaTeX using \the\textheight
47+
textheights = {
48+
'JSC_beamer': 214.43411,
49+
}
50+
assert (
51+
journal in textwidths.keys()
52+
), f"Textwidth only available for {list(textwidths.keys())}. Please implement one for \"{journal}\"! Get the textwidth using \"\\the\\textwidth\" in your tex file."
53+
54+
# see if the figure fits on the page or if we need to apply the scaling to the height instead
55+
if scale * ratio * textwidths[journal] > textheights.get(journal, 1e9):
56+
if textheights[journal] / scale / ratio > textwidths[journal]:
57+
raise ValueError(
58+
f"We cannot fit figure with scale {scale:.2f} and ratio {ratio:.2f} on the page for journal {journal}!"
59+
)
60+
return figsize(textheights[journal] / (scale * ratio), 1, ratio)
61+
62+
return figsize(textwidths[journal], scale, ratio)
63+
64+
1765
def setup_mpl(font_size=8, reset=False):
1866
if reset:
1967
mpl.rcParams.update(default_mpl_params)

pySDC/implementations/convergence_controller_classes/check_convergence.py

Lines changed: 38 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,35 @@ def setup(self, controller, params, description, **kwargs):
2424
Returns:
2525
(dict): The updated params dictionary
2626
"""
27-
return {"control_order": +200, **super().setup(controller, params, description, **kwargs)}
27+
defaults = {'control_order': +200, 'use_e_tol': 'e_tol' in description['level_params'].keys()}
28+
29+
return {**defaults, **super().setup(controller, params, description, **kwargs)}
30+
31+
def dependencies(self, controller, description, **kwargs):
32+
"""
33+
Load the embedded error estimator if needed.
34+
35+
Args:
36+
controller (pySDC.Controller): The controller
37+
description (dict): The description object used to instantiate the controller
38+
39+
Returns:
40+
None
41+
"""
42+
super().dependencies(controller, description)
43+
44+
if self.params.use_e_tol:
45+
from pySDC.implementations.convergence_controller_classes.estimate_embedded_error import (
46+
EstimateEmbeddedError,
47+
)
48+
from pySDC.implementations.controller_classes.controller_nonMPI import controller_nonMPI
49+
50+
controller.add_convergence_controller(
51+
EstimateEmbeddedError.get_implementation("nonMPI" if type(controller) == controller_nonMPI else "MPI"),
52+
description=description,
53+
)
54+
55+
return None
2856

2957
@staticmethod
3058
def check_convergence(S):
@@ -42,10 +70,16 @@ def check_convergence(S):
4270
L = S.levels[0]
4371
L.sweep.compute_residual()
4472

45-
# get residual and check against prescribed tolerance (plus check number of iterations
46-
res = L.status.residual
73+
# get residual and check against prescribed tolerance (plus check number of iterations)
74+
iter_converged = S.status.iter >= S.params.maxiter
75+
res_converged = L.status.residual <= L.params.restol
76+
e_tol_converged = (
77+
L.status.error_embedded_estimate < L.params.e_tol
78+
if (L.params.get('e_tol') and L.status.get('error_embedded_estimate'))
79+
else False
80+
)
4781
converged = (
48-
S.status.iter >= S.params.maxiter or res <= L.params.restol or S.status.force_done
82+
iter_converged or res_converged or e_tol_converged or S.status.force_done
4983
) and not S.status.force_continue
5084
if converged is None:
5185
converged = False

pySDC/implementations/hooks/log_embedded_error_estimate.py

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ class LogEmbeddedErrorEstimate(hooks):
66
Store the embedded error estimate at the end of each step as "error_embedded_estimate".
77
"""
88

9-
def post_step(self, step, level_number):
9+
def post_step(self, step, level_number, appendix=''):
1010
"""
1111
Record embedded error estimate
1212
@@ -27,6 +27,47 @@ def post_step(self, step, level_number):
2727
level=L.level_index,
2828
iter=step.status.iter,
2929
sweep=L.status.sweep,
30-
type='error_embedded_estimate',
30+
type=f'error_embedded_estimate{appendix}',
31+
value=L.status.get('error_embedded_estimate'),
32+
)
33+
34+
35+
class LogEmbeddedErrorEstimatePostIter(LogEmbeddedErrorEstimate):
36+
"""
37+
Store the embedded error estimate after each iteration as "error_embedded_estimate_post_iteration".
38+
39+
Because the error estimate is computed after the hook is called, we record the value belonging to the last
40+
iteration, which is also why we need to record something after the step, which belongs to the final iteration.
41+
"""
42+
43+
def post_iteration(self, step, level_number):
44+
"""
45+
Record embedded error estimate
46+
47+
Args:
48+
step (pySDC.Step.step): the current step
49+
level_number (int): the current level number
50+
51+
Returns:
52+
None
53+
"""
54+
# check if the estimate is available at all
55+
super().post_iteration(step, level_number)
56+
57+
L = step.levels[level_number]
58+
59+
if not L.status.get('error_embedded_estimate'):
60+
return None
61+
62+
self.add_to_stats(
63+
process=step.status.slot,
64+
time=L.time + L.dt,
65+
level=L.level_index,
66+
iter=step.status.iter - 1,
67+
sweep=L.status.sweep,
68+
type='error_embedded_estimate_post_iteration',
3169
value=L.status.get('error_embedded_estimate'),
3270
)
71+
72+
def post_step(self, step, level_number):
73+
super().post_step(step, level_number, appendix='_post_iteration')

pySDC/implementations/problem_classes/LeakySuperconductor.py

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import numpy as np
22
import scipy.sparse as sp
3-
from scipy.sparse.linalg import cg, spsolve
3+
from scipy.sparse.linalg import spsolve
44

55
from pySDC.core.Errors import ParameterError, ProblemError
66
from pySDC.core.Problem import ptype
@@ -72,7 +72,7 @@ def __init__(self, problem_params, dtype_u=mesh, dtype_f=mesh):
7272
xvalues = np.array([(i + 1) * self.dx for i in range(self.params.nvars)])
7373
elif self.params.bc == 'neumann-zero':
7474
self.dx = 1.0 / (self.params.nvars - 1)
75-
xvalues = np.array([(i + 1) * self.dx for i in range(self.params.nvars)])
75+
xvalues = np.array([i * self.dx for i in range(self.params.nvars)])
7676
else:
7777
raise ProblemError(f'Boundary conditions {self.params.bc} not implemented.')
7878

@@ -85,13 +85,15 @@ def __init__(self, problem_params, dtype_u=mesh, dtype_f=mesh):
8585
dim=1,
8686
bc=self.params.bc,
8787
)
88-
self.A *= self.params.K
88+
self.A *= self.params.K / self.params.Cv
8989

9090
self.xv = xvalues
9191
self.Id = sp.eye(np.prod(self.params.nvars), format='csc')
9292

9393
self.leak = np.logical_and(self.xv > self.params.leak_range[0], self.xv < self.params.leak_range[1])
9494

95+
self.total_newton_iter = 0 # store here how many iterations you needed for the Newton solver over the entire run to extract the desired information in the hooks
96+
9597
def eval_f_non_linear(self, u, t):
9698
"""
9799
Get the non-linear part of f
@@ -117,6 +119,8 @@ def eval_f_non_linear(self, u, t):
117119
me[0] = 0.0
118120
me[-1] = 0.0
119121

122+
me[:] /= self.params.Cv
123+
120124
return me
121125

122126
def eval_f(self, u, t):
@@ -131,7 +135,7 @@ def eval_f(self, u, t):
131135
dtype_f: The right hand side
132136
"""
133137
f = self.dtype_f(self.init)
134-
f[:] = (self.A.dot(u.flatten()).reshape(self.params.nvars) + self.eval_f_non_linear(u, t)) / self.params.Cv
138+
f[:] = self.A.dot(u.flatten()).reshape(self.params.nvars) + self.eval_f_non_linear(u, t)
135139
return f
136140

137141
def solve_system(self, rhs, factor, u0, t):
@@ -156,7 +160,7 @@ def get_non_linear_Jacobian(u):
156160
u (dtype_u): Current solution
157161
158162
Returns:
159-
dtype_u: The derivative of the non-linear part of the solution w.r.t. to the solution.
163+
scipy.sparse.csc: The derivative of the non-linear part of the solution w.r.t. to the solution.
160164
"""
161165
u_thresh = self.params.u_thresh
162166
u_max = self.params.u_max
@@ -172,28 +176,34 @@ def get_non_linear_Jacobian(u):
172176
me[0] = 0.0
173177
me[-1] = 0.0
174178

175-
me = self.dtype_u(self.init)
176-
return me
179+
me[:] /= self.params.Cv
180+
181+
return sp.diags(me, format='csc')
177182

178183
u = self.dtype_u(u0)
179184
res = np.inf
180-
for _n in range(0, self.params.newton_iter):
185+
for n in range(0, self.params.newton_iter):
181186
# assemble G such that G(u) = 0 at the solution of the step
182187
G = u - factor * self.eval_f(u, t) - rhs
183188

184189
res = np.linalg.norm(G, np.inf)
185-
if res <= self.params.newton_tol or np.isnan(res):
190+
if res <= self.params.newton_tol and n > 0: # we want to make at least one Newton iteration
186191
break
187192

188193
# assemble Jacobian J of G
189194
J = self.Id - factor * (self.A + get_non_linear_Jacobian(u))
190195

191196
# solve the linear system
192-
delta = np.linalg.solve(J, G)
197+
delta = spsolve(J, G)
198+
199+
if not np.isfinite(delta).all():
200+
break
193201

194202
# update solution
195203
u = u - delta
196204

205+
self.total_newton_iter += n
206+
197207
return u
198208

199209
def u_exact(self, t, u_init=None, t_init=None):
@@ -207,7 +217,7 @@ def u_exact(self, t, u_init=None, t_init=None):
207217
dtype_u: exact solution
208218
"""
209219

210-
me = self.dtype_u(self.init, val=0)
220+
me = self.dtype_u(self.init, val=0.0)
211221

212222
if t > 0:
213223

@@ -245,8 +255,8 @@ def eval_f(self, u, t):
245255
"""
246256

247257
f = self.dtype_f(self.init)
248-
f.impl[:] = self.A.dot(u.flatten()).reshape(self.params.nvars) / self.params.Cv
249-
f.expl[:] = self.eval_f_non_linear(u, t) / self.params.Cv
258+
f.impl[:] = self.A.dot(u.flatten()).reshape(self.params.nvars)
259+
f.expl[:] = self.eval_f_non_linear(u, t)
250260

251261
return f
252262

0 commit comments

Comments
 (0)