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 # DATA Q = 3 K = 2 ALPHA = math.log(K) / math.log(Q) PERIOD = 2.0 * math.pi / math.log(Q) RHO = (0.720790, 28.605680) BOX = (0.72074, 0.72084, 28.60563, 28.60573) BAND = (22.01, 24.01) BAND_RIGHT = 3.02 CONTROL_ABSCISSA = 1.0 CONTROL_LINE = 0.5 CONTROL_ORDINATES = [14.1347251417, 21.0220396388, 25.0108575801, 30.4248761259, 32.9350615877, 37.5861781588] TOP = 40.0 LEFT_PAD = 0.60 RIGHT_PAD = 0.90 def poles(top): return [j * PERIOD for j in range(int(top / PERIOD) + 1)] # DRAW def panel(parts, x0, y0, w, h, abscissa, marks, bands, comb, box): lo, hi = abscissa - LEFT_PAD, abscissa + RIGHT_PAD def px(re): return x0 + w * (min(max(re, lo), hi) - lo) / (hi - lo) def py(im): return y0 + h - h * im / TOP parts.append(rect(px(abscissa), y0, x0 + w - px(abscissa), h, PALE, 0.45)) for band in bands: parts.append(rect(px(abscissa), py(band[1]), x0 + w - px(abscissa), py(band[0]) - py(band[1]), CONCEPT["window"], 0.30)) parts.append(line(px(abscissa), y0, px(abscissa), y0 + h, CONCEPT["carpet"], STROKE)) for im in comb: parts.append(circle(px(abscissa), py(im), 2.6, CONCEPT["carpet"])) if box is not None: r0, r1, i0, i1 = box side = 11.0 cx, cy = px(0.5 * (r0 + r1)), py(0.5 * (i0 + i1)) parts.append('' % (cx - side / 2, cy - side / 2, side, side, CONCEPT["window"], THIN)) for re, im, ink in marks: parts.append(circle(px(re), py(im), 4.2, ink)) parts.append(line(x0, y0 + h, x0 + w, y0 + h, PALE, THIN)) return px, py def svg(out): width, height = 520, 470 parts = header(width, height) w = 190 h = height - 2 * MARGIN - 46 y0 = MARGIN + 20 left = MARGIN + 24 right = left + w + 62 comb = poles(TOP) panel(parts, left, y0, w, h, ALPHA, [(RHO[0], RHO[1], CONCEPT["window"])], [BAND], comb, BOX) panel(parts, right, y0, w, h, CONTROL_ABSCISSA, [(CONTROL_LINE, g, CONCEPT["control"]) for g in CONTROL_ORDINATES], [], [0.0], None) parts.append(text(left, MARGIN + 10, "base 3, digits {0,1}", size=10, weight="bold")) parts.append(text(right, MARGIN + 10, "base 2, every digit", size=10, weight="bold")) parts.append(text(left, y0 + h + 16, "abscissa 0.630930, 2 zeros to the right", size=9, fill=MUTED)) parts.append(text(right, y0 + h + 16, "abscissa 1, none to the right", size=9, fill=MUTED)) parts.append(text(left, y0 + h + 30, "poles on the abscissa, period 5.719202", size=9, fill=MUTED)) parts.append(text(right, y0 + h + 30, "6 zeros on Re s = 1/2, all to the left", size=9, fill=MUTED)) parts.append(text(MARGIN, y0 - 6, "Im s = %g" % TOP, size=8, fill=MUTED, anchor="start")) parts.append(text(MARGIN, y0 + h + 3, "Im s = 0", size=8, fill=MUTED, anchor="start")) save(parts, out) # TIKZ def tikz(out): xs, ys = 2.55, 0.168 gap = 0.75 lines = [] lines.append("\\begin{tikzpicture}[x=1cm,y=1cm]") lines.append("\\definecolor{lane}{HTML}{4059AD}") lines.append("\\definecolor{hit}{HTML}{C23B22}") lines.append("\\definecolor{ctl}{HTML}{9AA3AD}") lines.append("\\definecolor{pale}{HTML}{D5D9E0}") span = (LEFT_PAD + RIGHT_PAD) * xs for idx, (abscissa, marks, bands, comb, box, title, note) in enumerate([ (ALPHA, [RHO], [BAND], poles(TOP), BOX, "base $3$, digits $\\{0,1\\}$", "$\\alpha = 0.630930$"), (CONTROL_ABSCISSA, [(CONTROL_LINE, g) for g in CONTROL_ORDINATES], [], [0.0], None, "base $2$, every digit", "$\\alpha = 1$"), ]): ox = idx * (span + gap) lo = abscissa - LEFT_PAD def px(re): return ox + (min(max(re, lo), abscissa + RIGHT_PAD) - lo) * xs def py(im): return im * ys lines.append("\\fill[pale,opacity=0.45] (%.3f,%.3f) rectangle (%.3f,%.3f);" % (px(abscissa), 0.0, ox + span, py(TOP))) for band in bands: lines.append("\\fill[hit,opacity=0.25] (%.3f,%.3f) rectangle (%.3f,%.3f);" % (px(abscissa), py(band[0]), ox + span, py(band[1]))) lines.append("\\draw[lane,thick] (%.3f,%.3f) -- (%.3f,%.3f);" % (px(abscissa), -0.12, px(abscissa), py(TOP) + 0.12)) for im in comb: lines.append("\\fill[lane] (%.3f,%.3f) circle (0.045);" % (px(abscissa), py(im))) if box is not None: r0, r1, i0, i1 = box cx, cy = px(0.5 * (r0 + r1)), py(0.5 * (i0 + i1)) lines.append("\\draw[hit,thin] (%.3f,%.3f) rectangle (%.3f,%.3f);" % (cx - 0.13, cy - 0.13, cx + 0.13, cy + 0.13)) ink = "hit" if idx == 0 else "ctl" for re, im in marks: lines.append("\\fill[%s] (%.3f,%.3f) circle (0.075);" % (ink, px(re), py(im))) lines.append("\\draw[black!45] (%.3f,%.3f) -- (%.3f,%.3f);" % (ox, 0.0, ox + span, 0.0)) lines.append("\\node[anchor=south west,font=\\scriptsize] at (%.3f,%.3f) {%s};" % (ox, py(TOP) + 0.30, title)) lines.append("\\node[anchor=north west,font=\\scriptsize] at (%.3f,%.3f) {%s};" % (ox, -0.22, note)) for im in (0, 10, 20, 30, 40): lines.append("\\node[anchor=east,font=\\scriptsize] at (%.3f,%.3f) {$%d$};" % (-0.12, im * ys, im)) lines.append("\\node[anchor=east,font=\\scriptsize,rotate=90] at (%.3f,%.3f) {$\\operatorname{Im} s$};" % (-0.62, 0.5 * py(TOP))) lines.append("\\end{tikzpicture}") with open(out, "w") as handle: handle.write("\n".join(lines) + "\n") def main(): out = os.path.join(os.path.dirname(os.path.abspath(__file__)), "..", "figures") os.makedirs(out, exist_ok=True) assert len(poles(TOP)) == 7, "pole comb below Im 40 should hold 7 points" assert ALPHA < RHO[0] < ALPHA + BAND_RIGHT, "the certified zero must sit right of the abscissa" assert BOX[0] < RHO[0] < BOX[1] and BOX[2] < RHO[1] < BOX[3], "the certified zero must sit inside its box" assert len(CONTROL_ORDINATES) == 6, "six ordinates of zeta below Im 40" svg(os.path.join(out, "figure.svg")) tikz(os.path.join(out, "zeros.tex")) print("drew figures/figure.svg and figures/zeros.tex") if __name__ == "__main__": main()