figure.py

8.5 kB · python · 253 lines

1import os2import sys34sys.dont_write_bytecode = True5sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))6import verify78# STYLE910FONT = "Helvetica,Arial,sans-serif"11# PALETTE12INK = "#000000"13MUTED = "#8e8e93"14PALE = "#bababf"15PAPER = "#ffffff"16CONCEPT = {17    "gasket": "#00cad8",18    "carpet": "#008cff",19    "sponge": "#6768fa",20    "prime": "#ff325a",21    "bound": "#ff8f2c",22    "window": "#ff3d40",23    "control": "#8e8e93",24}25PALETTE = {26    "black": "#000000",27    "white": "#ffffff",28    "red": "#ff3d40",29    "red-light": "#ff9d95",30    "red-dark": "#a80016",31    "orange": "#ff8f2c",32    "orange-light": "#ffc093",33    "orange-dark": "#a25400",34    "yellow": "#ffd100",35    "yellow-light": "#ffe591",36    "yellow-dark": "#9e8100",37    "green": "#32cc58",38    "green-light": "#5eee79",39    "green-dark": "#007f2c",40    "mint": "#00d1bb",41    "mint-light": "#48efd8",42    "mint-dark": "#008173",43    "teal": "#00cad8",44    "teal-light": "#48e9f7",45    "teal-dark": "#007c85",46    "cyan": "#1ec9f3",47    "cyan-light": "#86e2ff",48    "cyan-dark": "#007c98",49    "blue": "#008cff",50    "blue-light": "#84bdff",51    "blue-dark": "#00559f",52    "indigo": "#6768fa",53    "indigo-light": "#9ea9ff",54    "indigo-dark": "#3c2abc",55    "purple": "#d332e9",56    "purple-light": "#f08aff",57    "purple-dark": "#870097",58    "pink": "#ff325a",59    "pink-light": "#ff9a9f",60    "pink-dark": "#a50030",61    "brown": "#b18462",62    "brown-light": "#dfaf8c",63    "brown-dark": "#754c2b",64    "gray": "#8e8e93",65    "gray-light": "#bababf",66    "gray-dark": "#56565a",67}68# PALETTE END69STROKE = 1.270THIN = 0.671MARGIN = 367273def esc(s):74    return s.replace("&", "&amp;").replace("<", "&lt;").replace(">", "&gt;")7576def header(width, height):77    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)]7879def text(x, y, s, size=11, fill=INK, anchor="start", weight=None):80    w = ' font-weight="%s"' % weight if weight else ""81    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))8283def line(x1, y1, x2, y2, stroke=INK, width=STROKE):84    return '<line x1="%.1f" y1="%.1f" x2="%.1f" y2="%.1f" stroke="%s" stroke-width="%.1f"/>' % (x1, y1, x2, y2, stroke, width)8586def rect(x, y, w, h, fill, opacity=1.0):87    return '<rect x="%.1f" y="%.1f" width="%.1f" height="%.1f" fill="%s" opacity="%.2f"/>' % (x, y, w, h, fill, opacity)8889def circle(x, y, r, fill):90    return '<circle cx="%.1f" cy="%.1f" r="%.1f" fill="%s"/>' % (x, y, r, fill)9192def save(parts, path):93    with open(path, "w") as handle:94        handle.write("\n".join(parts + ["</svg>"]) + "\n")9596# STYLE END9798# SVG99100ACCENT = CONCEPT["bound"]101SHADES = [PALETTE["blue-dark"], PALETTE["blue"], PALETTE["blue-light"]]102103def path(points, stroke, width=1.8):104    d = " ".join(("M" if i == 0 else "L") + " %.1f %.1f" % p for i, p in enumerate(points))105    return '<path d="%s" fill="none" stroke="%s" stroke-width="%.1f" stroke-linejoin="round"/>' % (d, stroke, width)106107# DEPTH DATA108109def depth_data(top=40):110    out = {}111    for q in (3, 5, 7, 9, 11):112        row = []113        for D in range(2, top + 1, 2):114            K, _x, _y = verify.certificate_depth(D, q, 4 * D + 8)115            assert K is not None, "no certificate at q=%d D=%d" % (q, D)116            row.append((D, K))117        out[q] = row118    return out119120# DEPTH FIGURE121122def step_points(row, X, Y):123    pts = []124    prev = None125    for D, K in row:126        if prev is not None and K != prev:127            pts.append((X(D), Y(prev)))128        pts.append((X(D), Y(K)))129        prev = K130    return pts131132def draw_depth(out):133    data = depth_data()134    width = 760135    height = 340136    base = 276137    topm = 66138    aleft = 58139    aright = 402140    bleft = 486141    bright = 736142    dmin, dmax = 2, 40143    kmax = 90144145    def XA(D):146        return aleft + (aright - aleft) * (D - dmin) / (dmax - dmin)147148    def YA(K):149        return base - (base - topm) * K / kmax150151    def XB(D):152        return bleft + (bright - bleft) * (D - dmin) / (dmax - dmin)153154    def YB(K):155        return base - (base - topm) * K / 3.0156157    rows = header(width, height)158    rows.append(text(aleft, 26, "How deep the contraction certificate has to look", 13, INK, weight="bold"))159    rows.append(text(aleft, 42, "least depth K with q b(K+1) < f b(K) at every carry, even D", 11, MUTED))160    for K in (0, 30, 60, 90):161        rows.append(line(aleft, YA(K), aright, YA(K), PALE, 0.8))162        rows.append(text(aleft - 8, YA(K) + 4, str(K), 11, MUTED, "end"))163    rows.append(line(aleft, base, aright, base, INK, 1.2))164    for D in range(8, dmax + 1, 8):165        rows.append(line(XA(D), base, XA(D), base + 4, INK, 1.0))166        rows.append(text(XA(D), base + 18, str(D), 11, MUTED, "middle"))167    rows.append(text(aleft, base + 36, "dimension D", 11, MUTED))168    rows.append(rect(aleft, YA(2), aright - aleft, base - YA(2), PALE))169    rows.append(path(step_points(data[3], XA, YA), ACCENT, 2.2))170    for D, K in data[3]:171        rows.append(circle(XA(D), YA(K), 2.6, ACCENT))172    dlast, klast = data[3][-1]173    rows.append(text(XA(dlast) - 6, YA(klast) + 4, "base 3", 12, ACCENT, "end", weight="bold"))174    rows.append(text(XA(dlast) - 6, YA(klast) + 19, "K = %d at D = %d" % (klast, dlast), 11, MUTED, "end"))175    rows.append(text(aright - 4, YA(2) - 8, "bases 5, 7, 9, 11", 11, MUTED, "end"))176    rows.append(text(bleft, 42, "the same strip, magnified", 11, MUTED))177    for K in (0, 1, 2, 3):178        rows.append(line(bleft, YB(K), bright, YB(K), PALE, 0.8))179        rows.append(text(bleft - 8, YB(K) + 4, str(K), 11, MUTED, "end"))180    rows.append(line(bleft, base, bright, base, INK, 1.2))181    for D in range(8, dmax + 1, 16):182        rows.append(line(XB(D), base, XB(D), base + 4, INK, 1.0))183        rows.append(text(XB(D), base + 18, str(D), 11, MUTED, "middle"))184    rows.append(text(bleft, base + 36, "dimension D", 11, MUTED))185    assert data[9] == data[11], "bases 9 and 11 no longer coincide"186    for i, q in enumerate((5, 7, 9)):187        rows.append(path(step_points(data[q], XB, YB), SHADES[i], 1.8))188    rows.append(text(XB(16) + 4, YB(2) - 8, "q = 5", 11, SHADES[0]))189    rows.append(text(XB(26) + 4, YB(2) + 17, "q = 7", 11, SHADES[1]))190    rows.append(text(XB(30) + 4, YB(1) - 8, "q = 9, 11", 11, SHADES[2]))191    save(rows, out)192    return data193194# TENT FIGURE195196def draw_tent(out, top=301):197    T = verify.trough_set(top)198    pts = []199    for D in range(3, top + 1, 2):200        rowsm, n = verify.m_even_rows_mod2(D)201        nul = n - verify.bit_rank(rowsm)202        assert nul == verify.tent(D, T), "tent law fails at D=%d" % D203        pts.append((D, nul))204    left = 58205    right = 26206    topm = 68207    base = 272208    width = 760209    height = 340210    ymax = max(v for _D, v in pts)211    span = width - left - right212213    def X(D):214        return left + span * (D - 3) / (top - 3)215216    def Y(v):217        return base - (base - topm) * v / ymax218219    rows = header(width, height)220    rows.append(text(left, 26, "The Jacobsthal tent", 13, INK, weight="bold"))221    rows.append(text(left, 42, "dimension of the kernel of the even core mod 2, base 3, odd D", 11, MUTED))222    for v in (0, 10, 20, 30, 40):223        rows.append(line(left, Y(v), width - right, Y(v), PALE, 0.8))224        rows.append(text(left - 8, Y(v) + 4, str(v), 11, MUTED, "end"))225    rows.append(line(left, Y(1), width - right, Y(1), PALE, 1.4))226    rows.append(line(left, base, width - right, base, INK, 1.2))227    for t in T:228        if 3 <= t <= top:229            rows.append(line(X(t), base, X(t), base + 8, ACCENT, 1.2))230    for D in (3, 100, 200, 300):231        rows.append(text(X(D), base + 26, str(D), 11, MUTED, "middle"))232    rows.append(text(left, base + 46, "dimension D", 11, MUTED))233    rows.append(text(left + 92, base + 46, "ticks below the axis: the floors 2J(k)+1 and 2J(k)+3, where the kernel drops to 1", 11, ACCENT))234    rows.append(path([(X(D), Y(v)) for D, v in pts], ACCENT, 1.8))235    peak = max(pts, key=lambda p: p[1])236    rows.append(circle(X(peak[0]), Y(peak[1]), 3.0, ACCENT))237    rows.append(text(X(peak[0]) - 10, Y(peak[1]) + 4, "D = %d, kernel %d" % peak, 11, ACCENT, "end"))238    save(rows, out)239    return pts240241# MAIN242243def main():244    here = os.path.dirname(os.path.abspath(__file__))245    figs = os.path.join(here, "..", "figures")246    os.makedirs(figs, exist_ok=True)247    draw_depth(os.path.join(figs, "depth.svg"))248    print("drew figures/depth.svg")249    draw_tent(os.path.join(figs, "tent.svg"))250    print("drew figures/tent.svg")251252if __name__ == "__main__":253    main()