매개변수 탐색 ↔ 사잇값 정리 문서의 그림 생성 코드다. if __name__ == "__main__": 아래가 자체 테스트다.
# 매개변수 탐색 ↔ 사잇값 정리 문서의 그림을 만든다: 39_parametric-search-ivt_fig1.svg
# 실행: ~/.venvs/vault-plots/bin/python 39_parametric-search-ivt_plot.py (matplotlib, numpy 필요)
import math
import os
import matplotlib
matplotlib.use("svg")
import matplotlib.pyplot as plt
import numpy as np
from matplotlib import font_manager
HERE = os.path.dirname(os.path.abspath(__file__))
PREFIX = "39_parametric-search-ivt"
INK = "#888888" # 밝은 테마와 어두운 테마 모두에서 보이는 회색
C = ["#3b82c4", "#d9622b", "#3a9a5b", "#9b59b6", "#c49a1b"]
_fonts = {f.name for f in font_manager.fontManager.ttflist}
FONT = next((n for n in ["Apple SD Gothic Neo", "NanumGothic", "Noto Sans CJK KR"] if n in _fonts), "DejaVu Sans")
plt.rcParams.update({
"font.family": FONT, "mathtext.fontset": "dejavusans",
"font.size": 11, "axes.unicode_minus": False, "svg.fonttype": "path", "svg.hashsalt": PREFIX,
"axes.edgecolor": INK, "axes.labelcolor": INK, "xtick.color": INK, "ytick.color": INK,
"text.color": INK, "legend.frameon": False,
"axes.facecolor": "none", "figure.facecolor": "none", "savefig.transparent": True,
"axes.spines.top": False, "axes.spines.right": False,
})
def save(fig, k):
fig.savefig(os.path.join(HERE, f"{PREFIX}_fig{k}.svg"), bbox_inches="tight", metadata={"Date": None})
plt.close(fig)
ROOTS = (0.5, 1.5, 3.3)
def f(x):
return (x - ROOTS[0]) * (x - ROOTS[1]) * (x - ROOTS[2])
def bisect_trace(a, b, steps):
"""f(a) < 0 <= f(b)를 지키며 중점에서 반씩 줄인 구간들."""
out = [(a, b)]
for _ in range(steps):
p = (a + b) / 2
if f(p) >= 0:
b = p
else:
a = p
out.append((a, b))
return out
STEPS = 6
trace = bisect_trace(0.0, 4.0, STEPS)
fig, (top, bot) = plt.subplots(2, 1, figsize=(6, 4.0), sharex=True, gridspec_kw={"height_ratios": [1.5, 1]})
x = np.linspace(-0.1, 4.1, 400)
top.plot(x, f(x), color=C[0], lw=1.8)
top.axhline(0, color=INK, lw=0.7)
for r in ROOTS:
top.plot([r], [0], "o", color=C[0], ms=5, mfc="none")
top.text(0.5, 0.9, "가장 작은 근 0.5", fontsize=9, ha="center")
top.text(3.75, -2.5, "이분법이 찾는 근 3.3", fontsize=9, ha="center")
for k in range(5): # 정수 칸의 판정 ok(x) = (f(x) >= 0)
ok = f(k) >= 0
top.plot([k], [f(k)], "s", color=C[2] if ok else C[1], ms=6)
top.text(4.05, f(4), "참", color=C[2], fontsize=9, va="center")
top.text(0.08, f(0), "거짓", color=C[1], fontsize=9, va="center")
top.set_ylim(-3.2, 7)
top.set_ylabel("$f(x)$")
for k, (a, b) in enumerate(trace):
bot.plot([a, b], [k, k], color=C[3], lw=3, solid_capstyle="butt")
bot.text(-0.15, k, f"{k}", fontsize=8, ha="right", va="center")
bot.axvline(ROOTS[2], color=C[0], lw=0.8, ls=":")
bot.set_ylim(STEPS + 0.6, -0.6)
bot.set_yticks([])
bot.spines["left"].set_visible(False)
bot.set_xlabel("$x$")
bot.set_title("반씩 줄인 구간 (위에서부터 0, 1, 2, … 번째)", fontsize=9, loc="left")
fig.tight_layout()
save(fig, 1)
if __name__ == "__main__":
# 문서 표: 정수 0~4의 판정 줄 = 거짓, 참, 거짓, 거짓, 참. f(0) < 0 < f(4)
assert [f(k) >= 0 for k in range(5)] == [False, True, False, False, True]
# 이분법 구간: [0,4] → [2,4] → [3,4] → [3,3.5] → [3.25,3.5] → …, 3.3으로 모인다
assert trace[:5] == [(0, 4), (2, 4), (3, 4), (3, 3.5), (3.25, 3.5)]
a, b = bisect_trace(0.0, 4.0, 60)[-1]
assert abs(a - 3.3) < 1e-12 and abs(b - 3.3) < 1e-12
print("ALL CHECKS PASSED")