{"spec_id":"feynman-basic","library":"pygal","language":"python","code":"\"\"\" anyplot.ai\nfeynman-basic: Feynman Diagram for Particle Interactions\nLibrary: pygal 3.1.0 | Python 3.13.13\nQuality: 82/100 | Updated: 2026-06-03\n\"\"\"\n\nimport os\nimport re\nimport sys as _sys\n\n\n# Remove this file's directory from sys.path so 'import pygal' finds the installed\n# package instead of this file (which shares its name with the library).\n_this_dir = os.path.dirname(os.path.abspath(__file__))\n_sys.path[:] = [p for p in _sys.path if os.path.abspath(p or \".\") != _this_dir]\ndel _this_dir, _sys\n\nimport numpy as np\nimport pygal\nfrom pygal.style import Style\n\n\n# Theme tokens — Imprint palette, theme-adaptive chrome\nTHEME = os.getenv(\"ANYPLOT_THEME\", \"light\")\nPAGE_BG = \"#FAF8F1\" if THEME == \"light\" else \"#1A1A17\"\nINK = \"#1A1A17\" if THEME == \"light\" else \"#F0EFE8\"\nINK_MUTED = \"#6B6A63\" if THEME == \"light\" else \"#A8A79F\"\n\n# Imprint palette — doubled entries for arrowhead series to share fermion color\nPALETTE = (\n    \"#009E73\",\n    \"#009E73\",  # e⁻ fermion + arrowhead  (Imprint #1)\n    \"#C475FD\",\n    \"#C475FD\",  # e⁺ antifermion + arrowhead  (Imprint #2)\n    \"#4467A3\",\n    \"#4467A3\",  # μ⁻ fermion + arrowhead  (Imprint #3)\n    \"#BD8233\",\n    \"#BD8233\",  # μ⁺ antifermion + arrowhead  (Imprint #4)\n    \"#AE3030\",  # γ virtual photon  (Imprint #5)\n    \"#2ABCCD\",  # Z⁰ virtual boson  (Imprint #6)\n    INK,  # vertex markers\n)\n\n# Data — e⁻e⁺ → γ / Z⁰ → μ⁻μ⁺  (QED + electroweak s-channel annihilation)\nv1 = (3.0, 5.0)  # annihilation vertex\nv2 = (7.0, 5.0)  # pair creation vertex\n\ne_minus_ep = (0.2, 8.8)\ne_plus_ep = (0.2, 1.2)\nmu_minus_ep = (9.8, 8.8)\nmu_plus_ep = (9.8, 1.2)\n\n# Fermion propagator lines (2-point series)\ne_minus_line = [\n    {\"value\": e_minus_ep, \"label\": \"e⁻ incoming fermion\"},\n    {\"value\": v1, \"label\": \"Vertex 1 — annihilation\"},\n]\ne_plus_line = [\n    {\"value\": e_plus_ep, \"label\": \"e⁺ incoming antifermion\"},\n    {\"value\": v1, \"label\": \"Vertex 1 — annihilation\"},\n]\nmu_minus_line = [\n    {\"value\": v2, \"label\": \"Vertex 2 — pair creation\"},\n    {\"value\": mu_minus_ep, \"label\": \"μ⁻ outgoing fermion\"},\n]\nmu_plus_line = [\n    {\"value\": v2, \"label\": \"Vertex 2 — pair creation\"},\n    {\"value\": mu_plus_ep, \"label\": \"μ⁺ outgoing antifermion\"},\n]\n\n# Arrowhead V-shapes — inline numpy computation per fermion line\n# Convention: particle flows left→right (forward in time), antiparticle right→left\n_frac, _sz, _hw = 0.60, 0.34, 0.22  # arrow position, size, half-width\n\n# e⁻ arrowhead: (e_minus_ep → v1), particle flows forward\n_p1, _p2 = np.array(e_minus_ep), np.array(v1)\n_d = _p2 - _p1\n_l = float(np.linalg.norm(_d))\n_u = _d / _l\n_q = np.array([-_u[1], _u[0]])\n_tp = _p1 + _frac * _d\n_bs = _tp - _sz * _u\ne_minus_arrow = [\n    {\"value\": (float(_bs[0] + _hw * _q[0]), float(_bs[1] + _hw * _q[1])), \"label\": \"wing\"},\n    {\"value\": (float(_tp[0]), float(_tp[1])), \"label\": \"→ tip\"},\n    {\"value\": (float(_bs[0] - _hw * _q[0]), float(_bs[1] - _hw * _q[1])), \"label\": \"wing\"},\n]\n\n# e⁺ arrowhead: (v1 → e_plus_ep), antiparticle flows backward in time\n_p1, _p2 = np.array(v1), np.array(e_plus_ep)\n_d = _p2 - _p1\n_l = float(np.linalg.norm(_d))\n_u = _d / _l\n_q = np.array([-_u[1], _u[0]])\n_tp = _p1 + _frac * _d\n_bs = _tp - _sz * _u\ne_plus_arrow = [\n    {\"value\": (float(_bs[0] + _hw * _q[0]), float(_bs[1] + _hw * _q[1])), \"label\": \"wing\"},\n    {\"value\": (float(_tp[0]), float(_tp[1])), \"label\": \"← tip\"},\n    {\"value\": (float(_bs[0] - _hw * _q[0]), float(_bs[1] - _hw * _q[1])), \"label\": \"wing\"},\n]\n\n# μ⁻ arrowhead: (v2 → mu_minus_ep), particle flows forward\n_p1, _p2 = np.array(v2), np.array(mu_minus_ep)\n_d = _p2 - _p1\n_l = float(np.linalg.norm(_d))\n_u = _d / _l\n_q = np.array([-_u[1], _u[0]])\n_tp = _p1 + _frac * _d\n_bs = _tp - _sz * _u\nmu_minus_arrow = [\n    {\"value\": (float(_bs[0] + _hw * _q[0]), float(_bs[1] + _hw * _q[1])), \"label\": \"wing\"},\n    {\"value\": (float(_tp[0]), float(_tp[1])), \"label\": \"→ tip\"},\n    {\"value\": (float(_bs[0] - _hw * _q[0]), float(_bs[1] - _hw * _q[1])), \"label\": \"wing\"},\n]\n\n# μ⁺ arrowhead: (mu_plus_ep → v2), antiparticle flows backward in time\n_p1, _p2 = np.array(mu_plus_ep), np.array(v2)\n_d = _p2 - _p1\n_l = float(np.linalg.norm(_d))\n_u = _d / _l\n_q = np.array([-_u[1], _u[0]])\n_tp = _p1 + _frac * _d\n_bs = _tp - _sz * _u\nmu_plus_arrow = [\n    {\"value\": (float(_bs[0] + _hw * _q[0]), float(_bs[1] + _hw * _q[1])), \"label\": \"wing\"},\n    {\"value\": (float(_tp[0]), float(_tp[1])), \"label\": \"← tip\"},\n    {\"value\": (float(_bs[0] - _hw * _q[0]), float(_bs[1] - _hw * _q[1])), \"label\": \"wing\"},\n]\n\n# Photon propagator — sinusoidal wavy path slightly above centre\nt = np.linspace(0, 1, 300)\nphoton_x = v1[0] + t * (v2[0] - v1[0])\nphoton_y = 5.3 + 0.55 * np.sin(t * 16 * np.pi)\nphoton_line = [\n    {\"value\": (float(x), float(y)), \"label\": \"γ virtual photon\"} for x, y in zip(photon_x, photon_y, strict=True)\n]\n\n# Z⁰ boson propagator — dashed line slightly below centre (electroweak mediator)\nt_z = np.linspace(0, 1, 200)\nz_x = v1[0] + t_z * (v2[0] - v1[0])\nz_y = np.full(200, 4.7)\nz_line = [{\"value\": (float(x), float(y)), \"label\": \"Z⁰ virtual boson\"} for x, y in zip(z_x, z_y, strict=True)]\n\n# Vertex markers — interaction points\nvertex_points = [\n    {\"value\": v1, \"label\": \"Vertex 1 — e⁻e⁺ annihilation\"},\n    {\"value\": v2, \"label\": \"Vertex 2 — μ⁻μ⁺ pair creation\"},\n]\n\n# Style — Imprint palette, theme-adaptive chrome, 2400×2400 canvas sizing\ntitle = \"e⁻e⁺ → γ/Z⁰ → μ⁻μ⁺ · feynman-basic · python · pygal · anyplot.ai\"\n_n = len(title)\n_title_fs = round(66 * 67 / _n) if _n > 67 else 66\n\ncustom_style = Style(\n    background=PAGE_BG,\n    plot_background=PAGE_BG,\n    foreground=INK,\n    foreground_strong=INK,\n    foreground_subtle=INK_MUTED,\n    colors=PALETTE,\n    opacity=1.0,\n    opacity_hover=0.9,\n    title_font_size=_title_fs,\n    label_font_size=56,\n    major_label_font_size=44,\n    legend_font_size=48,\n    value_font_size=36,\n    stroke_width=3.0,\n    legend_box_size=30,\n)\n\n# Chart — square canvas for symmetric Feynman diagram\nchart = pygal.XY(\n    width=2400,\n    height=2400,\n    style=custom_style,\n    title=title,\n    show_x_guides=False,\n    show_y_guides=False,\n    show_x_labels=False,\n    show_y_labels=False,\n    x_title=\"Time →\",\n    y_title=\"\",\n    show_legend=True,\n    legend_at_bottom=True,\n    legend_at_bottom_columns=4,\n    stroke=True,\n    show_dots=False,\n    print_values=False,\n    margin_top=30,\n    margin_bottom=100,\n    margin_left=30,\n    margin_right=30,\n    range=(0.0, 10.0),\n    xrange=(-0.5, 10.5),\n)\n\n# Fermion lines + direction arrowheads (interleaved to match palette)\nchart.add(\"e⁻ fermion\", e_minus_line, stroke_width=7)\nchart.add(\"e⁻ (→)\", e_minus_arrow, stroke_width=6, show_dots=False)\nchart.add(\"e⁺ antifermion\", e_plus_line, stroke_width=7)\nchart.add(\"e⁺ (←)\", e_plus_arrow, stroke_width=6, show_dots=False)\nchart.add(\"μ⁻ fermion\", mu_minus_line, stroke_width=7)\nchart.add(\"μ⁻ (→)\", mu_minus_arrow, stroke_width=6, show_dots=False)\nchart.add(\"μ⁺ antifermion\", mu_plus_line, stroke_width=7)\nchart.add(\"μ⁺ (←)\", mu_plus_arrow, stroke_width=6, show_dots=False)\n\n# Propagators\nchart.add(\"γ virtual photon\", photon_line, stroke_width=5)\nchart.add(\"Z⁰ virtual boson\", z_line, stroke_width=4, stroke_style={\"dasharray\": \"14 8\"})\n\n# Vertex markers\nchart.add(\"Vertices\", vertex_points, stroke=False, show_dots=True, dots_size=18)\n\n\n# Post-process SVG for PNG: remove arrowhead legend entries and add on-diagram\n# particle labels for γ and Z⁰ propagators.\n# Series 1,3,5,7 (0-indexed) are the arrowhead helpers — hidden from legend in PNG.\n# Vertex circles (dots_size=18) anchor the coordinate transform for label placement.\ndef _process_svg_for_png(svg_bytes):\n    svg = svg_bytes.decode(\"utf-8\")\n\n    # Remove arrowhead legend entries by series id\n    for idx in (1, 3, 5, 7):\n        svg = re.sub(rf'<g\\b[^>]*\\bid=\"activate-serie-{idx}\"[^>]*>[\\s\\S]*?</g>', \"\", svg)\n\n    # Find vertex-marker circles (dots_size=18 → r≈18 in SVG)\n    vertex_pts = []\n    for c in re.findall(r\"<circle\\b[^>]+>\", svg, flags=re.IGNORECASE):\n        cx_m = re.search(r\"\\bcx=[\\\"']([^\\\"']+)[\\\"']\", c)\n        cy_m = re.search(r\"\\bcy=[\\\"']([^\\\"']+)[\\\"']\", c)\n        r_m = re.search(r\"\\br=[\\\"']([^\\\"']+)[\\\"']\", c)\n        if cx_m and cy_m and r_m and 14 < float(r_m.group(1)) < 26:\n            vertex_pts.append((float(cx_m.group(1)), float(cy_m.group(1))))\n\n    if len(vertex_pts) >= 2:\n        vertex_pts.sort(key=lambda p: p[0])\n        sv1, sv2 = vertex_pts[0], vertex_pts[1]  # SVG px for v1=(3,5), v2=(7,5)\n        # x_scale: SVG pixels per data unit (derived from known Δx = 4 data units)\n        x_scale = (sv2[0] - sv1[0]) / (7.0 - 3.0)\n        # y_scale approximated as x_scale (square canvas, similar x/y data spans)\n        y_scale = x_scale\n        mid_sx = sv1[0] + 2.0 * x_scale  # data x=5.0 → midpoint\n        mid_sy = sv1[1]  # sv1[1] corresponds to data y=5.0\n        font_px = max(60, int(x_scale * 1.0))\n        g_sy = mid_sy - 1.05 * y_scale  # above wavy γ line\n        z_sy = mid_sy + 1.05 * y_scale  # below dashed Z⁰ line\n        svg = svg.replace(\n            \"</svg>\",\n            f'<text x=\"{mid_sx:.0f}\" y=\"{g_sy:.0f}\" font-family=\"sans-serif\" '\n            f'font-size=\"{font_px}\" font-weight=\"bold\" fill=\"#AE3030\" '\n            f'text-anchor=\"middle\" dominant-baseline=\"central\">γ</text>\\n'\n            f'<text x=\"{mid_sx:.0f}\" y=\"{z_sy:.0f}\" font-family=\"sans-serif\" '\n            f'font-size=\"{font_px}\" font-weight=\"bold\" fill=\"#2ABCCD\" '\n            f'text-anchor=\"middle\" dominant-baseline=\"central\">Z⁰</text>\\n'\n            \"</svg>\",\n            1,\n        )\n\n    return svg.encode(\"utf-8\")\n\n\nfrom cairosvg import svg2png\n\n\nsvg_bytes = chart.render()\nsvg2png(bytestring=_process_svg_for_png(svg_bytes), write_to=f\"plot-{THEME}.png\", output_width=2400, output_height=2400)\nwith open(f\"plot-{THEME}.html\", \"wb\") as f:\n    f.write(svg_bytes)\n"}