codexmemoization-fibonacci-recursion-and-dependencies.py
memoization-fibonacci-recursion-and-dependencies.py
"""Draw repeated Fibonacci calls and their distinct dependencies.
Run from _media with Python 3.14, matplotlib 3.10.7 and numpy 2.3.5.
"""
from pathlib import Path
import matplotlib.pyplot as plt
from matplotlib.patches import FancyArrowPatch
def main():
colors = {0: "#e5ecf0", 1: "#e5ecf0", 2: "#ffe0a7", 3: "#b9e5df", 4: "#c9d8f7", 5: "#d8c8ec"}
fig, axes = plt.subplots(1, 2, figsize=(11, 5.5), dpi=100,
gridspec_kw={"width_ratios": [1.65, 1]})
fig.patch.set_facecolor("white")
nodes, edges = [], []
def visit(n, depth):
index = len(nodes)
nodes.append([n, None, depth])
if n < 2:
nodes[index][1] = sum(node[0] < 2 for node in nodes) - 1
else:
left, right = visit(n - 1, depth + 1), visit(n - 2, depth + 1)
nodes[index][1] = (nodes[left][1] + nodes[right][1]) / 2
edges.extend([(index, left), (index, right)])
return index
visit(5, 0)
assert len(nodes) == 15 and len(edges) == 14
ax = axes[0]
for parent, child in edges:
x1, y1 = nodes[parent][1:]
x2, y2 = nodes[child][1:]
ax.plot([x1, x2], [-y1, -y2], color="#78838c", lw=1.3, zorder=1)
for n, x, depth in nodes:
ax.scatter(x, -depth, s=770, color=colors[n], edgecolor="#37434e", zorder=2)
ax.text(x, -depth, rf"$F_{n}$", ha="center", va="center", fontsize=12, zorder=3)
ax.set(xlim=(-0.6, 7.6), ylim=(-4.65, 0.55))
ax.set_title("Repeated recursion: 15 calls", fontsize=15, pad=16)
ax.text(3.5, -4.6, "Each occurrence is a separate call.\nRepeated labels repeat the same work.",
ha="center", va="top", fontsize=11)
ax = axes[1]
positions = {5: (0, 0), 4: (0, -1), 3: (0, -2), 2: (0, -3),
1: (0, -4), 0: (1.4, -4)}
dag_edges = [(n, n - d) for n in range(2, 6) for d in (1, 2)]
assert len(dag_edges) == 8
for n, child in dag_edges:
bend = -0.43 if n - child == 2 and n > 2 else 0
ax.add_patch(FancyArrowPatch(positions[n], positions[child],
connectionstyle=f"arc3,rad={bend}", arrowstyle="-|>",
mutation_scale=13, shrinkA=17, shrinkB=17,
color="#78838c", lw=1.4, zorder=1))
for n, (x, y) in positions.items():
ax.scatter(x, y, s=770, color=colors[n], edgecolor="#37434e", zorder=2)
ax.text(x, y, rf"$F_{n}$", ha="center", va="center", fontsize=12, zorder=3)
ax.set(xlim=(-1, 2.1), ylim=(-4.65, 0.55))
ax.set_title("Shared dependencies: 6 values", fontsize=15, pad=16)
ax.text(0.55, -4.6, "Arrow: caller → required value.\nOne stored answer per label.",
ha="center", va="top", fontsize=11)
for ax in axes:
ax.set_facecolor("white")
ax.axis("off")
fig.suptitle(r"$F_5=5$: one recurrence, two views of the work", fontsize=17, y=0.97)
fig.subplots_adjust(left=0.035, right=0.965, top=0.81, bottom=0.17, wspace=0.22)
fig.savefig(Path(__file__).with_suffix(".png").name, facecolor="white", transparent=False)
plt.close(fig)
if __name__ == "__main__":
main()