figure.py

5.8 kB · python · 169 lines

1import math2import os34# STYLE56FONT = "Helvetica,Arial,sans-serif"7# PALETTE8INK = "#000000"9MUTED = "#8e8e93"10PALE = "#bababf"11PAPER = "#ffffff"12CONCEPT = {13    "gasket": "#00cad8",14    "carpet": "#008cff",15    "sponge": "#6768fa",16    "prime": "#ff325a",17    "bound": "#ff8f2c",18    "window": "#ff3d40",19    "control": "#8e8e93",20}21PALETTE = {22    "black": "#000000",23    "white": "#ffffff",24    "red": "#ff3d40",25    "red-light": "#ff9d95",26    "red-dark": "#a80016",27    "orange": "#ff8f2c",28    "orange-light": "#ffc093",29    "orange-dark": "#a25400",30    "yellow": "#ffd100",31    "yellow-light": "#ffe591",32    "yellow-dark": "#9e8100",33    "green": "#32cc58",34    "green-light": "#5eee79",35    "green-dark": "#007f2c",36    "mint": "#00d1bb",37    "mint-light": "#48efd8",38    "mint-dark": "#008173",39    "teal": "#00cad8",40    "teal-light": "#48e9f7",41    "teal-dark": "#007c85",42    "cyan": "#1ec9f3",43    "cyan-light": "#86e2ff",44    "cyan-dark": "#007c98",45    "blue": "#008cff",46    "blue-light": "#84bdff",47    "blue-dark": "#00559f",48    "indigo": "#6768fa",49    "indigo-light": "#9ea9ff",50    "indigo-dark": "#3c2abc",51    "purple": "#d332e9",52    "purple-light": "#f08aff",53    "purple-dark": "#870097",54    "pink": "#ff325a",55    "pink-light": "#ff9a9f",56    "pink-dark": "#a50030",57    "brown": "#b18462",58    "brown-light": "#dfaf8c",59    "brown-dark": "#754c2b",60    "gray": "#8e8e93",61    "gray-light": "#bababf",62    "gray-dark": "#56565a",63}64# PALETTE END65STROKE = 1.266THIN = 0.667MARGIN = 366869def esc(s):70    return s.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")7172def header(width, height):73    return ['<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 %d %d" width="%d" height="%d">' % (width, height, width, height), '<rect width="%d" height="%d" fill="%s"/>' % (width, height, PAPER)]7475def text(x, y, s, size=11, fill=INK, anchor="start", weight=None):76    w = ' font-weight="%s"' % weight if weight else ""77    return '<text x="%.1f" y="%.1f" font-family="%s" font-size="%d" fill="%s" text-anchor="%s"%s>%s</text>' % (x, y, FONT, size, fill, anchor, w, esc(s))7879def line(x1, y1, x2, y2, stroke=INK, width=STROKE):80    return '<line x1="%.1f" y1="%.1f" x2="%.1f" y2="%.1f" stroke="%s" stroke-width="%.1f"/>' % (x1, y1, x2, y2, stroke, width)8182def rect(x, y, w, h, fill, opacity=1.0):83    return '<rect x="%.1f" y="%.1f" width="%.1f" height="%.1f" fill="%s" opacity="%.2f"/>' % (x, y, w, h, fill, opacity)8485def circle(x, y, r, fill):86    return '<circle cx="%.1f" cy="%.1f" r="%.1f" fill="%s"/>' % (x, y, r, fill)8788def save(parts, path):89    with open(path, "w") as handle:90        handle.write("\n".join(parts + ["</svg>"]) + "\n")9192# STYLE END9394# DRAW9596Q = 2197DIGITS = (0, 10)98SAMPLES = 180099TIKZ = 900100101def hat(q, a0, t):102    d = math.sin(math.pi * q * t) / math.sin(math.pi * t)103    e = d * d - 2.0 * d * math.cos(math.pi * (2 * a0 - q + 1) * t) + 1.0104    return math.sqrt(e) / (q - 1) if e > 0.0 else 0.0105106def cap(q, t):107    d = abs(math.sin(math.pi * q * t) / math.sin(math.pi * t))108    return min(1.0, (d + 1.0) / (q - 1))109110def samples(n):111    return [0.5 * (j + 0.5) / n for j in range(n)]112113def curves(n):114    ts = samples(n)115    rows = [("majorant", [cap(Q, t) for t in ts])]116    for a0 in DIGITS:117        rows.append((f"digit {a0}", [hat(Q, a0, t) for t in ts]))118    return ts, rows119120def svg(path):121    width, height = 640, 280122    left, right, top, foot = MARGIN + 4, width - MARGIN - 96, MARGIN - 12, height - MARGIN123    parts = header(width, height)124    ts, rows = curves(SAMPLES)125    place = lambda t, v: (left + (right - left) * (t / 0.5), foot - (foot - top) * v)126    parts.append(line(left, foot, right, foot, PALE, THIN))127    parts.append(line(left, top, left, foot, PALE, THIN))128    parts.append(line(left, top, right, top, PALE, THIN))129    keys = (CONCEPT["bound"], CONCEPT["gasket"], CONCEPT["carpet"])130    for (_, values), color in zip(rows, keys):131        pts = " ".join("%.2f,%.2f" % place(t, v) for t, v in zip(ts, values))132        parts.append('<polyline points="%s" fill="none" stroke="%s" stroke-width="%.1f"/>' % (pts, color, STROKE))133    labels = ("majorant u_q", "|F| missing 0", "|F| missing 10")134    for i, (text_line, color) in enumerate(zip(labels, keys)):135        y = top + 18 + 18 * i136        parts.append(line(right + 12, y - 4, right + 30, y - 4, color, STROKE))137        parts.append(text(right + 36, y, text_line, size=10, fill=color))138    parts.append(text(left, foot + 16, "0", size=9, fill=MUTED))139    parts.append(text(right, foot + 16, "1/2", size=9, fill=MUTED, anchor="end"))140    parts.append(text(left - 6, top + 4, "1", size=9, fill=MUTED, anchor="end"))141    parts.append(text(left, top - 8, "base 21, one digit removed", size=11, weight="bold"))142    save(parts, path)143144def tikz(path):145    ts, rows = curves(TIKZ)146    names = ("bound", "gasket", "carpet")147    body = ["\\begin{tikzpicture}[x=23cm,y=4.4cm]"]148    for key in names:149        body.append("\\definecolor{tone%s}{HTML}{%s}" % (key, CONCEPT[key][1:].upper()))150    body.append("\\draw[gray!45,line width=0.3pt] (0,0) rectangle (0.5,1);")151    for (_, values), key in zip(rows, names):152        pts = " ".join("(%.5f,%.5f)" % (t, v) for t, v in zip(ts, values))153        body.append("\\draw[tone%s,line width=0.4pt] plot coordinates {%s};" % (key, pts))154    body.append("\\node[anchor=north] at (0,0) {\\footnotesize $0$};")155    body.append("\\node[anchor=north] at (0.5,0) {\\footnotesize $\\tfrac12$};")156    body.append("\\node[anchor=east] at (0,1) {\\footnotesize $1$};")157    body.append("\\end{tikzpicture}")158    with open(path, "w") as handle:159        handle.write("\n".join(body) + "\n")160161def main():162    out = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "figures")163    os.makedirs(out, exist_ok=True)164    svg(os.path.join(out, "figure.svg"))165    tikz(os.path.join(out, "lemma.tex"))166    print("drew figures/figure.svg and figures/lemma.tex")167168if __name__ == "__main__":169    main()