codexmatrix-chain-dynamic-programming-certificate.py
matrix-chain-dynamic-programming-certificate.py
"""Draw an original four-matrix DP table and its split certificate.
Tested with Python 3.14, matplotlib 3.10.7; emit PNG in the CWD.
"""
from pathlib import Path
import matplotlib.pyplot as plt
from matplotlib.patches import Rectangle
def chain_table(p):
n = len(p) - 1
costs, splits = {}, {}
for i in range(1, n + 1):
costs[i, i] = 0
for length in range(2, n + 1):
for i in range(1, n - length + 2):
j = i + length - 1
candidates = [(costs[i, k] + costs[k + 1, j] + p[i - 1] * p[k] * p[j], k)
for k in range(i, j)]
costs[i, j], splits[i, j] = min(candidates)
return costs, splits
def main():
costs, splits = chain_table([12, 5, 40, 3, 15])
fig = plt.figure(figsize=(11.8, 7.2), dpi=100, facecolor="white")
fig.suptitle("Four matrices: costs and an optimal grouping", fontsize=20, y=0.96)
fig.text(0.5, 0.88, "A: 12 × 5 B: 5 × 40 C: 40 × 3 D: 3 × 15", ha="center", fontsize=14)
orange, ink = "#b25916", "#263e50"
table_ax = fig.add_axes([0.05, 0.30, 0.40, 0.46])
table_ax.set_xlim(-0.6, 4)
table_ax.set_ylim(-0.1, 4.6)
table_ax.axis("off")
table_ax.text(1.7, 4.5, "Minimum costs m[i, j]", ha="center", fontsize=16, color=ink)
active = {(1, 4), (1, 3), (2, 3)}
for i in range(1, 5):
table_ax.text(-0.34, 4 - i + 0.45, str(i), ha="center", va="center", fontsize=14)
table_ax.text(i - 0.5, 4.13, str(i), ha="center", va="center", fontsize=14)
for j in range(1, 5):
x, y = j - 1, 4 - i
used = (i, j) in active
face = "#fff0df" if used else ("#edf3f7" if i <= j else "#f6f6f6")
table_ax.add_patch(Rectangle((x, y), 1, 0.9, facecolor=face,
edgecolor=orange if used else "#aab7bf", linewidth=2 if used else 1))
table_ax.text(x + 0.5, y + 0.45, str(costs[i, j]) if i <= j else "—",
ha="center", va="center", fontsize=16, color=orange if used else ink)
table_ax.text(-0.34, 4.13, "i / j", ha="center", fontsize=11)
fig.text(0.24, 0.265, "Fill by increasing interval length.\nOrange: nonsingleton intervals in the optimum.",
ha="center", fontsize=12, color=ink)
tree_ax = fig.add_axes([0.50, 0.30, 0.46, 0.46])
tree_ax.set_xlim(0, 1)
tree_ax.set_ylim(-0.06, 1.13)
tree_ax.axis("off")
tree_ax.text(0.5, 1.12, "Follow both children at each split", ha="center", fontsize=16, color=ink)
positions = {(1, 4): (0.63, 0.94), (1, 3): (0.38, 0.62), (2, 3): (0.49, 0.30),
(1, 1): (0.07, 0.01), (2, 2): (0.34, 0.01), (3, 3): (0.64, 0.01), (4, 4): (0.91, 0.01)}
for interval in active:
i, j = interval
k = splits[interval]
for child in [(i, k), (k + 1, j)]:
start, end = positions[interval], positions[child]
tree_ax.plot([start[0], end[0]], [start[1], end[1]], color=orange, linewidth=1.8, zorder=1)
for (i, j), (x, y) in positions.items():
if i == j:
label = f"{'ABCD'[i - 1]}\nstop: i = j"
else:
label = f"[{i}, {j}]: cost {costs[i, j]}\nsplit k = {splits[i, j]}"
tree_ax.text(x, y, label, ha="center", va="center", fontsize=11.5,
bbox=dict(boxstyle="round,pad=0.45", facecolor="white" if i == j else "#fff0df",
edgecolor=ink if i == j else orange, linewidth=1.4), zorder=3)
fig.text(0.05, 0.17, "Final candidates:", fontsize=13, color=ink)
for x, k, cost in [(0.28, 1, 1725), (0.53, 2, 11400), (0.78, 3, 1320)]:
fig.text(x, 0.17, f"k = {k}: {cost}", fontsize=14, color=orange if k == 3 else ink,
bbox=dict(facecolor="#fff0df" if k == 3 else "white", edgecolor=orange if k == 3 else "none", pad=5))
fig.text(0.5, 0.09, "Recovered grouping: (A(BC))D 600 + 180 + 540 = 1320", ha="center", fontsize=16, color=orange)
fig.text(0.5, 0.03, "Original dimension example; counts scalar multiplications, not elapsed time.", ha="center", fontsize=11, color=ink)
fig.savefig(Path.cwd() / (Path(__file__).stem + ".png"), facecolor="white", transparent=False)
plt.close(fig)
if __name__ == "__main__":
main()