codex/karatsuba-recursion-digit-cost.py

karatsuba-recursion-digit-cost.py
"""Illustrate an ideal equal-half recurrence, not an execution-time benchmark.

Write a matching PNG in the CWD. Tested with Python 3.14,
matplotlib 3.10.7 and numpy 2.3.5.
"""

from pathlib import Path

import matplotlib.pyplot as plt
import numpy as np


def main():
    fig, (tree, cost) = plt.subplots(1, 2, figsize=(11.5, 5.9), dpi=110,
                                    gridspec_kw={"width_ratios": [1.04, 1]})
    fig.patch.set_facecolor("white")
    ink, blue, orange = "#24364b", "#276ea3", "#bd5d18"
    fig.suptitle("Three recursive products change the growth of multiplication cost",
                 fontsize=16, weight="bold", color=ink, y=0.98)
    tree.set(xlim=(-0.55, 8.55), ylim=(-1.75, 4.05))
    tree.axis("off")
    tree.set_title("Ideal equal halves: n = 8", fontsize=13, pad=12)
    root = [4.0]
    children = [1.0, 4.0, 7.0]
    grandchildren = [0.25, 1, 1.75, 3.25, 4, 4.75, 6.25, 7, 7.75]
    for x in children:
        tree.plot([4, x], [3.5, 2.4], color="#9cabb9", lw=1.3, zorder=1)
    for i, x in enumerate(children):
        for y in grandchildren[3 * i:3 * i + 3]:
            tree.plot([x, y], [2.4, 1.3], color="#9cabb9", lw=1.3, zorder=1)
    for xs, y, label in [(root, 3.5, "8"), (children, 2.4, "4"),
                         (grandchildren, 1.3, "2")]:
        tree.scatter(xs, np.full(len(xs), y), s=450, color="#e2eff9",
                     edgecolors=blue, linewidth=1.2, zorder=2)
        for x in xs:
            tree.text(x, y, label, ha="center", va="center", fontsize=11, zorder=3)
    for i, x in enumerate(grandchildren):
        for t in (-0.22, 0, 0.22):
            tree.plot([x, x + t], [1.3, 0.45], color="#9cabb9", lw=0.8, zorder=1)
            tree.scatter([x + t], [0.45], s=30, color=orange, zorder=2)
    tree.text(4, -0.05, "27 leaves of size 1 (one unit each)",
              ha="center", fontsize=11, color=ink)
    tree.text(4, -0.62, "Level j      Calls      Size       Internal work",
              ha="center", fontsize=11, weight="bold", color=ink)
    tree.text(4, -1.04, r"$j\qquad 3^j\qquad n/2^j\qquad \alpha n(3/2)^j$",
              ha="center", fontsize=13, color=ink)
    tree.text(4, -1.54, "Internal levels: j = 0, ..., k − 1; n = 2ᵏ",
              ha="center", fontsize=11, color=ink)

    k = np.arange(15)
    n = 2 ** k
    # One leaf unit; alpha=1 times operand length for each internal call.
    modeled = 3 * 3 ** k - 2 * 2 ** k
    school_products = n ** 2
    cost.loglog(n, school_products, "s-", base=2, ms=4, lw=2, color=blue,
                label=r"Ordinary digit products: $n^2$")
    cost.loglog(n, modeled, "o-", base=2, ms=4, lw=2, color=orange,
                label=r"Model: $3\cdot3^k-2\cdot2^k$")
    cost.set_title("Stipulated counts, not machine timings", fontsize=13, pad=12)
    cost.set_xlabel("Operand length n (fixed-base digit positions)", fontsize=11)
    cost.set_ylabel("Count in the stated model (log scale)", fontsize=11)
    cost.set_xticks(2 ** np.arange(0, 15, 2))
    cost.set_xticklabels([str(2 ** i) for i in range(0, 15, 2)], fontsize=9)
    cost.grid(True, alpha=0.23, which="major")
    cost.legend(loc="upper left", fontsize=10, frameon=False)
    cost.text(0.04, 0.08, "One unit per leaf; overhead αs per internal call\n"
              "of size s, with α = 1. Sum growth is idealized.\n"
              "Crossover depends on these chosen constants.",
              transform=cost.transAxes, fontsize=10, linespacing=1.4,
              color=ink, bbox={"facecolor": "white", "edgecolor": "none", "pad": 4})
    fig.subplots_adjust(left=0.025, right=0.98, bottom=0.16, top=0.86, wspace=0.28)
    fig.savefig(Path.cwd() / (Path(__file__).stem + ".png"), facecolor="white",
                transparent=False)
    plt.close(fig)


if __name__ == "__main__":
    main()