The explicit scheme
The explicit scheme and stability (lesson 6.5.1)
0 of 8 correct
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
How to type answers
- Powers with
^:x^7,2^(1/3),a^-2. - Multiply with
*or by writing side by side:3x^2,2(x+1). ln(x)is the natural log,log(x, 2)is log base 2,eis Euler's number,sqrt(x)is a square root.sin(x),cos(x)andtan(x)take radians; writepifor π andsin(x)^2for sin²x.- Your first answer to each question is saved: it counts towards your score and brings the question back for review.
- Exact answers like
ln(20)/ln(3)are best. Decimals need about 4 significant figures.
Implicit schemes and Crank–Nicolson
Implicit schemes and Crank–Nicolson (lesson 6.5.2)
0 of 8 correct
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
- 8.
How to type answers
- Powers with
^:x^7,2^(1/3),a^-2. - Multiply with
*or by writing side by side:3x^2,2(x+1). ln(x)is the natural log,log(x, 2)is log base 2,eis Euler's number,sqrt(x)is a square root.sin(x),cos(x)andtan(x)take radians; writepifor π andsin(x)^2for sin²x.- Your first answer to each question is saved: it counts towards your score and brings the question back for review.
- Exact answers like
ln(20)/ln(3)are best. Decimals need about 4 significant figures.
Coding exercise
Three schemes in one function
Write heat_scheme(u0, r, steps, scheme="cn") for on a grid, as in lessons 6.5.1 and 6.5.2. u0 holds the grid values at time 0, ends included; r is . Return the grid values after steps steps, as a NumPy array.
schemeis"explicit","implicit"or"cn"(Crank–Nicolson). Hold the two end values fixed at every step, and include them in the boundary terms of the implicit schemes.- Solve the implicit systems with
scipy.linalg.solve_banded. Never changeu0itself;steps = 0returns a copy of it. - Raise
ValueErrorifu0has fewer than 3 values or ifris not positive. Raise it ifstepsis not a whole number of at least 0, orschemeis not one of the three. Raise it too for the explicit scheme with .
The tests use the spike and the sine of the lessons and a rod whose ends are held at 0 and 1. They add a straight line that must not move, and a tent with .
See the tests
import math
import numpy as np
def expect_value_error(call, why):
try:
call()
except ValueError:
return
raise AssertionError(f"expected ValueError: {why}")
def test_implicit_spike():
"""One implicit step with r = 1 spreads the spike (0, 0, 1, 0, 0) to 1/7, 3/7, 1/7 inside"""
u = np.asarray(heat_scheme([0, 0, 1, 0, 0], 1, 1, "implicit"))
expect_close(u, np.array([0, 1, 3, 1, 0]) / 7, what="heat_scheme(spike, 1, 1, 'implicit')", rel=1e-9, abs_tol=1e-12)
def test_cn_spike():
"""One Crank–Nicolson step with r = 1 gives 2/7, 1/7, 2/7 inside: the middle dips"""
u = np.asarray(heat_scheme([0, 0, 1, 0, 0], 1, 1, "cn"))
expect_close(u, np.array([0, 2, 1, 2, 0]) / 7, what="heat_scheme(spike, 1, 1, 'cn')", rel=1e-9, abs_tol=1e-12)
def test_explicit_sine():
"""One explicit step with r = 0.25 on sin(πx) with 4 cells multiplies it by 1 − sin²(π/8)"""
x = np.linspace(0, 1, 5)
u0 = np.sin(math.pi * x)
u = np.asarray(heat_scheme(u0, 0.25, 1, "explicit"))
expect_close(u, (1 - math.sin(math.pi / 8) ** 2) * u0, what="one explicit step", rel=1e-9, abs_tol=1e-12)
def test_accuracy():
"""On 40 cells to t = 0.1 with 40 steps (r = 4), Crank–Nicolson is within 2e-4 of e^(−π²t) sin(πx) and the implicit scheme within 5e-3"""
x = np.linspace(0, 1, 41)
exact = math.exp(-math.pi**2 * 0.1) * np.sin(math.pi * x)
cn = np.max(np.abs(np.asarray(heat_scheme(np.sin(math.pi * x), 4, 40, "cn")) - exact))
im = np.max(np.abs(np.asarray(heat_scheme(np.sin(math.pi * x), 4, 40, "implicit")) - exact))
assert cn < 2e-4, f"Crank–Nicolson error should be under 2e-4, got {cn:.2e}"
assert 1e-3 < im < 5e-3, f"implicit error should be between 1e-3 and 5e-3, got {im:.2e}"
def test_ends_held():
"""The ends keep their values: from 0 inside with ends 0 and 1, the profile settles on the line u = x"""
u0 = np.zeros(11)
u0[-1] = 1
for scheme, r, steps in [("explicit", 0.4, 250), ("implicit", 5, 20), ("cn", 2, 50)]:
u = np.asarray(heat_scheme(u0, r, steps, scheme))
expect_close(u[[0, -1]], np.array([0.0, 1.0]), what=f"{scheme}: the end values", abs_tol=1e-12)
expect_close(u, np.linspace(0, 1, 11), what=f"{scheme}: the profile at t = 1", abs_tol=1e-3)
def test_line_is_steady():
"""A straight line between the end values does not change, for every scheme"""
line = np.linspace(2, 6, 9)
for scheme in ["explicit", "implicit", "cn"]:
expect_close(np.asarray(heat_scheme(line, 0.5, 10, scheme)), line, what=f"{scheme} on a line", abs_tol=1e-12)
def test_large_r_stays_bounded():
"""With r = 50 the implicit scheme keeps a tent between 0 and 0.5"""
x = np.linspace(0, 1, 21)
u = np.asarray(heat_scheme(np.minimum(x, 1 - x), 50, 5, "implicit"))
assert u.min() >= 0 and u.max() <= 0.5, f"values should stay between 0 and 0.5, got {u.min():.3g} to {u.max():.3g}"
def test_input_untouched():
"""Zero steps return the start unchanged, and u0 itself is never modified"""
u0 = np.array([0.0, 0.5, 1.0, 0.5, 0.0])
keep = u0.copy()
expect_close(np.asarray(heat_scheme(u0, 1, 0, "cn")), keep, what="heat_scheme(u0, 1, 0)", abs_tol=0)
heat_scheme(u0, 1, 3, "implicit")
expect_close(u0, keep, what="u0 after the call", abs_tol=0)
def test_bad_inputs():
"""An unstable explicit r, a non-positive r, a fractional step count, too few points or an unknown scheme raise ValueError"""
u0 = [0, 0.5, 1, 0.5, 0]
expect_value_error(lambda: heat_scheme(u0, 0.6, 5, "explicit"), "the explicit scheme is unstable for r = 0.6")
expect_value_error(lambda: heat_scheme(u0, 0, 5, "cn"), "r = 0 is not a time step")
expect_value_error(lambda: heat_scheme(u0, -1, 5, "implicit"), "r must be positive")
expect_value_error(lambda: heat_scheme(u0, 1, 2.5, "cn"), "2.5 steps is not a whole number")
expect_value_error(lambda: heat_scheme([0, 1], 1, 1, "cn"), "two values leave no inside point")
expect_value_error(lambda: heat_scheme(u0, 1, 1, "euler"), "'euler' is not one of the three schemes")