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("&", "&").replace("<", "<").replace(">", ">")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()