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("&", "&").replace("<", "<").replace(">", ">")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()