figure.py

5.4 kB · python · 172 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# DRAW9596MASS = CONCEPT["control"]97COST = CONCEPT["bound"]98CROSSING = CONCEPT["window"]99100GAMMA = 0.5772156649015328606065120900824024310421101WALL = 3690102LO, HI = 2.0, 9.0103FLOOR, CEIL = 0.86, 1.14104105def harmonic(n):106    return math.log(n) + GAMMA + 1.0 / (2 * n)107108def proved(q):109    n = -((-(q - 2)) // 2)110    phi = (4 / math.pi) * q + (2 * q / math.pi) * harmonic(n) + (1 - 2 / math.pi) * (q - 2) + 0.727111    return 1.0 + phi / q112113def mass(q):114    return math.log(q - 1) / math.log(q)115116def cost(q):117    return 0.75 + math.log(proved(q)) / math.log(q)118119def samples(f, steps=112):120    out = []121    for i in range(steps + 1):122        x = LO + (HI - LO) * i / steps123        out.append((x, f(int(round(10 ** x)))))124    return out125126def main():127    width, height = 640, 300128    left, right = MARGIN + 28, width - MARGIN129    top, bottom = MARGIN - 6, height - MARGIN - 8130    parts = header(width, height)131132    def at(x, y):133        return (left + (right - left) * (x - LO) / (HI - LO), bottom - (bottom - top) * (y - FLOOR) / (CEIL - FLOOR))134135    curve_mass = samples(mass)136    curve_cost = samples(cost)137    wall = math.log10(WALL)138    band = [at(*p) for p in curve_cost if p[0] >= wall]139    band = [at(wall, mass(WALL))] + band + [at(HI, mass(int(10 ** HI)))]140    band += [at(*p) for p in reversed([p for p in curve_mass if p[0] >= wall])]141    parts.append('<polygon points="%s" fill="%s" opacity="0.14"/>' % (" ".join("%.1f,%.1f" % p for p in band), COST))142143    parts.append(line(left, bottom, right, bottom, PALE, THIN))144    parts.append(line(left, top, left, bottom, PALE, THIN))145    for e in range(2, 10):146        x, y = at(e, FLOOR)147        parts.append(line(x, y, x, y + 4, PALE, THIN))148        parts.append(text(x, y + 16, "1e%d" % e, size=9, fill=MUTED, anchor="middle"))149    for v in (0.90, 1.00, 1.10):150        x, y = at(LO, v)151        parts.append(line(x - 4, y, x, y, PALE, THIN))152        parts.append(text(x - 8, y + 3, "%.2f" % v, size=9, fill=MUTED, anchor="end"))153154    xw, _ = at(wall, FLOOR)155    parts.append(line(xw, top, xw, bottom, CROSSING, THIN))156    parts.append(text(xw + 5, top + 10, "q = 3690", size=9, fill=CROSSING))157158    for series, colour in ((curve_mass, MASS), (curve_cost, COST)):159        pts = " ".join("%.1f,%.1f" % at(*p) for p in series)160        parts.append('<polyline points="%s" fill="none" stroke="%s" stroke-width="%.1f"/>' % (pts, colour, STROKE))161162    parts.append(circle(*at(wall, mass(WALL)), 3.0, INK))163    parts.append(text(at(6.3, 1.030)[0], at(6.3, 1.030)[1], "mass exponent", size=10, fill=MASS))164    parts.append(text(at(6.3, 0.895)[0], at(6.3, 0.895)[1], "cost exponent", size=10, fill=COST))165166    out = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "figures")167    os.makedirs(out, exist_ok=True)168    save(parts, os.path.join(out, "figure.svg"))169    print("drew figures/figure.svg")170171if __name__ == "__main__":172    main()