figure.py

8.7 kB · python · 216 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# DATA9596Q = 397K = 298ALPHA = math.log(K) / math.log(Q)99PERIOD = 2.0 * math.pi / math.log(Q)100RHO = (0.720790, 28.605680)101BOX = (0.72074, 0.72084, 28.60563, 28.60573)102BAND = (22.01, 24.01)103BAND_RIGHT = 3.02104CONTROL_ABSCISSA = 1.0105CONTROL_LINE = 0.5106CONTROL_ORDINATES = [14.1347251417, 21.0220396388, 25.0108575801, 30.4248761259, 32.9350615877, 37.5861781588]107TOP = 40.0108LEFT_PAD = 0.60109RIGHT_PAD = 0.90110111def poles(top):112    return [j * PERIOD for j in range(int(top / PERIOD) + 1)]113114# DRAW115116def panel(parts, x0, y0, w, h, abscissa, marks, bands, comb, box):117    lo, hi = abscissa - LEFT_PAD, abscissa + RIGHT_PAD118    def px(re):119        return x0 + w * (min(max(re, lo), hi) - lo) / (hi - lo)120    def py(im):121        return y0 + h - h * im / TOP122    parts.append(rect(px(abscissa), y0, x0 + w - px(abscissa), h, PALE, 0.45))123    for band in bands:124        parts.append(rect(px(abscissa), py(band[1]), x0 + w - px(abscissa), py(band[0]) - py(band[1]), CONCEPT["window"], 0.30))125    parts.append(line(px(abscissa), y0, px(abscissa), y0 + h, CONCEPT["carpet"], STROKE))126    for im in comb:127        parts.append(circle(px(abscissa), py(im), 2.6, CONCEPT["carpet"]))128    if box is not None:129        r0, r1, i0, i1 = box130        side = 11.0131        cx, cy = px(0.5 * (r0 + r1)), py(0.5 * (i0 + i1))132        parts.append('<rect x="%.1f" y="%.1f" width="%.1f" height="%.1f" fill="none" stroke="%s" stroke-width="%.1f"/>' % (cx - side / 2, cy - side / 2, side, side, CONCEPT["window"], THIN))133    for re, im, ink in marks:134        parts.append(circle(px(re), py(im), 4.2, ink))135    parts.append(line(x0, y0 + h, x0 + w, y0 + h, PALE, THIN))136    return px, py137138def svg(out):139    width, height = 520, 470140    parts = header(width, height)141    w = 190142    h = height - 2 * MARGIN - 46143    y0 = MARGIN + 20144    left = MARGIN + 24145    right = left + w + 62146    comb = poles(TOP)147    panel(parts, left, y0, w, h, ALPHA, [(RHO[0], RHO[1], CONCEPT["window"])], [BAND], comb, BOX)148    panel(parts, right, y0, w, h, CONTROL_ABSCISSA, [(CONTROL_LINE, g, CONCEPT["control"]) for g in CONTROL_ORDINATES], [], [0.0], None)149    parts.append(text(left, MARGIN + 10, "base 3, digits {0,1}", size=10, weight="bold"))150    parts.append(text(right, MARGIN + 10, "base 2, every digit", size=10, weight="bold"))151    parts.append(text(left, y0 + h + 16, "abscissa 0.630930, 2 zeros to the right", size=9, fill=MUTED))152    parts.append(text(right, y0 + h + 16, "abscissa 1, none to the right", size=9, fill=MUTED))153    parts.append(text(left, y0 + h + 30, "poles on the abscissa, period 5.719202", size=9, fill=MUTED))154    parts.append(text(right, y0 + h + 30, "6 zeros on Re s = 1/2, all to the left", size=9, fill=MUTED))155    parts.append(text(MARGIN, y0 - 6, "Im s = %g" % TOP, size=8, fill=MUTED, anchor="start"))156    parts.append(text(MARGIN, y0 + h + 3, "Im s = 0", size=8, fill=MUTED, anchor="start"))157    save(parts, out)158159# TIKZ160161def tikz(out):162    xs, ys = 2.55, 0.168163    gap = 0.75164    lines = []165    lines.append("\\begin{tikzpicture}[x=1cm,y=1cm]")166    lines.append("\\definecolor{lane}{HTML}{4059AD}")167    lines.append("\\definecolor{hit}{HTML}{C23B22}")168    lines.append("\\definecolor{ctl}{HTML}{9AA3AD}")169    lines.append("\\definecolor{pale}{HTML}{D5D9E0}")170    span = (LEFT_PAD + RIGHT_PAD) * xs171    for idx, (abscissa, marks, bands, comb, box, title, note) in enumerate([172        (ALPHA, [RHO], [BAND], poles(TOP), BOX, "base $3$, digits $\\{0,1\\}$", "$\\alpha = 0.630930$"),173        (CONTROL_ABSCISSA, [(CONTROL_LINE, g) for g in CONTROL_ORDINATES], [], [0.0], None, "base $2$, every digit", "$\\alpha = 1$"),174    ]):175        ox = idx * (span + gap)176        lo = abscissa - LEFT_PAD177        def px(re):178            return ox + (min(max(re, lo), abscissa + RIGHT_PAD) - lo) * xs179        def py(im):180            return im * ys181        lines.append("\\fill[pale,opacity=0.45] (%.3f,%.3f) rectangle (%.3f,%.3f);" % (px(abscissa), 0.0, ox + span, py(TOP)))182        for band in bands:183            lines.append("\\fill[hit,opacity=0.25] (%.3f,%.3f) rectangle (%.3f,%.3f);" % (px(abscissa), py(band[0]), ox + span, py(band[1])))184        lines.append("\\draw[lane,thick] (%.3f,%.3f) -- (%.3f,%.3f);" % (px(abscissa), -0.12, px(abscissa), py(TOP) + 0.12))185        for im in comb:186            lines.append("\\fill[lane] (%.3f,%.3f) circle (0.045);" % (px(abscissa), py(im)))187        if box is not None:188            r0, r1, i0, i1 = box189            cx, cy = px(0.5 * (r0 + r1)), py(0.5 * (i0 + i1))190            lines.append("\\draw[hit,thin] (%.3f,%.3f) rectangle (%.3f,%.3f);" % (cx - 0.13, cy - 0.13, cx + 0.13, cy + 0.13))191        ink = "hit" if idx == 0 else "ctl"192        for re, im in marks:193            lines.append("\\fill[%s] (%.3f,%.3f) circle (0.075);" % (ink, px(re), py(im)))194        lines.append("\\draw[black!45] (%.3f,%.3f) -- (%.3f,%.3f);" % (ox, 0.0, ox + span, 0.0))195        lines.append("\\node[anchor=south west,font=\\scriptsize] at (%.3f,%.3f) {%s};" % (ox, py(TOP) + 0.30, title))196        lines.append("\\node[anchor=north west,font=\\scriptsize] at (%.3f,%.3f) {%s};" % (ox, -0.22, note))197    for im in (0, 10, 20, 30, 40):198        lines.append("\\node[anchor=east,font=\\scriptsize] at (%.3f,%.3f) {$%d$};" % (-0.12, im * ys, im))199    lines.append("\\node[anchor=east,font=\\scriptsize,rotate=90] at (%.3f,%.3f) {$\\operatorname{Im} s$};" % (-0.62, 0.5 * py(TOP)))200    lines.append("\\end{tikzpicture}")201    with open(out, "w") as handle:202        handle.write("\n".join(lines) + "\n")203204def main():205    out = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "figures")206    os.makedirs(out, exist_ok=True)207    assert len(poles(TOP)) == 7, "pole comb below Im 40 should hold 7 points"208    assert ALPHA < RHO[0] < ALPHA + BAND_RIGHT, "the certified zero must sit right of the abscissa"209    assert BOX[0] < RHO[0] < BOX[1] and BOX[2] < RHO[1] < BOX[3], "the certified zero must sit inside its box"210    assert len(CONTROL_ORDINATES) == 6, "six ordinates of zeta below Im 40"211    svg(os.path.join(out, "figure.svg"))212    tikz(os.path.join(out, "zeros.tex"))213    print("drew figures/figure.svg and figures/zeros.tex")214215if __name__ == "__main__":216    main()