import os import sys sys.dont_write_bytecode = True sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) import verify as V # STYLE FONT = "Helvetica,Arial,sans-serif" # PALETTE INK = "#000000" MUTED = "#8e8e93" PALE = "#bababf" PAPER = "#ffffff" CONCEPT = { "gasket": "#00cad8", "carpet": "#008cff", "sponge": "#6768fa", "prime": "#ff325a", "bound": "#ff8f2c", "window": "#ff3d40", "control": "#8e8e93", } PALETTE = { "black": "#000000", "white": "#ffffff", "red": "#ff3d40", "red-light": "#ff9d95", "red-dark": "#a80016", "orange": "#ff8f2c", "orange-light": "#ffc093", "orange-dark": "#a25400", "yellow": "#ffd100", "yellow-light": "#ffe591", "yellow-dark": "#9e8100", "green": "#32cc58", "green-light": "#5eee79", "green-dark": "#007f2c", "mint": "#00d1bb", "mint-light": "#48efd8", "mint-dark": "#008173", "teal": "#00cad8", "teal-light": "#48e9f7", "teal-dark": "#007c85", "cyan": "#1ec9f3", "cyan-light": "#86e2ff", "cyan-dark": "#007c98", "blue": "#008cff", "blue-light": "#84bdff", "blue-dark": "#00559f", "indigo": "#6768fa", "indigo-light": "#9ea9ff", "indigo-dark": "#3c2abc", "purple": "#d332e9", "purple-light": "#f08aff", "purple-dark": "#870097", "pink": "#ff325a", "pink-light": "#ff9a9f", "pink-dark": "#a50030", "brown": "#b18462", "brown-light": "#dfaf8c", "brown-dark": "#754c2b", "gray": "#8e8e93", "gray-light": "#bababf", "gray-dark": "#56565a", } # PALETTE END STROKE = 1.2 THIN = 0.6 MARGIN = 36 def esc(s): return s.replace("&", "&").replace("<", "<").replace(">", ">") def header(width, height): return ['' % (width, height, width, height), '' % (width, height, PAPER)] def text(x, y, s, size=11, fill=INK, anchor="start", weight=None): w = ' font-weight="%s"' % weight if weight else "" return '%s' % (x, y, FONT, size, fill, anchor, w, esc(s)) def line(x1, y1, x2, y2, stroke=INK, width=STROKE): return '' % (x1, y1, x2, y2, stroke, width) def rect(x, y, w, h, fill, opacity=1.0): return '' % (x, y, w, h, fill, opacity) def circle(x, y, r, fill): return '' % (x, y, r, fill) def save(parts, path): with open(path, "w") as handle: handle.write("\n".join(parts + [""]) + "\n") # STYLE END # DRAW CELL = 20 PLATE = 17 SAT = CONCEPT["bound"] SHORT = CONCEPT["window"] EXACT = CONCEPT["control"] REFUTED = CONCEPT["carpet"] def cells(word): rows, wide = V.grid(word) return [(i, j) for i, row in enumerate(rows) for j in range(wide) if (row >> j) & 1], wide def board(parts, x, y, word): filled, wide = cells(word) have = set(filled) for i in range(wide): for j in range(wide): px, py = x + j * CELL, y + i * CELL if (i, j) in have: parts.append(rect(px, py, CELL, CELL, INK)) else: parts.append('' % (px, py, CELL, CELL, PALE, THIN)) parts.append('' % (x, y, wide * CELL, wide * CELL, MUTED, STROKE)) return wide * CELL def witness(parts, x, y, left, right): top = y + CELL width = board(parts, x, top, [left]) parts.append(text(x + width + 11, top + CELL + 5, "\u00d7", size=15, anchor="middle")) step = x + width + 22 board(parts, step, top, [right]) parts.append(text(step + width + 11, top + CELL + 5, "=", size=15, anchor="middle")) end = step + width + 22 span = board(parts, end, y, [left, right]) pieces = V.drawn([left, right]) parts.append(text(end + span + 12, y + 2 * CELL + 5, "%d %s" % (pieces, "piece" if pieces == 1 else "pieces"), size=12)) return pieces def verdicts(): sat, phi = {}, {} for a, b in V.PAIRS: wa, wb = V.rate_weight(a, b) sat[(a, b)] = (wa, wb) == (V.fill_weight(a), V.fill_weight(b)) phi[(a, b)] = (wa, wb) == (V.phi_weight(a), V.phi_weight(b)) return sat, phi def ledger(parts, x, y): sat, phi = verdicts() codes = V.CODES for i, a in enumerate(codes): parts.append(text(x - 6, y + i * PLATE + PLATE - 5, str(a), size=8, fill=MUTED, anchor="end")) parts.append(text(x + i * PLATE + PLATE / 2, y - 6, str(a), size=8, fill=MUTED, anchor="middle")) for i, a in enumerate(codes): for j, b in enumerate(codes): px, py = x + j * PLATE, y + i * PLATE if a == b: parts.append(rect(px, py, PLATE - 1, PLATE - 1, PALE, 0.55)) continue key = (min(a, b), max(a, b)) if j > i: parts.append(rect(px, py, PLATE - 1, PLATE - 1, EXACT if phi[key] else REFUTED)) else: parts.append(rect(px, py, PLATE - 1, PLATE - 1, SAT if sat[key] else SHORT)) return sum(1 for v in sat.values() if v), sum(1 for v in phi.values() if v) def key(parts, x, y, swatch, label): parts.append(rect(x, y, 11, 11, swatch)) parts.append(text(x + 17, y + 9, label, size=10, fill=MUTED)) def main(): width, height = 620, 600 parts = header(width, height) parts.append(text(MARGIN, 26, "One multiset, two orders", size=12, weight="bold")) first = witness(parts, MARGIN, 40, 3, 6) second = witness(parts, MARGIN, 40 + 5 * CELL, 6, 3) parts.append(text(MARGIN, 40 + 9 * CELL + 22, "Four black cells either way; %d pieces above, %d below." % (first, second), size=10, fill=MUTED)) base = 40 + 10 * CELL + 42 parts.append(text(MARGIN, base - 22, "All 105 two-letter alphabets", size=12, weight="bold")) saturating, exact = ledger(parts, MARGIN + 16, base) legend = base + 15 * PLATE + 26 key(parts, MARGIN, legend, SAT, "lower left: exponent meets the fill ceiling (%d)" % saturating) key(parts, MARGIN, legend + 18, SHORT, "lower left: falls short (%d)" % (105 - saturating)) key(parts, MARGIN + 300, legend, EXACT, "upper right: the constant-word rule holds (%d)" % exact) key(parts, MARGIN + 300, legend + 18, REFUTED, "upper right: the constant-word rule is refuted (%d)" % (105 - exact)) out = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "figures") os.makedirs(out, exist_ok=True) save(parts, os.path.join(out, "ledger.svg")) plates(os.path.join(out, "plates.tex")) print("drew figures/ledger.svg and figures/plates.tex") # TIKZ def tikz_board(lines, x, y, word, unit): filled, wide = cells(word) have = set(filled) for i in range(wide): for j in range(wide): px, py = x + j * unit, y + (wide - 1 - i) * unit shade = "black!84" if (i, j) in have else "white" lines.append("\\fill[%s] (%.2f,%.2f) rectangle ++(%.2f,%.2f);" % (shade, px, py, unit, unit)) lines.append("\\draw[black!22,line width=0.2pt,step=%.2f] (%.2f,%.2f) grid ++(%.2f,%.2f);" % (unit, x, y, wide * unit, wide * unit)) lines.append("\\draw[black!55,line width=0.4pt] (%.2f,%.2f) rectangle ++(%.2f,%.2f);" % (x, y, wide * unit, wide * unit)) return wide * unit def tikz_witness(lines, x, y, left, right, unit): top = y + unit width = tikz_board(lines, x, top, [left], unit) lines.append("\\node at (%.2f,%.2f) {$\\otimes$};" % (x + width + 0.45, top + unit)) step = x + width + 0.9 tikz_board(lines, step, top, [right], unit) lines.append("\\node at (%.2f,%.2f) {$=$};" % (step + width + 0.45, top + unit)) end = step + width + 0.9 span = tikz_board(lines, end, y, [left, right], unit) pieces = V.drawn([left, right]) 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")) return end + span + 1.55 def plates(path): unit = 0.30 lines = ["\\newcommand{\\witnessplate}{%", "\\begin{tikzpicture}[x=1cm,y=1cm]"] span = tikz_witness(lines, 0.0, 0.0, 3, 6, unit) tikz_witness(lines, span + 0.55, 0.0, 6, 3, unit) lines += ["\\end{tikzpicture}}", "", "\\newcommand{\\ledgerplate}{%", "\\begin{tikzpicture}[x=1cm,y=1cm]"] sat, phi = verdicts() step = 0.34 for i, a in enumerate(V.CODES): top = (len(V.CODES) - 1 - i) * step lines.append("\\node[anchor=east,font=\\tiny,text=black!55] at (%.2f,%.2f) {%d};" % (-0.06, top + step / 2, a)) 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)) for j, b in enumerate(V.CODES): px, py = j * step, top if a == b: shade = "black!12" elif j > i: shade = "black!35" if phi[(min(a, b), max(a, b))] else "blue!58!black" else: shade = "orange!85!black" if sat[(min(a, b), max(a, b))] else "red!72!black" lines.append("\\fill[%s] (%.3f,%.3f) rectangle ++(%.3f,%.3f);" % (shade, px, py, step - 0.03, step - 0.03)) lines += ["\\end{tikzpicture}}", ""] with open(path, "w") as handle: handle.write("\n".join(lines) + "\n") if __name__ == "__main__": main()