codex/past-exam-of-the-mathematics-course-of-the-university-of-cambridge/2010/iii/paper-30-erlang-fixed-points.py

past-exam-of-the-mathematics-course-of-the-university-of-cambridge/2010/iii/paper-30-erlang-fixed-points.py
"""Plot the alternative-routing residual; write the PNG to the caller's CWD.

Tested with Python 3.14, NumPy 2.3.5 and Matplotlib 3.10.7.
"""

from pathlib import Path

import matplotlib

matplotlib.use("Agg")
import matplotlib.pyplot as plt
import numpy as np


def erlang_b(load, capacity):
    """Stable recursion, vectorized over offered loads."""
    load = np.asarray(load, dtype=float)
    blocking = np.ones_like(load)
    for circuits in range(1, capacity + 1):
        blocking = load * blocking / (circuits + load * blocking)
    return blocking


def residual(blocking):
    return erlang_b(2000 * (1 + 2 * blocking * (1 - blocking)), 2100) - blocking


def root(left, right):
    left_sign = float(residual(left))
    for _ in range(55):
        middle = (left + right) / 2
        if float(residual(middle)) * left_sign > 0:
            left = middle
        else:
            right = middle
    return (left + right) / 2


def main():
    witnesses = np.array([0.0, 0.01, 0.125, 1.0])
    signs = residual(witnesses)
    if not (signs[0] > 0 and signs[1] < 0 and signs[2] > 0 and signs[3] < 0):
        raise ValueError("The chosen finite-capacity example lost its sign changes")
    roots = [root(a, b) for a, b in zip(witnesses[:-1], witnesses[1:])]
    fig, axes = plt.subplots(1, 2, figsize=(11, 4.3), dpi=100)
    fig.set_facecolor("white")
    for ax, end in zip(axes, [0.34, 0.014]):
        b = np.linspace(0, end, 1000)
        ax.plot(b, residual(b), color="#176a9e", linewidth=2,
                label=r"$E(2000[1+2B(1-B)],2100)-B$")
        ax.axhline(0, color="black", linewidth=0.8)
        for value in roots:
            if value < end:
                ax.scatter([value], [0], color="#b84431", zorder=3)
                if end < 0.02 or value > 0.01:
                    ax.annotate(f"B = {value:.5f}", (value, 0),
                                xytext=(0, 13), textcoords="offset points",
                                ha="center", fontsize=9)
        ax.set_xlim(0, end)
        ax.set_xlabel("Blocking probability B")
        ax.set_ylabel("Fixed-point residual")
        ax.grid(alpha=0.25)
    axes[0].set_title("Three distinct equilibrium approximations")
    axes[1].set_title("Low-blocking root, enlarged")
    axes[0].legend(fontsize=9, loc="lower left")
    fig.suptitle("Alternative routing: offered load 2000, capacity 2100")
    fig.tight_layout()
    output = Path.cwd() / "paper-30-erlang-fixed-points.png"
    fig.savefig(output, facecolor="white", transparent=False)
    plt.close(fig)
    print(f"{output.name}: roots {', '.join(f'{r:.8f}' for r in roots)}")


if __name__ == "__main__":
    main()