codex/past-exam-of-the-year-1-of-the-computer-science-course-of-the-university-of-oxford/2015/discrete-main-2015-euler-stability.py

past-exam-of-the-year-1-of-the-computer-science-course-of-the-university-of-oxford/2015/discrete-main-2015-euler-stability.py
"""Plot Question 5(b)'s exact and Euler decay curves; write PNG to CWD.

Tested with Python 3.14.4, matplotlib 3.10.7+dfsg1, numpy 2.3.5.
Dependencies are documented in the repository's top-level pyproject.toml.
Run: python /absolute/path/to/discrete-main-2015-euler-stability.py
"""
from pathlib import Path
import os

os.environ.setdefault('MPLCONFIGDIR', str(Path.cwd() / '.discrete-main-2015-matplotlib'))
import matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
import numpy as np


def main():
    plt.rcParams.update({'font.family': 'DejaVu Sans', 'font.size': 10})
    decay_rate = 8.0
    fig, axes = plt.subplots(1, 2, figsize=(10, 3.8), layout='constrained')
    fig.set_facecolor('white')
    t = np.linspace(0, 1, 401)
    exact = 2 * np.exp(-decay_rate * t)
    panels = [
        ('Unstable and neutral', [(3, '#c23b22'), (4, '#b07700')]),
        ('Stable: damped oscillation or monotone decay', [(5, '#8053a1'), (16, '#007d79')]),
    ]
    for ax, (title, cases) in zip(axes, panels):
        ax.set_facecolor('white')
        ax.plot(t, exact, color='#202020', linewidth=2, label=r'Exact $2e^{-8t}$')
        for steps, colour in cases:
            grid = np.arange(steps + 1) / steps
            factor = 1 - decay_rate / steps
            values = 2 * factor ** np.arange(steps + 1)
            ax.plot(grid, values, 'o-', markersize=4, color=colour,
                    label=f'N = {steps}, factor = {factor:.2f}')
        ax.axhline(0, color='#777777', linewidth=0.6)
        ax.set(xlim=(0, 1), xlabel='x', ylabel='y', title=title)
        ax.grid(alpha=0.2)
        ax.legend(fontsize=9, loc='best')
    axes[0].set_ylim(-10, 6)
    axes[1].set_ylim(-1.4, 2.3)
    fig.suptitle('Forward Euler for exponential decay: stability requires N > 4', fontsize=13)
    fig.savefig(Path.cwd() / 'discrete-main-2015-euler-stability.png', dpi=120,
                facecolor='white', transparent=False, metadata={'Software': 'Matplotlib'})
    plt.close(fig)


if __name__ == '__main__':
    main()