Skip to content

Commit d316cde

Browse files
committed
Implemented separate Jacobian inversion to fix bug pointed out by
@JHopeCollins
1 parent 9cddb17 commit d316cde

File tree

6 files changed

+70
-28
lines changed

6 files changed

+70
-28
lines changed

pySDC/core/problem.py

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -164,3 +164,35 @@ def plot(self, u, t=None, fig=None):
164164
None
165165
"""
166166
raise NotImplementedError
167+
168+
def solve_system(self, rhs, dt, u0, t):
169+
"""
170+
Perform an Euler step.
171+
172+
Args:
173+
rhs: Right hand side for the Euler step
174+
dt (float): Step size for the Euler step
175+
u0: Initial guess
176+
t (float): Current time
177+
178+
Returns:
179+
solution to the Euler step
180+
"""
181+
raise NotImplementedError
182+
183+
def solve_jacobian(self, rhs, dt, u=None, u0=None, t=0, **kwargs):
184+
"""
185+
Solve the Jacobian for an Euler step, linearized around u.
186+
This defaults to an Euler step to accommodate linear problems.
187+
188+
Args:
189+
rhs: Right hand side for the Euler step
190+
dt (float): Step size for the Euler step
191+
u: Solution to linearize around
192+
u0: Initial guess
193+
t (float): Current time
194+
195+
Returns:
196+
Solution
197+
"""
198+
return self.solve_system(rhs, dt, u0=u, t=t, **kwargs)

pySDC/implementations/controller_classes/controller_ParaDiag_nonMPI.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -213,12 +213,12 @@ def it_ParaDiag(self, local_MS_running):
213213
for hook in self.hooks:
214214
hook.pre_sweep(step=S, level_number=0)
215215

216-
# replace the values stored in the steps with the residuals in order to compute the increment
217-
self.swap_solution_for_all_at_once_residual(local_MS_running)
218-
219216
# communicate average residual for setting up Jacobians for non-linear problems
220217
self.prepare_Jacobians(local_MS_running)
221218

219+
# replace the values stored in the steps with the residuals in order to compute the increment
220+
self.swap_solution_for_all_at_once_residual(local_MS_running)
221+
222222
# weighted FFT in time
223223
self.FFT_in_time()
224224

pySDC/implementations/problem_classes/Van_der_Pol_implicit.py

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@ def __init__(
6969
localVars=locals(),
7070
)
7171
self.work_counters['newton'] = WorkCounter()
72+
self.work_counters['jacobian_solves'] = WorkCounter()
7273
self.work_counters['rhs'] = WorkCounter()
7374

7475
def u_exact(self, t, u_init=None, t_init=None):
@@ -167,13 +168,7 @@ def solve_system(self, rhs, dt, u0, t):
167168
if res < self.newton_tol or np.isnan(res):
168169
break
169170

170-
# prefactor for dg/du
171-
c = 1.0 / (-2 * dt**2 * mu * x1 * x2 - dt**2 - 1 + dt * mu * (1 - x1**2))
172-
# assemble dg/du
173-
dg = c * np.array([[dt * mu * (1 - x1**2) - 1, -dt], [2 * dt * mu * x1 * x2 + dt, -1]])
174-
175-
# newton update: u1 = u0 - g/dg
176-
u -= np.dot(dg, g)
171+
u -= self.solve_jacobian(g, dt, u)
177172

178173
# set new values and increase iteration count
179174
x1 = u[0]
@@ -191,3 +186,16 @@ def solve_system(self, rhs, dt, u0, t):
191186
raise ProblemError('Newton did not converge after %i iterations, error is %s' % (n, res))
192187

193188
return u
189+
190+
def solve_jacobian(self, rhs, dt, u, **kwargs):
191+
mu = self.mu
192+
u1 = u[0]
193+
u2 = u[1]
194+
195+
# assemble prefactor
196+
c = 1.0 / (-2 * dt**2 * mu * u1 * u2 - dt**2 - 1 + dt * mu * (1 - u1**2))
197+
# assemble (dg/du)^-1
198+
dg = c * np.array([[dt * mu * (1 - u1**2) - 1, -dt], [2 * dt * mu * u1 * u2 + dt, -1]])
199+
200+
self.work_counters['jacobian_solves']()
201+
return np.dot(dg, rhs)

pySDC/implementations/sweeper_classes/ParaDiagSweepers.py

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -105,10 +105,16 @@ def update_nodes(self):
105105
x1 = self.mat_vec(self.S_inv, [self.level.u[m + 1] for m in range(M)])
106106
else:
107107
x1 = self.mat_vec(self.S_inv, [self.level.u[0] for _ in range(M)])
108+
109+
# get averaged state over all nodes for constructing the Jacobian
110+
u_avg = P.u_init
111+
if not any(me is None for me in L.u_avg):
112+
for m in range(M):
113+
u_avg += L.u_avg[m] / M
114+
108115
x2 = []
109116
for m in range(M):
110-
u0 = L.u_avg[m] if L.u_avg[m] is not None else x1[m]
111-
x2.append(P.solve_system(x1[m], self.w[m] * L.dt, u0=u0, t=L.time + L.dt * self.coll.nodes[m]))
117+
x2.append(P.solve_jacobian(x1[m], self.w[m] * L.dt, u=u_avg, t=L.time + L.dt * self.coll.nodes[m]))
112118
z = self.mat_vec(self.S, x2)
113119
y = self.mat_vec(self.params.G_inv, z)
114120

pySDC/tutorial/step_9/B_paradiag_for_nonlinear_problems.py

Lines changed: 7 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,10 @@
1515
have the same Jacobian on all steps.
1616
The ParaDiag iteration then proceeds as follows:
1717
- (1) Compute residual of composite collocation problem
18-
- (2) Average the residual across the steps as preparation for computing the average Jacobian
19-
Note that we still have values for each collocation node and space position.
18+
- (2) Average the solution across the steps and nodes as preparation for computing the average Jacobian
2019
- (3) Weighted FFT in time to diagonalize E_alpha
21-
- (4) Solve for the increment by perform a single Newton iterations on the subproblems on the different steps and
22-
nodes. The Jacobian is based on the averaged residual from (2)
20+
- (4) Solve for the increment by inverting the averaged Jacobian from (2) on the subproblems on the different steps
21+
and nodes.
2322
- (5) Weighted iFFT in time
2423
- (6) Increment solution
2524
As IMEX ParaDiag is a trivial extension of ParaDiag for linear problems, we focus on the second approach here.
@@ -170,15 +169,15 @@ def residual(_u, u0):
170169
sol_paradiag = u.copy() * 0j
171170
u0 = u.copy()
172171

173-
buf = prob.u_init
174172
niter = 0
175173
res = residual(sol_paradiag, u0)
176174
while np.max(np.abs(res)) > restol:
177175
# compute all-at-once residual
178176
res = residual(sol_paradiag, u0)
179177

180-
# compute residual averaged across the L steps. This is the difference to ParaDiag for linear problems.
181-
res_avg = np.mean(res, axis=0)
178+
# compute residual averaged across the L steps and M nodes. This is the difference to ParaDiag for linear problems.
179+
u_avg = prob.u_init
180+
u_avg[:] = np.mean(sol_paradiag, axis=(0, 1))
182181

183182
# weighted FFT in time
184183
x = np.fft.fft(mat_vec(J_inv.toarray(), res), axis=0)
@@ -191,8 +190,7 @@ def residual(_u, u0):
191190
x1 = S_inv[l] @ x[l]
192191
x2 = np.empty_like(x1)
193192
for m in range(M):
194-
buf[:] = res_avg[m] # set up averaged Jacobian by using averaged residual as initial guess
195-
x2[m, :] = prob.solve_system(x1[m], w[l][m] * dt, u0=buf, t=l * dt)
193+
x2[m, :] = prob.solve_jacobian(x1[m], w[l][m] * dt, u=u_avg, t=l * dt)
196194
z = S[l] @ x2
197195
y[l, ...] = sp.linalg.spsolve(G[l], z)
198196

pySDC/tutorial/step_9/C_paradiag_in_pySDC.py

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -47,11 +47,9 @@ def get_description(problem='advection', mode='ParaDiag'):
4747
from pySDC.implementations.sweeper_classes.ParaDiagSweepers import QDiagonalization as sweeper_class
4848

4949
# we only want to use the averaged Jacobian and do only one Newton iteration per ParaDiag iteration!
50-
newton_maxiter = 1
5150
else:
5251
from pySDC.implementations.sweeper_classes.generic_implicit import generic_implicit as sweeper_class
5352

54-
newton_maxiter = 99
5553
# need diagonal preconditioner for same concurrency as ParaDiag
5654
sweeper_params['QI'] = 'MIN-SR-S'
5755

@@ -63,7 +61,7 @@ def get_description(problem='advection', mode='ParaDiag'):
6361
from pySDC.implementations.problem_classes.Van_der_Pol_implicit import vanderpol as problem_class
6462

6563
# need to not raise an error when Newton has not converged because we do only one iteration
66-
problem_params = {'newton_maxiter': newton_maxiter, 'crash_at_maxiter': False, 'mu': 1, 'newton_tol': 1e-9}
64+
problem_params = {'newton_maxiter': 99, 'crash_at_maxiter': False, 'mu': 1, 'newton_tol': 1e-9}
6765

6866
step_params = {}
6967
step_params['maxiter'] = 99
@@ -164,11 +162,11 @@ def compare_ParaDiag_and_PFASST(n_steps, problem):
164162
f'Maximum GMRES iterations on each step: {max(me[1] for me in k_GMRES_PD)} in ParaDiag, {max(me[1] for me in k_GMRES_PF)} in single-level PFASST and {sum(me[1] for me in k_GMRES_S)} total GMRES iterations in serial'
165163
)
166164
elif problem == 'vdp':
167-
k_Newton_PD = get_sorted(stats_PD, type='work_newton')
168-
k_Newton_PF = get_sorted(stats_PF, type='work_newton')
169-
k_Newton_S = get_sorted(stats_S, type='work_newton')
165+
k_Jac_PD = get_sorted(stats_PD, type='work_jacobian_solves')
166+
k_Jac_PF = get_sorted(stats_PF, type='work_jacobian_solves')
167+
k_Jac_S = get_sorted(stats_S, type='work_jacobian_solves')
170168
my_print(
171-
f'Maximum Newton iterations on each step: {max(me[1] for me in k_Newton_PD)} in ParaDiag, {max(me[1] for me in k_Newton_PF)} in single-level PFASST and {sum(me[1] for me in k_Newton_S)} total Newton iterations in serial'
169+
f'Maximum Jacabian solves on each step: {max(me[1] for me in k_Jac_PD)} in ParaDiag, {max(me[1] for me in k_Jac_PF)} in single-level PFASST and {sum(me[1] for me in k_Jac_S)} total Jacobian solves in serial'
172170
)
173171
my_print()
174172

0 commit comments

Comments
 (0)