728 lines
23 KiB
Python
728 lines
23 KiB
Python
|
from __future__ import division, print_function, absolute_import
|
||
|
from itertools import product
|
||
|
from numpy.testing import (assert_, assert_allclose,
|
||
|
assert_equal, assert_no_warnings)
|
||
|
from pytest import raises as assert_raises
|
||
|
from scipy._lib._numpy_compat import suppress_warnings
|
||
|
import numpy as np
|
||
|
from scipy.optimize._numdiff import group_columns
|
||
|
from scipy.integrate import solve_ivp, RK23, RK45, Radau, BDF, LSODA
|
||
|
from scipy.integrate import OdeSolution
|
||
|
from scipy.integrate._ivp.common import num_jac
|
||
|
from scipy.integrate._ivp.base import ConstantDenseOutput
|
||
|
from scipy.sparse import coo_matrix, csc_matrix
|
||
|
|
||
|
|
||
|
def fun_linear(t, y):
|
||
|
return np.array([-y[0] - 5 * y[1], y[0] + y[1]])
|
||
|
|
||
|
|
||
|
def jac_linear():
|
||
|
return np.array([[-1, -5], [1, 1]])
|
||
|
|
||
|
|
||
|
def sol_linear(t):
|
||
|
return np.vstack((-5 * np.sin(2 * t),
|
||
|
2 * np.cos(2 * t) + np.sin(2 * t)))
|
||
|
|
||
|
|
||
|
def fun_rational(t, y):
|
||
|
return np.array([y[1] / t,
|
||
|
y[1] * (y[0] + 2 * y[1] - 1) / (t * (y[0] - 1))])
|
||
|
|
||
|
|
||
|
def fun_rational_vectorized(t, y):
|
||
|
return np.vstack((y[1] / t,
|
||
|
y[1] * (y[0] + 2 * y[1] - 1) / (t * (y[0] - 1))))
|
||
|
|
||
|
|
||
|
def jac_rational(t, y):
|
||
|
return np.array([
|
||
|
[0, 1 / t],
|
||
|
[-2 * y[1] ** 2 / (t * (y[0] - 1) ** 2),
|
||
|
(y[0] + 4 * y[1] - 1) / (t * (y[0] - 1))]
|
||
|
])
|
||
|
|
||
|
|
||
|
def jac_rational_sparse(t, y):
|
||
|
return csc_matrix([
|
||
|
[0, 1 / t],
|
||
|
[-2 * y[1] ** 2 / (t * (y[0] - 1) ** 2),
|
||
|
(y[0] + 4 * y[1] - 1) / (t * (y[0] - 1))]
|
||
|
])
|
||
|
|
||
|
|
||
|
def sol_rational(t):
|
||
|
return np.asarray((t / (t + 10), 10 * t / (t + 10) ** 2))
|
||
|
|
||
|
|
||
|
def fun_medazko(t, y):
|
||
|
n = y.shape[0] // 2
|
||
|
k = 100
|
||
|
c = 4
|
||
|
|
||
|
phi = 2 if t <= 5 else 0
|
||
|
y = np.hstack((phi, 0, y, y[-2]))
|
||
|
|
||
|
d = 1 / n
|
||
|
j = np.arange(n) + 1
|
||
|
alpha = 2 * (j * d - 1) ** 3 / c ** 2
|
||
|
beta = (j * d - 1) ** 4 / c ** 2
|
||
|
|
||
|
j_2_p1 = 2 * j + 2
|
||
|
j_2_m3 = 2 * j - 2
|
||
|
j_2_m1 = 2 * j
|
||
|
j_2 = 2 * j + 1
|
||
|
|
||
|
f = np.empty(2 * n)
|
||
|
f[::2] = (alpha * (y[j_2_p1] - y[j_2_m3]) / (2 * d) +
|
||
|
beta * (y[j_2_m3] - 2 * y[j_2_m1] + y[j_2_p1]) / d ** 2 -
|
||
|
k * y[j_2_m1] * y[j_2])
|
||
|
f[1::2] = -k * y[j_2] * y[j_2_m1]
|
||
|
|
||
|
return f
|
||
|
|
||
|
|
||
|
def medazko_sparsity(n):
|
||
|
cols = []
|
||
|
rows = []
|
||
|
|
||
|
i = np.arange(n) * 2
|
||
|
|
||
|
cols.append(i[1:])
|
||
|
rows.append(i[1:] - 2)
|
||
|
|
||
|
cols.append(i)
|
||
|
rows.append(i)
|
||
|
|
||
|
cols.append(i)
|
||
|
rows.append(i + 1)
|
||
|
|
||
|
cols.append(i[:-1])
|
||
|
rows.append(i[:-1] + 2)
|
||
|
|
||
|
i = np.arange(n) * 2 + 1
|
||
|
|
||
|
cols.append(i)
|
||
|
rows.append(i)
|
||
|
|
||
|
cols.append(i)
|
||
|
rows.append(i - 1)
|
||
|
|
||
|
cols = np.hstack(cols)
|
||
|
rows = np.hstack(rows)
|
||
|
|
||
|
return coo_matrix((np.ones_like(cols), (cols, rows)))
|
||
|
|
||
|
|
||
|
def fun_complex(t, y):
|
||
|
return -y
|
||
|
|
||
|
|
||
|
def jac_complex(t, y):
|
||
|
return -np.eye(y.shape[0])
|
||
|
|
||
|
|
||
|
def jac_complex_sparse(t, y):
|
||
|
return csc_matrix(jac_complex(t, y))
|
||
|
|
||
|
|
||
|
def sol_complex(t):
|
||
|
y = (0.5 + 1j) * np.exp(-t)
|
||
|
return y.reshape((1, -1))
|
||
|
|
||
|
|
||
|
def compute_error(y, y_true, rtol, atol):
|
||
|
e = (y - y_true) / (atol + rtol * np.abs(y_true))
|
||
|
return np.sqrt(np.sum(np.real(e * e.conj()), axis=0) / e.shape[0])
|
||
|
|
||
|
|
||
|
def test_integration():
|
||
|
rtol = 1e-3
|
||
|
atol = 1e-6
|
||
|
y0 = [1/3, 2/9]
|
||
|
|
||
|
for vectorized, method, t_span, jac in product(
|
||
|
[False, True],
|
||
|
['RK23', 'RK45', 'Radau', 'BDF', 'LSODA'],
|
||
|
[[5, 9], [5, 1]],
|
||
|
[None, jac_rational, jac_rational_sparse]):
|
||
|
|
||
|
if vectorized:
|
||
|
fun = fun_rational_vectorized
|
||
|
else:
|
||
|
fun = fun_rational
|
||
|
|
||
|
with suppress_warnings() as sup:
|
||
|
sup.filter(UserWarning,
|
||
|
"The following arguments have no effect for a chosen solver: `jac`")
|
||
|
res = solve_ivp(fun, t_span, y0, rtol=rtol,
|
||
|
atol=atol, method=method, dense_output=True,
|
||
|
jac=jac, vectorized=vectorized)
|
||
|
assert_equal(res.t[0], t_span[0])
|
||
|
assert_(res.t_events is None)
|
||
|
assert_(res.success)
|
||
|
assert_equal(res.status, 0)
|
||
|
|
||
|
assert_(res.nfev < 40)
|
||
|
|
||
|
if method in ['RK23', 'RK45', 'LSODA']:
|
||
|
assert_equal(res.njev, 0)
|
||
|
assert_equal(res.nlu, 0)
|
||
|
else:
|
||
|
assert_(0 < res.njev < 3)
|
||
|
assert_(0 < res.nlu < 10)
|
||
|
|
||
|
y_true = sol_rational(res.t)
|
||
|
e = compute_error(res.y, y_true, rtol, atol)
|
||
|
assert_(np.all(e < 5))
|
||
|
|
||
|
tc = np.linspace(*t_span)
|
||
|
yc_true = sol_rational(tc)
|
||
|
yc = res.sol(tc)
|
||
|
|
||
|
e = compute_error(yc, yc_true, rtol, atol)
|
||
|
assert_(np.all(e < 5))
|
||
|
|
||
|
tc = (t_span[0] + t_span[-1]) / 2
|
||
|
yc_true = sol_rational(tc)
|
||
|
yc = res.sol(tc)
|
||
|
|
||
|
e = compute_error(yc, yc_true, rtol, atol)
|
||
|
assert_(np.all(e < 5))
|
||
|
|
||
|
# LSODA for some reasons doesn't pass the polynomial through the
|
||
|
# previous points exactly after the order change. It might be some
|
||
|
# bug in LSOSA implementation or maybe we missing something.
|
||
|
if method != 'LSODA':
|
||
|
assert_allclose(res.sol(res.t), res.y, rtol=1e-15, atol=1e-15)
|
||
|
|
||
|
|
||
|
def test_integration_complex():
|
||
|
rtol = 1e-3
|
||
|
atol = 1e-6
|
||
|
y0 = [0.5 + 1j]
|
||
|
t_span = [0, 1]
|
||
|
tc = np.linspace(t_span[0], t_span[1])
|
||
|
for method, jac in product(['RK23', 'RK45', 'BDF'],
|
||
|
[None, jac_complex, jac_complex_sparse]):
|
||
|
with suppress_warnings() as sup:
|
||
|
sup.filter(UserWarning,
|
||
|
"The following arguments have no effect for a chosen solver: `jac`")
|
||
|
res = solve_ivp(fun_complex, t_span, y0, method=method,
|
||
|
dense_output=True, rtol=rtol, atol=atol, jac=jac)
|
||
|
|
||
|
assert_equal(res.t[0], t_span[0])
|
||
|
assert_(res.t_events is None)
|
||
|
assert_(res.success)
|
||
|
assert_equal(res.status, 0)
|
||
|
|
||
|
assert_(res.nfev < 25)
|
||
|
if method == 'BDF':
|
||
|
assert_equal(res.njev, 1)
|
||
|
assert_(res.nlu < 6)
|
||
|
else:
|
||
|
assert_equal(res.njev, 0)
|
||
|
assert_equal(res.nlu, 0)
|
||
|
|
||
|
y_true = sol_complex(res.t)
|
||
|
e = compute_error(res.y, y_true, rtol, atol)
|
||
|
assert_(np.all(e < 5))
|
||
|
|
||
|
yc_true = sol_complex(tc)
|
||
|
yc = res.sol(tc)
|
||
|
e = compute_error(yc, yc_true, rtol, atol)
|
||
|
|
||
|
assert_(np.all(e < 5))
|
||
|
|
||
|
|
||
|
def test_integration_sparse_difference():
|
||
|
n = 200
|
||
|
t_span = [0, 20]
|
||
|
y0 = np.zeros(2 * n)
|
||
|
y0[1::2] = 1
|
||
|
sparsity = medazko_sparsity(n)
|
||
|
|
||
|
for method in ['BDF', 'Radau']:
|
||
|
res = solve_ivp(fun_medazko, t_span, y0, method=method,
|
||
|
jac_sparsity=sparsity)
|
||
|
|
||
|
assert_equal(res.t[0], t_span[0])
|
||
|
assert_(res.t_events is None)
|
||
|
assert_(res.success)
|
||
|
assert_equal(res.status, 0)
|
||
|
|
||
|
assert_allclose(res.y[78, -1], 0.233994e-3, rtol=1e-2)
|
||
|
assert_allclose(res.y[79, -1], 0, atol=1e-3)
|
||
|
assert_allclose(res.y[148, -1], 0.359561e-3, rtol=1e-2)
|
||
|
assert_allclose(res.y[149, -1], 0, atol=1e-3)
|
||
|
assert_allclose(res.y[198, -1], 0.117374129e-3, rtol=1e-2)
|
||
|
assert_allclose(res.y[199, -1], 0.6190807e-5, atol=1e-3)
|
||
|
assert_allclose(res.y[238, -1], 0, atol=1e-3)
|
||
|
assert_allclose(res.y[239, -1], 0.9999997, rtol=1e-2)
|
||
|
|
||
|
|
||
|
def test_integration_const_jac():
|
||
|
rtol = 1e-3
|
||
|
atol = 1e-6
|
||
|
y0 = [0, 2]
|
||
|
t_span = [0, 2]
|
||
|
J = jac_linear()
|
||
|
J_sparse = csc_matrix(J)
|
||
|
|
||
|
for method, jac in product(['Radau', 'BDF'], [J, J_sparse]):
|
||
|
res = solve_ivp(fun_linear, t_span, y0, rtol=rtol, atol=atol,
|
||
|
method=method, dense_output=True, jac=jac)
|
||
|
assert_equal(res.t[0], t_span[0])
|
||
|
assert_(res.t_events is None)
|
||
|
assert_(res.success)
|
||
|
assert_equal(res.status, 0)
|
||
|
|
||
|
assert_(res.nfev < 100)
|
||
|
assert_equal(res.njev, 0)
|
||
|
assert_(0 < res.nlu < 15)
|
||
|
|
||
|
y_true = sol_linear(res.t)
|
||
|
e = compute_error(res.y, y_true, rtol, atol)
|
||
|
assert_(np.all(e < 10))
|
||
|
|
||
|
tc = np.linspace(*t_span)
|
||
|
yc_true = sol_linear(tc)
|
||
|
yc = res.sol(tc)
|
||
|
|
||
|
e = compute_error(yc, yc_true, rtol, atol)
|
||
|
assert_(np.all(e < 15))
|
||
|
|
||
|
assert_allclose(res.sol(res.t), res.y, rtol=1e-14, atol=1e-14)
|
||
|
|
||
|
|
||
|
def test_events():
|
||
|
def event_rational_1(t, y):
|
||
|
return y[0] - y[1] ** 0.7
|
||
|
|
||
|
def event_rational_2(t, y):
|
||
|
return y[1] ** 0.6 - y[0]
|
||
|
|
||
|
def event_rational_3(t, y):
|
||
|
return t - 7.4
|
||
|
|
||
|
event_rational_3.terminal = True
|
||
|
|
||
|
for method in ['RK23', 'RK45', 'Radau', 'BDF', 'LSODA']:
|
||
|
res = solve_ivp(fun_rational, [5, 8], [1/3, 2/9], method=method,
|
||
|
events=(event_rational_1, event_rational_2))
|
||
|
assert_equal(res.status, 0)
|
||
|
assert_equal(res.t_events[0].size, 1)
|
||
|
assert_equal(res.t_events[1].size, 1)
|
||
|
assert_(5.3 < res.t_events[0][0] < 5.7)
|
||
|
assert_(7.3 < res.t_events[1][0] < 7.7)
|
||
|
|
||
|
event_rational_1.direction = 1
|
||
|
event_rational_2.direction = 1
|
||
|
res = solve_ivp(fun_rational, [5, 8], [1 / 3, 2 / 9], method=method,
|
||
|
events=(event_rational_1, event_rational_2))
|
||
|
assert_equal(res.status, 0)
|
||
|
assert_equal(res.t_events[0].size, 1)
|
||
|
assert_equal(res.t_events[1].size, 0)
|
||
|
assert_(5.3 < res.t_events[0][0] < 5.7)
|
||
|
|
||
|
event_rational_1.direction = -1
|
||
|
event_rational_2.direction = -1
|
||
|
res = solve_ivp(fun_rational, [5, 8], [1 / 3, 2 / 9], method=method,
|
||
|
events=(event_rational_1, event_rational_2))
|
||
|
assert_equal(res.status, 0)
|
||
|
assert_equal(res.t_events[0].size, 0)
|
||
|
assert_equal(res.t_events[1].size, 1)
|
||
|
assert_(7.3 < res.t_events[1][0] < 7.7)
|
||
|
|
||
|
event_rational_1.direction = 0
|
||
|
event_rational_2.direction = 0
|
||
|
|
||
|
res = solve_ivp(fun_rational, [5, 8], [1 / 3, 2 / 9], method=method,
|
||
|
events=(event_rational_1, event_rational_2,
|
||
|
event_rational_3), dense_output=True)
|
||
|
assert_equal(res.status, 1)
|
||
|
assert_equal(res.t_events[0].size, 1)
|
||
|
assert_equal(res.t_events[1].size, 0)
|
||
|
assert_equal(res.t_events[2].size, 1)
|
||
|
assert_(5.3 < res.t_events[0][0] < 5.7)
|
||
|
assert_(7.3 < res.t_events[2][0] < 7.5)
|
||
|
|
||
|
res = solve_ivp(fun_rational, [5, 8], [1 / 3, 2 / 9], method=method,
|
||
|
events=event_rational_1, dense_output=True)
|
||
|
assert_equal(res.status, 0)
|
||
|
assert_equal(res.t_events[0].size, 1)
|
||
|
assert_(5.3 < res.t_events[0][0] < 5.7)
|
||
|
|
||
|
# Also test that termination by event doesn't break interpolants.
|
||
|
tc = np.linspace(res.t[0], res.t[-1])
|
||
|
yc_true = sol_rational(tc)
|
||
|
yc = res.sol(tc)
|
||
|
e = compute_error(yc, yc_true, 1e-3, 1e-6)
|
||
|
assert_(np.all(e < 5))
|
||
|
|
||
|
# Test in backward direction.
|
||
|
event_rational_1.direction = 0
|
||
|
event_rational_2.direction = 0
|
||
|
for method in ['RK23', 'RK45', 'Radau', 'BDF', 'LSODA']:
|
||
|
res = solve_ivp(fun_rational, [8, 5], [4/9, 20/81], method=method,
|
||
|
events=(event_rational_1, event_rational_2))
|
||
|
assert_equal(res.status, 0)
|
||
|
assert_equal(res.t_events[0].size, 1)
|
||
|
assert_equal(res.t_events[1].size, 1)
|
||
|
assert_(5.3 < res.t_events[0][0] < 5.7)
|
||
|
assert_(7.3 < res.t_events[1][0] < 7.7)
|
||
|
|
||
|
event_rational_1.direction = -1
|
||
|
event_rational_2.direction = -1
|
||
|
res = solve_ivp(fun_rational, [8, 5], [4/9, 20/81], method=method,
|
||
|
events=(event_rational_1, event_rational_2))
|
||
|
assert_equal(res.status, 0)
|
||
|
assert_equal(res.t_events[0].size, 1)
|
||
|
assert_equal(res.t_events[1].size, 0)
|
||
|
assert_(5.3 < res.t_events[0][0] < 5.7)
|
||
|
|
||
|
event_rational_1.direction = 1
|
||
|
event_rational_2.direction = 1
|
||
|
res = solve_ivp(fun_rational, [8, 5], [4/9, 20/81], method=method,
|
||
|
events=(event_rational_1, event_rational_2))
|
||
|
assert_equal(res.status, 0)
|
||
|
assert_equal(res.t_events[0].size, 0)
|
||
|
assert_equal(res.t_events[1].size, 1)
|
||
|
assert_(7.3 < res.t_events[1][0] < 7.7)
|
||
|
|
||
|
event_rational_1.direction = 0
|
||
|
event_rational_2.direction = 0
|
||
|
|
||
|
res = solve_ivp(fun_rational, [8, 5], [4/9, 20/81], method=method,
|
||
|
events=(event_rational_1, event_rational_2,
|
||
|
event_rational_3), dense_output=True)
|
||
|
assert_equal(res.status, 1)
|
||
|
assert_equal(res.t_events[0].size, 0)
|
||
|
assert_equal(res.t_events[1].size, 1)
|
||
|
assert_equal(res.t_events[2].size, 1)
|
||
|
assert_(7.3 < res.t_events[1][0] < 7.7)
|
||
|
assert_(7.3 < res.t_events[2][0] < 7.5)
|
||
|
|
||
|
# Also test that termination by event doesn't break interpolants.
|
||
|
tc = np.linspace(res.t[-1], res.t[0])
|
||
|
yc_true = sol_rational(tc)
|
||
|
yc = res.sol(tc)
|
||
|
e = compute_error(yc, yc_true, 1e-3, 1e-6)
|
||
|
assert_(np.all(e < 5))
|
||
|
|
||
|
|
||
|
def test_max_step():
|
||
|
rtol = 1e-3
|
||
|
atol = 1e-6
|
||
|
y0 = [1/3, 2/9]
|
||
|
for method in [RK23, RK45, Radau, BDF, LSODA]:
|
||
|
for t_span in ([5, 9], [5, 1]):
|
||
|
res = solve_ivp(fun_rational, t_span, y0, rtol=rtol,
|
||
|
max_step=0.5, atol=atol, method=method,
|
||
|
dense_output=True)
|
||
|
assert_equal(res.t[0], t_span[0])
|
||
|
assert_equal(res.t[-1], t_span[-1])
|
||
|
assert_(np.all(np.abs(np.diff(res.t)) <= 0.5))
|
||
|
assert_(res.t_events is None)
|
||
|
assert_(res.success)
|
||
|
assert_equal(res.status, 0)
|
||
|
|
||
|
y_true = sol_rational(res.t)
|
||
|
e = compute_error(res.y, y_true, rtol, atol)
|
||
|
assert_(np.all(e < 5))
|
||
|
|
||
|
tc = np.linspace(*t_span)
|
||
|
yc_true = sol_rational(tc)
|
||
|
yc = res.sol(tc)
|
||
|
|
||
|
e = compute_error(yc, yc_true, rtol, atol)
|
||
|
assert_(np.all(e < 5))
|
||
|
|
||
|
# See comment in test_integration.
|
||
|
if method is not LSODA:
|
||
|
assert_allclose(res.sol(res.t), res.y, rtol=1e-15, atol=1e-15)
|
||
|
|
||
|
assert_raises(ValueError, method, fun_rational, t_span[0], y0,
|
||
|
t_span[1], max_step=-1)
|
||
|
|
||
|
if method is not LSODA:
|
||
|
solver = method(fun_rational, t_span[0], y0, t_span[1],
|
||
|
rtol=rtol, atol=atol, max_step=1e-20)
|
||
|
message = solver.step()
|
||
|
|
||
|
assert_equal(solver.status, 'failed')
|
||
|
assert_("step size is less" in message)
|
||
|
assert_raises(RuntimeError, solver.step)
|
||
|
|
||
|
|
||
|
def test_t_eval():
|
||
|
rtol = 1e-3
|
||
|
atol = 1e-6
|
||
|
y0 = [1/3, 2/9]
|
||
|
for t_span in ([5, 9], [5, 1]):
|
||
|
t_eval = np.linspace(t_span[0], t_span[1], 10)
|
||
|
res = solve_ivp(fun_rational, t_span, y0, rtol=rtol, atol=atol,
|
||
|
t_eval=t_eval)
|
||
|
assert_equal(res.t, t_eval)
|
||
|
assert_(res.t_events is None)
|
||
|
assert_(res.success)
|
||
|
assert_equal(res.status, 0)
|
||
|
|
||
|
y_true = sol_rational(res.t)
|
||
|
e = compute_error(res.y, y_true, rtol, atol)
|
||
|
assert_(np.all(e < 5))
|
||
|
|
||
|
t_eval = [5, 5.01, 7, 8, 8.01, 9]
|
||
|
res = solve_ivp(fun_rational, [5, 9], y0, rtol=rtol, atol=atol,
|
||
|
t_eval=t_eval)
|
||
|
assert_equal(res.t, t_eval)
|
||
|
assert_(res.t_events is None)
|
||
|
assert_(res.success)
|
||
|
assert_equal(res.status, 0)
|
||
|
|
||
|
y_true = sol_rational(res.t)
|
||
|
e = compute_error(res.y, y_true, rtol, atol)
|
||
|
assert_(np.all(e < 5))
|
||
|
|
||
|
t_eval = [5, 4.99, 3, 1.5, 1.1, 1.01, 1]
|
||
|
res = solve_ivp(fun_rational, [5, 1], y0, rtol=rtol, atol=atol,
|
||
|
t_eval=t_eval)
|
||
|
assert_equal(res.t, t_eval)
|
||
|
assert_(res.t_events is None)
|
||
|
assert_(res.success)
|
||
|
assert_equal(res.status, 0)
|
||
|
|
||
|
t_eval = [5.01, 7, 8, 8.01]
|
||
|
res = solve_ivp(fun_rational, [5, 9], y0, rtol=rtol, atol=atol,
|
||
|
t_eval=t_eval)
|
||
|
assert_equal(res.t, t_eval)
|
||
|
assert_(res.t_events is None)
|
||
|
assert_(res.success)
|
||
|
assert_equal(res.status, 0)
|
||
|
|
||
|
y_true = sol_rational(res.t)
|
||
|
e = compute_error(res.y, y_true, rtol, atol)
|
||
|
assert_(np.all(e < 5))
|
||
|
|
||
|
t_eval = [4.99, 3, 1.5, 1.1, 1.01]
|
||
|
res = solve_ivp(fun_rational, [5, 1], y0, rtol=rtol, atol=atol,
|
||
|
t_eval=t_eval)
|
||
|
assert_equal(res.t, t_eval)
|
||
|
assert_(res.t_events is None)
|
||
|
assert_(res.success)
|
||
|
assert_equal(res.status, 0)
|
||
|
|
||
|
t_eval = [4, 6]
|
||
|
assert_raises(ValueError, solve_ivp, fun_rational, [5, 9], y0,
|
||
|
rtol=rtol, atol=atol, t_eval=t_eval)
|
||
|
|
||
|
|
||
|
def test_no_integration():
|
||
|
for method in ['RK23', 'RK45', 'Radau', 'BDF', 'LSODA']:
|
||
|
sol = solve_ivp(lambda t, y: -y, [4, 4], [2, 3],
|
||
|
method=method, dense_output=True)
|
||
|
assert_equal(sol.sol(4), [2, 3])
|
||
|
assert_equal(sol.sol([4, 5, 6]), [[2, 2, 2], [3, 3, 3]])
|
||
|
|
||
|
|
||
|
def test_no_integration_class():
|
||
|
for method in [RK23, RK45, Radau, BDF, LSODA]:
|
||
|
solver = method(lambda t, y: -y, 0.0, [10.0, 0.0], 0.0)
|
||
|
solver.step()
|
||
|
assert_equal(solver.status, 'finished')
|
||
|
sol = solver.dense_output()
|
||
|
assert_equal(sol(0.0), [10.0, 0.0])
|
||
|
assert_equal(sol([0, 1, 2]), [[10, 10, 10], [0, 0, 0]])
|
||
|
|
||
|
solver = method(lambda t, y: -y, 0.0, [], np.inf)
|
||
|
solver.step()
|
||
|
assert_equal(solver.status, 'finished')
|
||
|
sol = solver.dense_output()
|
||
|
assert_equal(sol(100.0), [])
|
||
|
assert_equal(sol([0, 1, 2]), np.empty((0, 3)))
|
||
|
|
||
|
|
||
|
def test_empty():
|
||
|
def fun(t, y):
|
||
|
return np.zeros((0,))
|
||
|
|
||
|
y0 = np.zeros((0,))
|
||
|
|
||
|
for method in ['RK23', 'RK45', 'Radau', 'BDF', 'LSODA']:
|
||
|
sol = assert_no_warnings(solve_ivp, fun, [0, 10], y0,
|
||
|
method=method, dense_output=True)
|
||
|
assert_equal(sol.sol(10), np.zeros((0,)))
|
||
|
assert_equal(sol.sol([1, 2, 3]), np.zeros((0, 3)))
|
||
|
|
||
|
for method in ['RK23', 'RK45', 'Radau', 'BDF', 'LSODA']:
|
||
|
sol = assert_no_warnings(solve_ivp, fun, [0, np.inf], y0,
|
||
|
method=method, dense_output=True)
|
||
|
assert_equal(sol.sol(10), np.zeros((0,)))
|
||
|
assert_equal(sol.sol([1, 2, 3]), np.zeros((0, 3)))
|
||
|
|
||
|
|
||
|
def test_ConstantDenseOutput():
|
||
|
sol = ConstantDenseOutput(0, 1, np.array([1, 2]))
|
||
|
assert_allclose(sol(1.5), [1, 2])
|
||
|
assert_allclose(sol([1, 1.5, 2]), [[1, 1, 1], [2, 2, 2]])
|
||
|
|
||
|
sol = ConstantDenseOutput(0, 1, np.array([]))
|
||
|
assert_allclose(sol(1.5), np.empty(0))
|
||
|
assert_allclose(sol([1, 1.5, 2]), np.empty((0, 3)))
|
||
|
|
||
|
|
||
|
def test_classes():
|
||
|
y0 = [1 / 3, 2 / 9]
|
||
|
for cls in [RK23, RK45, Radau, BDF, LSODA]:
|
||
|
solver = cls(fun_rational, 5, y0, np.inf)
|
||
|
assert_equal(solver.n, 2)
|
||
|
assert_equal(solver.status, 'running')
|
||
|
assert_equal(solver.t_bound, np.inf)
|
||
|
assert_equal(solver.direction, 1)
|
||
|
assert_equal(solver.t, 5)
|
||
|
assert_equal(solver.y, y0)
|
||
|
assert_(solver.step_size is None)
|
||
|
if cls is not LSODA:
|
||
|
assert_(solver.nfev > 0)
|
||
|
assert_(solver.njev >= 0)
|
||
|
assert_equal(solver.nlu, 0)
|
||
|
else:
|
||
|
assert_equal(solver.nfev, 0)
|
||
|
assert_equal(solver.njev, 0)
|
||
|
assert_equal(solver.nlu, 0)
|
||
|
|
||
|
assert_raises(RuntimeError, solver.dense_output)
|
||
|
|
||
|
message = solver.step()
|
||
|
assert_equal(solver.status, 'running')
|
||
|
assert_equal(message, None)
|
||
|
assert_equal(solver.n, 2)
|
||
|
assert_equal(solver.t_bound, np.inf)
|
||
|
assert_equal(solver.direction, 1)
|
||
|
assert_(solver.t > 5)
|
||
|
assert_(not np.all(np.equal(solver.y, y0)))
|
||
|
assert_(solver.step_size > 0)
|
||
|
assert_(solver.nfev > 0)
|
||
|
assert_(solver.njev >= 0)
|
||
|
assert_(solver.nlu >= 0)
|
||
|
sol = solver.dense_output()
|
||
|
assert_allclose(sol(5), y0, rtol=1e-15, atol=0)
|
||
|
|
||
|
|
||
|
def test_OdeSolution():
|
||
|
ts = np.array([0, 2, 5], dtype=float)
|
||
|
s1 = ConstantDenseOutput(ts[0], ts[1], np.array([-1]))
|
||
|
s2 = ConstantDenseOutput(ts[1], ts[2], np.array([1]))
|
||
|
|
||
|
sol = OdeSolution(ts, [s1, s2])
|
||
|
|
||
|
assert_equal(sol(-1), [-1])
|
||
|
assert_equal(sol(1), [-1])
|
||
|
assert_equal(sol(2), [-1])
|
||
|
assert_equal(sol(3), [1])
|
||
|
assert_equal(sol(5), [1])
|
||
|
assert_equal(sol(6), [1])
|
||
|
|
||
|
assert_equal(sol([0, 6, -2, 1.5, 4.5, 2.5, 5, 5.5, 2]),
|
||
|
np.array([[-1, 1, -1, -1, 1, 1, 1, 1, -1]]))
|
||
|
|
||
|
ts = np.array([10, 4, -3])
|
||
|
s1 = ConstantDenseOutput(ts[0], ts[1], np.array([-1]))
|
||
|
s2 = ConstantDenseOutput(ts[1], ts[2], np.array([1]))
|
||
|
|
||
|
sol = OdeSolution(ts, [s1, s2])
|
||
|
assert_equal(sol(11), [-1])
|
||
|
assert_equal(sol(10), [-1])
|
||
|
assert_equal(sol(5), [-1])
|
||
|
assert_equal(sol(4), [-1])
|
||
|
assert_equal(sol(0), [1])
|
||
|
assert_equal(sol(-3), [1])
|
||
|
assert_equal(sol(-4), [1])
|
||
|
|
||
|
assert_equal(sol([12, -5, 10, -3, 6, 1, 4]),
|
||
|
np.array([[-1, 1, -1, 1, -1, 1, -1]]))
|
||
|
|
||
|
ts = np.array([1, 1])
|
||
|
s = ConstantDenseOutput(1, 1, np.array([10]))
|
||
|
sol = OdeSolution(ts, [s])
|
||
|
assert_equal(sol(0), [10])
|
||
|
assert_equal(sol(1), [10])
|
||
|
assert_equal(sol(2), [10])
|
||
|
|
||
|
assert_equal(sol([2, 1, 0]), np.array([[10, 10, 10]]))
|
||
|
|
||
|
|
||
|
def test_num_jac():
|
||
|
def fun(t, y):
|
||
|
return np.vstack([
|
||
|
-0.04 * y[0] + 1e4 * y[1] * y[2],
|
||
|
0.04 * y[0] - 1e4 * y[1] * y[2] - 3e7 * y[1] ** 2,
|
||
|
3e7 * y[1] ** 2
|
||
|
])
|
||
|
|
||
|
def jac(t, y):
|
||
|
return np.array([
|
||
|
[-0.04, 1e4 * y[2], 1e4 * y[1]],
|
||
|
[0.04, -1e4 * y[2] - 6e7 * y[1], -1e4 * y[1]],
|
||
|
[0, 6e7 * y[1], 0]
|
||
|
])
|
||
|
|
||
|
t = 1
|
||
|
y = np.array([1, 0, 0])
|
||
|
J_true = jac(t, y)
|
||
|
threshold = 1e-5
|
||
|
f = fun(t, y).ravel()
|
||
|
|
||
|
J_num, factor = num_jac(fun, t, y, f, threshold, None)
|
||
|
assert_allclose(J_num, J_true, rtol=1e-5, atol=1e-5)
|
||
|
|
||
|
J_num, factor = num_jac(fun, t, y, f, threshold, factor)
|
||
|
assert_allclose(J_num, J_true, rtol=1e-5, atol=1e-5)
|
||
|
|
||
|
|
||
|
def test_num_jac_sparse():
|
||
|
def fun(t, y):
|
||
|
e = y[1:]**3 - y[:-1]**2
|
||
|
z = np.zeros(y.shape[1])
|
||
|
return np.vstack((z, 3 * e)) + np.vstack((2 * e, z))
|
||
|
|
||
|
def structure(n):
|
||
|
A = np.zeros((n, n), dtype=int)
|
||
|
A[0, 0] = 1
|
||
|
A[0, 1] = 1
|
||
|
for i in range(1, n - 1):
|
||
|
A[i, i - 1: i + 2] = 1
|
||
|
A[-1, -1] = 1
|
||
|
A[-1, -2] = 1
|
||
|
|
||
|
return A
|
||
|
|
||
|
np.random.seed(0)
|
||
|
n = 20
|
||
|
y = np.random.randn(n)
|
||
|
A = structure(n)
|
||
|
groups = group_columns(A)
|
||
|
|
||
|
f = fun(0, y[:, None]).ravel()
|
||
|
|
||
|
# Compare dense and sparse results, assuming that dense implementation
|
||
|
# is correct (as it is straightforward).
|
||
|
J_num_sparse, factor_sparse = num_jac(fun, 0, y.ravel(), f, 1e-8, None,
|
||
|
sparsity=(A, groups))
|
||
|
J_num_dense, factor_dense = num_jac(fun, 0, y.ravel(), f, 1e-8, None)
|
||
|
assert_allclose(J_num_dense, J_num_sparse.toarray(),
|
||
|
rtol=1e-12, atol=1e-14)
|
||
|
assert_allclose(factor_dense, factor_sparse, rtol=1e-12, atol=1e-14)
|
||
|
|
||
|
# Take small factors to trigger their recomputing inside.
|
||
|
factor = np.random.uniform(0, 1e-12, size=n)
|
||
|
J_num_sparse, factor_sparse = num_jac(fun, 0, y.ravel(), f, 1e-8, factor,
|
||
|
sparsity=(A, groups))
|
||
|
J_num_dense, factor_dense = num_jac(fun, 0, y.ravel(), f, 1e-8, factor)
|
||
|
|
||
|
assert_allclose(J_num_dense, J_num_sparse.toarray(),
|
||
|
rtol=1e-12, atol=1e-14)
|
||
|
assert_allclose(factor_dense, factor_sparse, rtol=1e-12, atol=1e-14)
|
||
|
|