import math import os # 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 MASS = CONCEPT["control"] COST = CONCEPT["bound"] CROSSING = CONCEPT["window"] GAMMA = 0.5772156649015328606065120900824024310421 WALL = 3690 LO, HI = 2.0, 9.0 FLOOR, CEIL = 0.86, 1.14 def harmonic(n): return math.log(n) + GAMMA + 1.0 / (2 * n) def proved(q): n = -((-(q - 2)) // 2) phi = (4 / math.pi) * q + (2 * q / math.pi) * harmonic(n) + (1 - 2 / math.pi) * (q - 2) + 0.727 return 1.0 + phi / q def mass(q): return math.log(q - 1) / math.log(q) def cost(q): return 0.75 + math.log(proved(q)) / math.log(q) def samples(f, steps=112): out = [] for i in range(steps + 1): x = LO + (HI - LO) * i / steps out.append((x, f(int(round(10 ** x))))) return out def main(): width, height = 640, 300 left, right = MARGIN + 28, width - MARGIN top, bottom = MARGIN - 6, height - MARGIN - 8 parts = header(width, height) def at(x, y): return (left + (right - left) * (x - LO) / (HI - LO), bottom - (bottom - top) * (y - FLOOR) / (CEIL - FLOOR)) curve_mass = samples(mass) curve_cost = samples(cost) wall = math.log10(WALL) band = [at(*p) for p in curve_cost if p[0] >= wall] band = [at(wall, mass(WALL))] + band + [at(HI, mass(int(10 ** HI)))] band += [at(*p) for p in reversed([p for p in curve_mass if p[0] >= wall])] parts.append('' % (" ".join("%.1f,%.1f" % p for p in band), COST)) parts.append(line(left, bottom, right, bottom, PALE, THIN)) parts.append(line(left, top, left, bottom, PALE, THIN)) for e in range(2, 10): x, y = at(e, FLOOR) parts.append(line(x, y, x, y + 4, PALE, THIN)) parts.append(text(x, y + 16, "1e%d" % e, size=9, fill=MUTED, anchor="middle")) for v in (0.90, 1.00, 1.10): x, y = at(LO, v) parts.append(line(x - 4, y, x, y, PALE, THIN)) parts.append(text(x - 8, y + 3, "%.2f" % v, size=9, fill=MUTED, anchor="end")) xw, _ = at(wall, FLOOR) parts.append(line(xw, top, xw, bottom, CROSSING, THIN)) parts.append(text(xw + 5, top + 10, "q = 3690", size=9, fill=CROSSING)) for series, colour in ((curve_mass, MASS), (curve_cost, COST)): pts = " ".join("%.1f,%.1f" % at(*p) for p in series) parts.append('' % (pts, colour, STROKE)) parts.append(circle(*at(wall, mass(WALL)), 3.0, INK)) parts.append(text(at(6.3, 1.030)[0], at(6.3, 1.030)[1], "mass exponent", size=10, fill=MASS)) parts.append(text(at(6.3, 0.895)[0], at(6.3, 0.895)[1], "cost exponent", size=10, fill=COST)) out = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "figures") os.makedirs(out, exist_ok=True) save(parts, os.path.join(out, "figure.svg")) print("drew figures/figure.svg") if __name__ == "__main__": main()