codexmemoization-cache-trace-and-lifetime.py
memoization-cache-trace-and-lifetime.py
"""Draw exact memoized Fibonacci lookups and cache lifetime effects.
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 FancyBboxPatch
def evaluate(n, memo):
lookups, stores = [], []
def fib(k):
hit = k in memo
lookups.append((k, hit))
if hit:
return memo[k]
value = k if k < 2 else fib(k - 1) + fib(k - 2)
memo[k] = value
stores.append(k)
return value
value = fib(n)
return value, lookups, stores
def main():
memo = {}
cold = evaluate(5, memo)
assert cold == (5, [(5, False), (4, False), (3, False), (2, False), (1, False),
(0, False), (1, True), (2, True), (3, True)], [1, 0, 2, 3, 4, 5])
records = [("F(5), empty cache", cold, "0–5")]
records.append(("F(5), retain cache", evaluate(5, memo), "0–5"))
records.append(("F(6), retain cache", evaluate(6, memo), "0–6"))
memo.clear()
records.append(("Clear; F(5) again", evaluate(5, memo), "0–5"))
fig, ax = plt.subplots(figsize=(11, 5.7), dpi=100)
fig.patch.set_facecolor("white")
ax.set(xlim=(-0.7, 8.7), ylim=(-4.55, 1.6))
ax.axis("off")
ax.text(4, 1.15, "Lookup order for F(5), starting empty", ha="center", fontsize=17)
for i, (n, hit) in enumerate(cold[1]):
fill = "#b9e5df" if hit else "#ffe0a7"
ax.add_patch(FancyBboxPatch((i - 0.38, -0.1), 0.76, 0.8,
boxstyle="round,pad=0.04", facecolor=fill,
edgecolor="#37434e", lw=1))
ax.text(i, 0.44, f"F({n})", ha="center", fontsize=12)
ax.text(i, 0.07, "HIT" if hit else "MISS", ha="center", fontsize=10)
ax.text(4, -0.55, "9 wrapper calls = 6 body evaluations + 3 reused answers",
ha="center", fontsize=12)
ax.text(4, -0.95, "Store on return: 1 → 0 → 2 → 3 → 4 → 5 (arguments, not values)",
ha="center", fontsize=11, color="#37434e")
columns = ["Next request / action", "Value", "Calls", "Bodies", "Hits", "Stored keys"]
data = []
for name, (value, lookups, stores), keys in records:
hits = sum(hit for _, hit in lookups)
data.append([name, value, len(lookups), len(stores), hits, keys])
table = ax.table(cellText=data, colLabels=columns, cellLoc="center",
colWidths=[0.32, 0.10, 0.11, 0.11, 0.10, 0.16],
bbox=[0.02, 0.10, 0.96, 0.39])
table.auto_set_font_size(False)
table.set_fontsize(11)
for (row, col), cell in table.get_celld().items():
cell.set_edgecolor("#aab5bf")
cell.set_facecolor("#e5ecf0" if row == 0 else "white")
if row == 0:
cell.set_text_props(weight="bold")
ax.text(4, -4.45, "Sequential calls • cache every completed value • no eviction • left child first",
ha="center", fontsize=11, color="#37434e")
fig.subplots_adjust(left=0.025, right=0.975, top=0.96, bottom=0.03)
fig.savefig(Path(__file__).with_suffix(".png").name, facecolor="white", transparent=False)
plt.close(fig)
if __name__ == "__main__":
main()