Skip to content

Commit 45809fe

Browse files
committed
Removed solve_collocation_system_Z implementation in Radau and improved test coverage.
1 parent 05352c4 commit 45809fe

File tree

2 files changed

+27
-153
lines changed

2 files changed

+27
-153
lines changed

scipy_dae/integrate/_dae/radau.py

Lines changed: 18 additions & 148 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,6 @@
1010
# de Swart proposes 0.067 for s=3.
1111
KAPPA = 1 # factor of the smooth limiter
1212

13-
# UNKNOWN_VELOCITIES = False
14-
UNKNOWN_VELOCITIES = True
15-
1613

1714
def butcher_tableau(s):
1815
"""
@@ -133,112 +130,7 @@ def radau_constants(s):
133130
return A, A_inv, c, T, TI, P, P2, b_hat1_implicit, v_implicit, v_explicit, MU_REAL, MU_COMPLEX, b_hat_implicit, b_hat_explicit, p
134131

135132

136-
def solve_collocation_system_Z(fun, t, y, h, Z0, scale, tol,
137-
LU_real, LU_complex, solve_lu,
138-
C, T, TI, A, A_inv, newton_max_iter):
139-
"""Solve the collocation system.
140-
141-
Parameters
142-
----------
143-
fun : callable
144-
Right-hand side of the system.
145-
t : float
146-
Current time.
147-
y : ndarray, shape (n,)
148-
Current state.
149-
h : float
150-
Step to try.
151-
Z0 : ndarray, shape (s, n)
152-
Initial guess for the solution. It determines new values of `y` at
153-
``t + h * C`` as ``y + Z0``, where ``C`` is the Radau method constants.
154-
scale : ndarray, shape (n)
155-
Problem tolerance scale, i.e. ``rtol * abs(y) + atol``.
156-
tol : float
157-
Tolerance to which solve the system. This value is compared with
158-
the normalized by `scale` error.
159-
LU_real, LU_complex
160-
LU decompositions of the system Jacobians.
161-
solve_lu : callable
162-
Callable which solves a linear system given a LU decomposition. The
163-
signature is ``solve_lu(LU, b)``.
164-
C : ndarray, shape (s,)
165-
Array containing the Radau IIA nodes.
166-
T, TI : ndarray, shape (s, s)
167-
Transformation matrix and inverse of the methods coefficient matrix A.
168-
A_inv : ndarray, shape (s, s)
169-
Inverse the methods coefficient matrix A.
170-
171-
Returns
172-
-------
173-
converged : bool
174-
Whether iterations converged.
175-
n_iter : int
176-
Number of completed iterations.
177-
Z : ndarray, shape (3, n)
178-
Found solution.
179-
rate : float
180-
The rate of convergence.
181-
"""
182-
s, n = Z0.shape
183-
ncs = s // 2
184-
tau = t + h * C
185-
186-
Z = Z0
187-
W = TI.dot(Z0)
188-
Yp = (A_inv / h) @ Z
189-
Y = y + Z
190-
191-
F = np.empty((s, n))
192-
193-
dW_norm_old = None
194-
dW = np.empty_like(W)
195-
converged = False
196-
rate = None
197-
for k in range(newton_max_iter):
198-
for i in range(s):
199-
F[i] = fun(tau[i], Y[i], Yp[i])
200-
201-
if not np.all(np.isfinite(F)):
202-
break
203-
204-
U = TI @ F
205-
f_real = -U[0]
206-
f_complex = np.empty((ncs, n), dtype=complex)
207-
for i in range(ncs):
208-
f_complex[i] = -(U[2 * i + 1] + 1j * U[2 * i + 2])
209-
210-
dW_real = solve_lu(LU_real, f_real)
211-
dW_complex = np.empty_like(f_complex)
212-
for i in range(ncs):
213-
dW_complex[i] = solve_lu(LU_complex[i], f_complex[i])
214-
215-
dW[0] = dW_real
216-
for i in range(ncs):
217-
dW[2 * i + 1] = dW_complex[i].real
218-
dW[2 * i + 2] = dW_complex[i].imag
219-
220-
dW_norm = norm(dW / scale)
221-
if dW_norm_old is not None:
222-
rate = dW_norm / dW_norm_old
223-
224-
if (rate is not None and (rate >= 1 or rate ** (newton_max_iter - k) / (1 - rate) * dW_norm > tol)):
225-
break
226-
227-
W += dW
228-
Z = T.dot(W)
229-
Yp = (A_inv / h) @ Z
230-
Y = y + Z
231-
232-
if (dW_norm == 0 or rate is not None and rate / (1 - rate) * dW_norm < tol):
233-
converged = True
234-
break
235-
236-
dW_norm_old = dW_norm
237-
238-
return converged, k + 1, Y, Yp, Z, rate
239-
240-
241-
def solve_collocation_system_Yp(fun, t, y, h, Yp0, scale, tol,
133+
def solve_collocation_system(fun, t, y, h, Yp0, scale, tol,
242134
LU_real, LU_complex, solve_lu,
243135
C, T, TI, A, newton_max_iter):
244136
s, n = Yp0.shape
@@ -629,43 +521,27 @@ def _step_impl(self):
629521
h_abs = np.abs(h)
630522

631523
if self.sol is None:
632-
if UNKNOWN_VELOCITIES:
633-
Yp0 = np.tile(yp, s).reshape(s, -1)
634-
else:
635-
Z0 = np.zeros((s, y.shape[0]))
524+
Yp0 = np.tile(yp, s).reshape(s, -1)
636525
else:
637-
if UNKNOWN_VELOCITIES:
638-
# note: this only works when we extrapolate the derivative
639-
# of the collocation polynomial but do not use the sth order
640-
# collocation polynomial for the derivatives as well.
641-
Yp0 = self.sol(t + h * C)[1].T
642-
# # Zp0 = self.sol(t + h * C)[1].T - yp
643-
# Z0 = self.sol(t + h * C)[0].T - y
644-
# Yp0 = (1 / h) * A_inv @ Z0
645-
else:
646-
Z0 = self.sol(t + h * C)[0].T - y
526+
# note: this only works when we extrapolate the derivative
527+
# of the collocation polynomial but do not use the sth order
528+
# collocation polynomial for the derivatives as well.
529+
Yp0 = self.sol(t + h * C)[1].T
530+
# # Zp0 = self.sol(t + h * C)[1].T - yp
531+
# Z0 = self.sol(t + h * C)[0].T - y
532+
# Yp0 = (1 / h) * A_inv @ Z0
647533
scale = atol + np.abs(y) * rtol
648534

649535
converged = False
650536
while not converged:
651537
if LU_real is None or LU_complex is None:
652-
if UNKNOWN_VELOCITIES:
653-
LU_real = self.lu(Jyp + h * MU_REAL * Jy)
654-
LU_complex = [self.lu(Jyp + h * MU * Jy) for MU in MU_COMPLEX]
655-
else:
656-
LU_real = self.lu(1 / (MU_REAL * h) * Jyp + Jy)
657-
LU_complex = [self.lu(1 / (MU * h) * Jyp + Jy) for MU in MU_COMPLEX]
658-
659-
if UNKNOWN_VELOCITIES:
660-
converged, n_iter, Y, Yp, Z, rate = solve_collocation_system_Yp(
661-
self.fun, t, y, h, Yp0, scale, newton_tol,
662-
LU_real, LU_complex, self.solve_lu,
663-
C, T, TI, A, newton_max_iter)
664-
else:
665-
converged, n_iter, Y, Yp, Z, rate = solve_collocation_system_Z(
666-
self.fun, t, y, h, Z0, scale, newton_tol,
667-
LU_real, LU_complex, self.solve_lu,
668-
C, T, TI, A, A_inv, newton_max_iter)
538+
LU_real = self.lu(Jyp + h * MU_REAL * Jy)
539+
LU_complex = [self.lu(Jyp + h * MU * Jy) for MU in MU_COMPLEX]
540+
541+
converged, n_iter, Y, Yp, Z, rate = solve_collocation_system(
542+
self.fun, t, y, h, Yp0, scale, newton_tol,
543+
LU_real, LU_complex, self.solve_lu,
544+
C, T, TI, A, newton_max_iter)
669545

670546
if not converged:
671547
if current_jac:
@@ -699,21 +575,15 @@ def _step_impl(self):
699575
# R(z) = b_hat1 / b_hats2 = DAMPING_RATIO_ERROR_ESTIMATE for z -> oo
700576
yp_hat_new = (v_implicit @ Yp - b_hat1_implicit * yp) / MU_REAL
701577
F = self.fun(t_new, y_new, yp_hat_new)
702-
if UNKNOWN_VELOCITIES:
703-
error_embedded = -h * MU_REAL * self.solve_lu(LU_real, F)
704-
else:
705-
error_embedded = -self.solve_lu(LU_real, F)
578+
error_embedded = -h * MU_REAL * self.solve_lu(LU_real, F)
706579
else:
707580
# compute implicit embedded method with `newton_iter_embedded` iterations
708581
yp_hat_new0 = -(y / h + b_hat1_implicit * yp + self.b_hat_implicit @ Yp) / MU_REAL
709582
y_hat_new = y_new.copy() # initial guess
710583
for _ in range(self.newton_iter_embedded):
711584
yp_hat_new = yp_hat_new0 + y_hat_new / (h * MU_REAL)
712585
F = self.fun(t_new, y_hat_new, yp_hat_new)
713-
if UNKNOWN_VELOCITIES:
714-
y_hat_new -= h * MU_REAL * self.solve_lu(LU_real, F)
715-
else:
716-
y_hat_new -= self.solve_lu(LU_real, F)
586+
y_hat_new -= h * MU_REAL * self.solve_lu(LU_real, F)
717587

718588
error_embedded = y_hat_new - y_new
719589

scipy_dae/integrate/_dae/tests/test_integration_robertson.py

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -51,18 +51,20 @@ def F_robertson(t, state, statep):
5151
assert res.njev < 50
5252

5353
else: # Radau
54-
for stages, continuous_error_weight in product(
54+
for stages, continuous_error_weight, newton_iter_embedded in product(
5555
[3, 5, 7], # stages
5656
[0.0, 0.5, 1.0], # continuous_error_weight
57+
[0, 1, 2], # newton_iter_embedded
5758
):
5859
res = solve_dae(F_robertson, tspan, y0, yp0, rtol=rtol,
5960
atol=atol, method=method, stages=stages,
60-
continuous_error_weight=continuous_error_weight)
61+
continuous_error_weight=continuous_error_weight,
62+
newton_iter_embedded=newton_iter_embedded)
6163

6264
# If the stiff mode is not activated correctly, these numbers will be much bigger
6365
assert res.nfev < 3300
6466
assert res.njev < 150
65-
assert res.nlu < 340
67+
assert res.nlu < 370
6668

6769

6870
@pytest.mark.slow
@@ -108,13 +110,15 @@ def F_robertson(t, y, yp):
108110
assert res.njev < 30
109111

110112
else: # Radau
111-
for stages, continuous_error_weight in product(
113+
for stages, continuous_error_weight, newton_iter_embedded in product(
112114
[3, 5, 7], # stages
113115
[0.0, 0.5, 1.0], # continuous_error_weight
116+
[0, 1, 2], # newton_iter_embedded
114117
):
115118
res = solve_dae(F_robertson, tspan, y0, yp0, rtol=rtol,
116119
atol=atol, method=method, stages=stages,
117-
continuous_error_weight=continuous_error_weight)
120+
continuous_error_weight=continuous_error_weight,
121+
newton_iter_embedded=newton_iter_embedded)
118122

119123
# If the stiff mode is not activated correctly, these numbers will be much bigger
120124
assert res.nfev < 2150

0 commit comments

Comments
 (0)