figure.py

9.5 kB · python · 243 lines

1import os2import sys34sys.dont_write_bytecode = True5sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))67import verify as V89# STYLE1011FONT = "Helvetica,Arial,sans-serif"12# PALETTE13INK = "#000000"14MUTED = "#8e8e93"15PALE = "#bababf"16PAPER = "#ffffff"17CONCEPT = {18    "gasket": "#00cad8",19    "carpet": "#008cff",20    "sponge": "#6768fa",21    "prime": "#ff325a",22    "bound": "#ff8f2c",23    "window": "#ff3d40",24    "control": "#8e8e93",25}26PALETTE = {27    "black": "#000000",28    "white": "#ffffff",29    "red": "#ff3d40",30    "red-light": "#ff9d95",31    "red-dark": "#a80016",32    "orange": "#ff8f2c",33    "orange-light": "#ffc093",34    "orange-dark": "#a25400",35    "yellow": "#ffd100",36    "yellow-light": "#ffe591",37    "yellow-dark": "#9e8100",38    "green": "#32cc58",39    "green-light": "#5eee79",40    "green-dark": "#007f2c",41    "mint": "#00d1bb",42    "mint-light": "#48efd8",43    "mint-dark": "#008173",44    "teal": "#00cad8",45    "teal-light": "#48e9f7",46    "teal-dark": "#007c85",47    "cyan": "#1ec9f3",48    "cyan-light": "#86e2ff",49    "cyan-dark": "#007c98",50    "blue": "#008cff",51    "blue-light": "#84bdff",52    "blue-dark": "#00559f",53    "indigo": "#6768fa",54    "indigo-light": "#9ea9ff",55    "indigo-dark": "#3c2abc",56    "purple": "#d332e9",57    "purple-light": "#f08aff",58    "purple-dark": "#870097",59    "pink": "#ff325a",60    "pink-light": "#ff9a9f",61    "pink-dark": "#a50030",62    "brown": "#b18462",63    "brown-light": "#dfaf8c",64    "brown-dark": "#754c2b",65    "gray": "#8e8e93",66    "gray-light": "#bababf",67    "gray-dark": "#56565a",68}69# PALETTE END70STROKE = 1.271THIN = 0.672MARGIN = 367374def esc(s):75    return s.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")7677def header(width, height):78    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)]7980def text(x, y, s, size=11, fill=INK, anchor="start", weight=None):81    w = ' font-weight="%s"' % weight if weight else ""82    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))8384def line(x1, y1, x2, y2, stroke=INK, width=STROKE):85    return '<line x1="%.1f" y1="%.1f" x2="%.1f" y2="%.1f" stroke="%s" stroke-width="%.1f"/>' % (x1, y1, x2, y2, stroke, width)8687def rect(x, y, w, h, fill, opacity=1.0):88    return '<rect x="%.1f" y="%.1f" width="%.1f" height="%.1f" fill="%s" opacity="%.2f"/>' % (x, y, w, h, fill, opacity)8990def circle(x, y, r, fill):91    return '<circle cx="%.1f" cy="%.1f" r="%.1f" fill="%s"/>' % (x, y, r, fill)9293def save(parts, path):94    with open(path, "w") as handle:95        handle.write("\n".join(parts + ["</svg>"]) + "\n")9697# STYLE END9899# DRAW100101CELL = 20102PLATE = 17103SAT = CONCEPT["bound"]104SHORT = CONCEPT["window"]105EXACT = CONCEPT["control"]106REFUTED = CONCEPT["carpet"]107108def cells(word):109    rows, wide = V.grid(word)110    return [(i, j) for i, row in enumerate(rows) for j in range(wide) if (row >> j) & 1], wide111112def board(parts, x, y, word):113    filled, wide = cells(word)114    have = set(filled)115    for i in range(wide):116        for j in range(wide):117            px, py = x + j * CELL, y + i * CELL118            if (i, j) in have:119                parts.append(rect(px, py, CELL, CELL, INK))120            else:121                parts.append('<rect x="%.1f" y="%.1f" width="%d" height="%d" fill="none" stroke="%s" stroke-width="%.1f"/>' % (px, py, CELL, CELL, PALE, THIN))122    parts.append('<rect x="%.1f" y="%.1f" width="%d" height="%d" fill="none" stroke="%s" stroke-width="%.1f"/>' % (x, y, wide * CELL, wide * CELL, MUTED, STROKE))123    return wide * CELL124125def witness(parts, x, y, left, right):126    top = y + CELL127    width = board(parts, x, top, [left])128    parts.append(text(x + width + 11, top + CELL + 5, "\u00d7", size=15, anchor="middle"))129    step = x + width + 22130    board(parts, step, top, [right])131    parts.append(text(step + width + 11, top + CELL + 5, "=", size=15, anchor="middle"))132    end = step + width + 22133    span = board(parts, end, y, [left, right])134    pieces = V.drawn([left, right])135    parts.append(text(end + span + 12, y + 2 * CELL + 5, "%d %s" % (pieces, "piece" if pieces == 1 else "pieces"), size=12))136    return pieces137138def verdicts():139    sat, phi = {}, {}140    for a, b in V.PAIRS:141        wa, wb = V.rate_weight(a, b)142        sat[(a, b)] = (wa, wb) == (V.fill_weight(a), V.fill_weight(b))143        phi[(a, b)] = (wa, wb) == (V.phi_weight(a), V.phi_weight(b))144    return sat, phi145146def ledger(parts, x, y):147    sat, phi = verdicts()148    codes = V.CODES149    for i, a in enumerate(codes):150        parts.append(text(x - 6, y + i * PLATE + PLATE - 5, str(a), size=8, fill=MUTED, anchor="end"))151        parts.append(text(x + i * PLATE + PLATE / 2, y - 6, str(a), size=8, fill=MUTED, anchor="middle"))152    for i, a in enumerate(codes):153        for j, b in enumerate(codes):154            px, py = x + j * PLATE, y + i * PLATE155            if a == b:156                parts.append(rect(px, py, PLATE - 1, PLATE - 1, PALE, 0.55))157                continue158            key = (min(a, b), max(a, b))159            if j > i:160                parts.append(rect(px, py, PLATE - 1, PLATE - 1, EXACT if phi[key] else REFUTED))161            else:162                parts.append(rect(px, py, PLATE - 1, PLATE - 1, SAT if sat[key] else SHORT))163    return sum(1 for v in sat.values() if v), sum(1 for v in phi.values() if v)164165def key(parts, x, y, swatch, label):166    parts.append(rect(x, y, 11, 11, swatch))167    parts.append(text(x + 17, y + 9, label, size=10, fill=MUTED))168169def main():170    width, height = 620, 600171    parts = header(width, height)172    parts.append(text(MARGIN, 26, "One multiset, two orders", size=12, weight="bold"))173    first = witness(parts, MARGIN, 40, 3, 6)174    second = witness(parts, MARGIN, 40 + 5 * CELL, 6, 3)175    parts.append(text(MARGIN, 40 + 9 * CELL + 22, "Four black cells either way; %d pieces above, %d below." % (first, second), size=10, fill=MUTED))176    base = 40 + 10 * CELL + 42177    parts.append(text(MARGIN, base - 22, "All 105 two-letter alphabets", size=12, weight="bold"))178    saturating, exact = ledger(parts, MARGIN + 16, base)179    legend = base + 15 * PLATE + 26180    key(parts, MARGIN, legend, SAT, "lower left: exponent meets the fill ceiling (%d)" % saturating)181    key(parts, MARGIN, legend + 18, SHORT, "lower left: falls short (%d)" % (105 - saturating))182    key(parts, MARGIN + 300, legend, EXACT, "upper right: the constant-word rule holds (%d)" % exact)183    key(parts, MARGIN + 300, legend + 18, REFUTED, "upper right: the constant-word rule is refuted (%d)" % (105 - exact))184    out = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "figures")185    os.makedirs(out, exist_ok=True)186    save(parts, os.path.join(out, "ledger.svg"))187    plates(os.path.join(out, "plates.tex"))188    print("drew figures/ledger.svg and figures/plates.tex")189190# TIKZ191192def tikz_board(lines, x, y, word, unit):193    filled, wide = cells(word)194    have = set(filled)195    for i in range(wide):196        for j in range(wide):197            px, py = x + j * unit, y + (wide - 1 - i) * unit198            shade = "black!84" if (i, j) in have else "white"199            lines.append("\\fill[%s] (%.2f,%.2f) rectangle ++(%.2f,%.2f);" % (shade, px, py, unit, unit))200    lines.append("\\draw[black!22,line width=0.2pt,step=%.2f] (%.2f,%.2f) grid ++(%.2f,%.2f);" % (unit, x, y, wide * unit, wide * unit))201    lines.append("\\draw[black!55,line width=0.4pt] (%.2f,%.2f) rectangle ++(%.2f,%.2f);" % (x, y, wide * unit, wide * unit))202    return wide * unit203204def tikz_witness(lines, x, y, left, right, unit):205    top = y + unit206    width = tikz_board(lines, x, top, [left], unit)207    lines.append("\\node at (%.2f,%.2f) {$\\otimes$};" % (x + width + 0.45, top + unit))208    step = x + width + 0.9209    tikz_board(lines, step, top, [right], unit)210    lines.append("\\node at (%.2f,%.2f) {$=$};" % (step + width + 0.45, top + unit))211    end = step + width + 0.9212    span = tikz_board(lines, end, y, [left, right], unit)213    pieces = V.drawn([left, right])214    lines.append("\\node[anchor=west,font=\\small] at (%.2f,%.2f) {%d %s};" % (end + span + 0.22, y + 2 * unit, pieces, "piece" if pieces == 1 else "pieces"))215    return end + span + 1.55216217def plates(path):218    unit = 0.30219    lines = ["\\newcommand{\\witnessplate}{%", "\\begin{tikzpicture}[x=1cm,y=1cm]"]220    span = tikz_witness(lines, 0.0, 0.0, 3, 6, unit)221    tikz_witness(lines, span + 0.55, 0.0, 6, 3, unit)222    lines += ["\\end{tikzpicture}}", "", "\\newcommand{\\ledgerplate}{%", "\\begin{tikzpicture}[x=1cm,y=1cm]"]223    sat, phi = verdicts()224    step = 0.34225    for i, a in enumerate(V.CODES):226        top = (len(V.CODES) - 1 - i) * step227        lines.append("\\node[anchor=east,font=\\tiny,text=black!55] at (%.2f,%.2f) {%d};" % (-0.06, top + step / 2, a))228        lines.append("\\node[anchor=south,font=\\tiny,text=black!55] at (%.2f,%.2f) {%d};" % (i * step + step / 2, len(V.CODES) * step + 0.02, a))229        for j, b in enumerate(V.CODES):230            px, py = j * step, top231            if a == b:232                shade = "black!12"233            elif j > i:234                shade = "black!35" if phi[(min(a, b), max(a, b))] else "blue!58!black"235            else:236                shade = "orange!85!black" if sat[(min(a, b), max(a, b))] else "red!72!black"237            lines.append("\\fill[%s] (%.3f,%.3f) rectangle ++(%.3f,%.3f);" % (shade, px, py, step - 0.03, step - 0.03))238    lines += ["\\end{tikzpicture}}", ""]239    with open(path, "w") as handle:240        handle.write("\n".join(lines) + "\n")241242if __name__ == "__main__":243    main()