Skip to content

Commit d2b3c79

Browse files
committed
bug fixes
1 parent 71b0279 commit d2b3c79

12 files changed

Lines changed: 1065 additions & 182 deletions

File tree

README.md

Lines changed: 39 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
[![PyPI](https://img.shields.io/pypi/v/feynman-engine?label=PyPI&color=blue&cacheSeconds=300)](https://pypi.org/project/feynman-engine/)
44
[![Python](https://img.shields.io/pypi/pyversions/feynman-engine?cacheSeconds=300)](https://pypi.org/project/feynman-engine/)
55
[![License](https://img.shields.io/pypi/l/feynman-engine?cacheSeconds=300)](LICENSE)
6-
[![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.19673075.svg?v=0.1.4)](https://doi.org/10.5281/zenodo.19673075)
6+
[![DOI](https://zenodo.org/badge/DOI/10.5281/zenodo.19673075.svg?v=0.1.5)](https://doi.org/10.5281/zenodo.19673075)
77

88
A Feynman diagram generator and amplitude calculator for particle physics. Type a process like `e+ e- -> mu+ mu-` and get back enumerated diagrams (SVG/TikZ), the symbolic spin-averaged $|\overline{\mathcal{M}}|^2$, integrated cross-sections at LO and NLO, decay widths, and 1-loop scalar integrals.
99

@@ -46,14 +46,14 @@ For platform-specific prerequisites, the lightweight install path, Docker, and t
4646
## Capabilities
4747

4848
- **Feynman diagrams** by topology, rendered to SVG/TikZ
49-
- **Tree amplitudes** via 54 curated formulas, FORM color algebra, or SymPy γ-matrix traces
50-
- **1-loop amplitudes** via 35 curated formulas, Passarino-Veltman reduction, and analytic A0/B0/C0/D0 (pure Python, no Fortran needed for the closed-form integrals)
49+
- **Tree amplitudes** via 140 curated formulas (15 QED, 56 QCD, 68 EW, 1 BSM template, including per-quark-flavour Drell-Yan and charged-current variants), FORM color algebra, or SymPy γ-matrix traces
50+
- **1-loop amplitudes** via 35 curated formulas, Passarino-Veltman reduction, and analytic A0/B0/C0/D0 (pure Python, no Fortran needed for the closed-form integrals; LoopTools fallback for general kinematics)
5151
- **Cross-sections** via scipy.quad (2→2) and RAMBO/Vegas Monte Carlo (2→N) with full massive Källén kinematics
52-
- **Decay widths** for every Z/W/H/top channel with self-reported PDG deviation
52+
- **Decay widths** for nine Higgs channels plus all Z/W/top channels, agreeing with PDG 2024 within 3% per channel and within 0.1% on the summed Higgs width; off-shell H → V*V* → 4f handled via 2-D Breit-Wigner integration (Pocsik-Zsigmond / Cahn)
5353
- **Differential observables** for cosθ, pT, η, y, M_inv, M_ll, ΔR
5454
- **Hadronic σ** with built-in LO PDF (factor-of-2-3 accuracy) or LHAPDF + CT18LO (percent-level)
55-
- **NLO** in five regimes: tabulated LHC K-factors, universal QED via charge-correlator (`K = 1 + 3α/(4π)` exact for textbook cases), EW Sudakov LL+NLL, first-principles Catani-Seymour Drell-Yan (`K(pp→DY @ 13 TeV) = 1.190` vs YR4 1.21), and OpenLoops virtuals for arbitrary QCD processes
56-
- **Trust badges** on every numerical result (`validated` / `approximate` / `blocked`); the API refuses (HTTP 422) processes that would otherwise return wrong numbers
55+
- **NLO** in six regimes: 34 tabulated LHC K-factors, universal QED via charge-correlator (`K = 1 + 3α/(4π)` exact for textbook cases), EW Sudakov LL+NLL, first-principles Catani-Seymour Drell-Yan (`K(pp→DY @ 13 TeV) = 1.19` vs YR4 1.21), tabulated NLO QCD K-factors for partial decay widths (`H → gg` K=1.66, `H → bb̄` K=1.13, `H → cc̄` K=1.24), and OpenLoops virtuals for arbitrary QCD processes
56+
- **Trust labels** on every numerical result (`validated` / `approximate` / `rough` / `blocked`); the API refuses (HTTP 422) processes that would otherwise return wrong numbers
5757
- **REST API + Python API + browser UI** all from one `pip install`
5858

5959
### Theory coverage
@@ -70,13 +70,30 @@ For platform-specific prerequisites, the lightweight install path, Docker, and t
7070

7171
| Process | Engine σ | Reference | Status |
7272
|---|---|---|---|
73-
| pp → tt̄ | 793 pb | 700-830 pb | within 13% |
73+
| pp → tt̄ (LO) | 518 pb | MG5 LO 504 pb | within 3% of MG5 |
74+
| pp → tt̄ (NLO, K=1.6) | 828 pb | LHC NLO 700-830 pb | in band |
7475
| pp → DY (60 < M_ll < 120) | 1530 pb | ~2000 pb | within 25% |
75-
| pp → ZZ | 7.8 pb | ~10 pb | within 22% |
76-
| pp → H (ggF) | 22.7 pb | 16-22 pb (PDF dep.) | in range |
77-
| pp → ZH | 0.27 pb | ~0.5 pb | within 50% |
76+
| pp → ZZ | 8.8 pb | ~10 pb | within 12% |
77+
| pp → H (ggF, NLO K=1.7) | 38.6 pb | YR4 NNLO ~44 pb | within 12% |
78+
| pp → ZH | 0.58 pb | ~0.5 pb | within 16% |
7879
| pp → H + jj (VBF) | 3.78 pb | 3.78 pb | exact (calibrated) |
79-
| pp → γγ (pT_γ > 30 GeV) | 59 pb | 30-50 pb | within 2× |
80+
| pp → γγ (pT_γ > 30 GeV) | 30 pb | 30-50 pb | in range |
81+
82+
### Higgs decay validation (m_H = 125.20 GeV)
83+
84+
All nine SM Higgs partial widths agree with PDG 2024 within 3%; sum of partial widths reproduces the PDG total Γ_H to 0.1%.
85+
86+
| Channel | Engine | PDG 2024 | Δ |
87+
|---|---|---|---|
88+
| H → bb̄ (LO + NLO QCD K=1.13) | 2.413 MeV | 2.41 MeV | +0.12% |
89+
| H → cc̄ (LO + NLO QCD K=1.24) | 0.117 MeV | 0.117 MeV | +0.24% |
90+
| H → τ⁺τ⁻ | 0.259 MeV | 0.257 MeV | +0.83% |
91+
| H → gg (LO + NLO QCD K=1.66) | 0.335 MeV | 0.336 MeV | -0.25% |
92+
| H → γγ (exact loop FF) | 9.16 keV | 9.31 keV | -1.6% |
93+
| H → Zγ (loop FF) | 6.41 keV | 6.31 keV | +1.5% |
94+
| H → W*W* → 4f (off-shell BW) | 0.855 MeV | 0.881 MeV | -3.0% |
95+
| H → Z*Z* → 4f (off-shell BW) | 0.108 MeV | 0.108 MeV | -0.02% |
96+
| **Σ Γ_H (sum)** | **4.10 MeV** | **4.10 MeV** | **+0.1%** |
8097

8198
### What's intentionally out of scope
8299

@@ -96,7 +113,16 @@ r = total_cross_section("e+ e- -> mu+ mu-", "QED", sqrt_s=91.0)
96113
print(r["sigma_pb"], r["trust_level"]) # 10.47 pb, "validated"
97114

98115
r = hadronic_cross_section("p p -> t t~", sqrt_s=13000.0, theory="QCD", order="NLO")
99-
print(r["sigma_pb"], r["k_factor"]) # 793 pb, K=1.6 (tabulated NLO)
116+
print(r["sigma_pb"], r["k_factor"]) # 828 pb, K=1.6 (tabulated NLO)
117+
```
118+
119+
The decay-width route accepts an `order` parameter:
120+
121+
```python
122+
from feynman_engine.api.routes import get_decay_width
123+
124+
r = get_decay_width(process="H -> g g", theory="QCD", order="NLO")
125+
print(r["width_mev"], r["k_factor_nlo"]) # 0.335 MeV, K=1.66 (Spira 1995)
100126
```
101127

102128
The full REST API surface is documented at `http://localhost:8000/docs` (Swagger) once you run `feynman serve`.
@@ -162,7 +188,7 @@ feynman_engine/
162188
└── resources/ Bundled HEP source archives + QGRAF model files
163189
```
164190

165-
The trust system in `physics/trust.py` is the safety boundary. Every endpoint that returns a number classifies the request first and refuses (HTTP 422 with a structured `block_reason` and `workaround`) for processes known to produce wrong values.
191+
The trust labelling in `physics/trust.py` is the safety boundary. Every endpoint that returns a number classifies the request first and refuses (HTTP 422 with a structured `block_reason` and `workaround`) for processes known to produce wrong values.
166192

167193
## Citations
168194

feynman_engine/amplitudes/cross_section.py

Lines changed: 65 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -80,11 +80,21 @@ def _build_coupling_defaults(theory: str) -> dict[str, float]:
8080
|M|² if Z were a pure vector boson with that single coupling — the
8181
backend's V-A structure approximation.
8282
"""
83-
# Electroweak parameters (PDG 2023 values).
83+
# Electroweak parameters (PDG 2024 values).
8484
_SIN2_THETA_W = 0.23122
8585
_COS2_THETA_W = 1.0 - _SIN2_THETA_W
86-
_G_W = _E_EM / math.sqrt(_SIN2_THETA_W)
87-
_G_Z = _E_EM / math.sqrt(_SIN2_THETA_W * _COS2_THETA_W)
86+
87+
# "Improved Born" coupling: use α(M_Z²) ≈ 1/128.95 instead of α(0)
88+
# ≈ 1/137.036 for resonance-scale processes. This absorbs the bulk of
89+
# the leptonic + hadronic vacuum polarisation into the Z/W vertex
90+
# factors, bringing tree-level Γ(Z→ll), Γ(W→ℓν), Γ(Z→qq̄) within ~1 %
91+
# of PDG values. The Thomson-limit α(0) is kept as ``alpha`` /
92+
# ``e_em`` so QED-only processes (Bhabha, Compton, e+e-→μ+μ- pure
93+
# photon) are unchanged; the on-shell α only enters the EW couplings.
94+
_ALPHA_MZ = 1.0 / 128.95
95+
_E_EM_MZ = math.sqrt(4.0 * math.pi * _ALPHA_MZ)
96+
_G_W = _E_EM_MZ / math.sqrt(_SIN2_THETA_W)
97+
_G_Z = _E_EM_MZ / math.sqrt(_SIN2_THETA_W * _COS2_THETA_W)
8898
_VEV = 246.21965 # Higgs VEV in GeV
8999

90100
def _g_Z_fermion(T3: float, Q: float) -> float:
@@ -439,7 +449,20 @@ def differential_cross_section(
439449

440450
defaults = _build_coupling_defaults(theory)
441451
if coupling_vals:
452+
# Apply override. When the user overrides ``alpha`` (the
453+
# fine-structure constant), also update derived symbols ``e``
454+
# and ``e_em`` (= √(4π·α)) so curated formulas that use the
455+
# electric-charge symbol pick up the new value. Same for α_s
456+
# and ``g_s``/``g``.
442457
defaults.update(coupling_vals)
458+
if "alpha" in coupling_vals and coupling_vals["alpha"] is not None:
459+
new_e = math.sqrt(4.0 * math.pi * coupling_vals["alpha"])
460+
defaults["e"] = new_e
461+
defaults["e_em"] = new_e
462+
if "alpha_s" in coupling_vals and coupling_vals["alpha_s"] is not None:
463+
new_g_s = math.sqrt(4.0 * math.pi * coupling_vals["alpha_s"])
464+
defaults["g_s"] = new_g_s
465+
defaults["g"] = new_g_s
443466

444467
try:
445468
masses = _get_particle_masses(process, theory)
@@ -534,7 +557,20 @@ def total_cross_section(
534557

535558
defaults = _build_coupling_defaults(theory)
536559
if coupling_vals:
560+
# Apply override. When the user overrides ``alpha`` (the
561+
# fine-structure constant), also update derived symbols ``e``
562+
# and ``e_em`` (= √(4π·α)) so curated formulas that use the
563+
# electric-charge symbol pick up the new value. Same for α_s
564+
# and ``g_s``/``g``.
537565
defaults.update(coupling_vals)
566+
if "alpha" in coupling_vals and coupling_vals["alpha"] is not None:
567+
new_e = math.sqrt(4.0 * math.pi * coupling_vals["alpha"])
568+
defaults["e"] = new_e
569+
defaults["e_em"] = new_e
570+
if "alpha_s" in coupling_vals and coupling_vals["alpha_s"] is not None:
571+
new_g_s = math.sqrt(4.0 * math.pi * coupling_vals["alpha_s"])
572+
defaults["g_s"] = new_g_s
573+
defaults["g"] = new_g_s
538574

539575
try:
540576
masses = _get_particle_masses(process, theory)
@@ -698,7 +734,20 @@ def total_cross_section_mc(
698734

699735
defaults = _build_coupling_defaults(theory)
700736
if coupling_vals:
737+
# Apply override. When the user overrides ``alpha`` (the
738+
# fine-structure constant), also update derived symbols ``e``
739+
# and ``e_em`` (= √(4π·α)) so curated formulas that use the
740+
# electric-charge symbol pick up the new value. Same for α_s
741+
# and ``g_s``/``g``.
701742
defaults.update(coupling_vals)
743+
if "alpha" in coupling_vals and coupling_vals["alpha"] is not None:
744+
new_e = math.sqrt(4.0 * math.pi * coupling_vals["alpha"])
745+
defaults["e"] = new_e
746+
defaults["e_em"] = new_e
747+
if "alpha_s" in coupling_vals and coupling_vals["alpha_s"] is not None:
748+
new_g_s = math.sqrt(4.0 * math.pi * coupling_vals["alpha_s"])
749+
defaults["g_s"] = new_g_s
750+
defaults["g"] = new_g_s
702751

703752
# Substitute coupling constants by symbol name.
704753
subs_map = {}
@@ -886,7 +935,20 @@ def total_cross_section_vegas(
886935

887936
defaults = _build_coupling_defaults(theory)
888937
if coupling_vals:
938+
# Apply override. When the user overrides ``alpha`` (the
939+
# fine-structure constant), also update derived symbols ``e``
940+
# and ``e_em`` (= √(4π·α)) so curated formulas that use the
941+
# electric-charge symbol pick up the new value. Same for α_s
942+
# and ``g_s``/``g``.
889943
defaults.update(coupling_vals)
944+
if "alpha" in coupling_vals and coupling_vals["alpha"] is not None:
945+
new_e = math.sqrt(4.0 * math.pi * coupling_vals["alpha"])
946+
defaults["e"] = new_e
947+
defaults["e_em"] = new_e
948+
if "alpha_s" in coupling_vals and coupling_vals["alpha_s"] is not None:
949+
new_g_s = math.sqrt(4.0 * math.pi * coupling_vals["alpha_s"])
950+
defaults["g_s"] = new_g_s
951+
defaults["g"] = new_g_s
890952

891953
# Substitute coupling constants by symbol name.
892954
subs_map = {}

feynman_engine/amplitudes/differential.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -369,15 +369,20 @@ def _histogram_2toN_mc(
369369
from feynman_engine.physics.amplitude import get_amplitude
370370
from feynman_engine.physics.translator import parse_process
371371

372-
if observable not in _MC_OBSERVABLES:
372+
# Accept observable name case-insensitively (M_ll == m_ll, pT_lepton == pt_lepton, …)
373+
obs_canon = next(
374+
(k for k in _MC_OBSERVABLES if k.lower() == observable.lower()),
375+
observable,
376+
)
377+
if obs_canon not in _MC_OBSERVABLES:
373378
return {
374379
"supported": False,
375380
"error": (
376381
f"Observable '{observable}' not implemented for 2→N. "
377382
f"Available: {sorted(_MC_OBSERVABLES)}."
378383
),
379384
}
380-
obs_fn, unit = _MC_OBSERVABLES[observable]
385+
obs_fn, unit = _MC_OBSERVABLES[obs_canon]
381386

382387
try:
383388
spec = parse_process(process.strip(), theory.upper())

feynman_engine/amplitudes/loop_curated.py

Lines changed: 52 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,8 @@
2626
from typing import Optional
2727

2828
from sympy import (
29-
Expr, Integer, Rational, Symbol, latex, pi, symbols, sqrt, log, Abs
29+
Expr, Integer, Rational, Symbol, latex, pi, symbols, sqrt, log, Abs,
30+
asin, Piecewise,
3031
)
3132

3233
from feynman_engine.amplitudes.types import AmplitudeResult
@@ -595,28 +596,34 @@ def _ew_h_to_gg_1loop() -> AmplitudeResult:
595596
In the heavy-top limit (m_t → ∞), the form factor A_{1/2}(τ) → 4/3, and
596597
the decay width becomes exact to O(α_s²):
597598
598-
Γ(H→gg) = (α_s² m_H³)/(72π³ v²)
599+
Γ(H→gg) = (α_s² m_H³)/(72 π³ v²) [LO, NLO and NNLO QCD
600+
corrections add ~85%]
599601
600-
The spin-summed |M|² for H → gg (from the effective ggH vertex):
601-
Σ|M|² = (α_s² m_H⁴)/(8π² v²)
602+
The spin- and colour-summed ||² for H → gg from the effective ggH
603+
vertex (L_eff = (α_s/(3π v)) h G^a_μν G^{aμν}/4) is:
602604
603-
averaged over initial polarisations (no average — scalar parent):
604-
|M̄|² = (α_s² m_H⁴)/(8π² v²)
605+
|M̄|² = (α_s/(3π v))² × δ^{ab}δ^{ab} × 2 (k₁·k₂)²
606+
= (α_s²)/(9π²v²) × 8 × 2 × (m_H²/2)²
607+
= 4 α_s² m_H⁴ / (9 π² v²)
605608
606-
This is the dominant Higgs production mechanism at the LHC (gg fusion)
607-
and its reverse decay.
609+
H is scalar so no initial-state spin average. Two final gluons get a
610+
1/2! identical-particle factor when computing the width.
611+
612+
Then Γ = (1/2!) × |M̄|² × |p|/(8π m_H²) with |p| = m_H/2 reproduces
613+
the standard formula α_s² m_H³/(72 π³ v²).
608614
609615
Ref: Ellis, Grinstein, Wilczek, PLB 292 (1992);
610616
Spira, Djouadi, Graudenz, Zerwas, NPB 453 (1995);
611617
Peskin & Schroeder problem 21.3.
612618
"""
613-
msq = alpha_s**2 * m_H**4 / (8 * pi**2 * v**2)
619+
# 4/(9 π² v²) factor — was (1/8 π² v²) which is 32/9× too small.
620+
msq = Rational(4, 9) * alpha_s**2 * m_H**4 / (pi**2 * v**2)
614621
return AmplitudeResult(
615622
process="H -> g g",
616623
theory="QCD",
617624
msq=msq,
618625
msq_latex=(
619-
r"|\mathcal{M}|^2 = \frac{\alpha_s^2 m_H^4}{8\pi^2 v^2}"
626+
r"|\mathcal{M}|^2 = \frac{4\alpha_s^2 m_H^4}{9\pi^2 v^2}"
620627
r"\quad\text{(heavy-top limit)}"
621628
),
622629
integral_latex=(
@@ -653,23 +660,38 @@ def _ew_h_to_gammagamma_1loop() -> AmplitudeResult:
653660
= (α² m_H³)/(256π³ v²) × |−7 + 16/9|²
654661
= (α² m_H³)/(256π³ v²) × (47/9)²
655662
656-
The spin-summed |M|²:
657-
Σ|M|² = (α² m_H⁴)/(32π² v²) × (47/9)²
663+
Inverting Γ = (1/(2!)) × |M|² × |p|/(8π m_H²) with |p| = m_H/2:
664+
|M|² = (α² m_H⁴)/(² v²) × (47/9)²
658665
659666
Ref: Ellis, Grinstein, Wilczek PLB 292 (1992);
660667
Marciano & Zhang PRD 33 (1986);
661-
Djouadi, Phys. Rep. 457 (2008) 1–216 §2.3.
668+
Djouadi, Phys. Rep. 457 (2008) 1–216 §2.3;
669+
PDG 2024 Higgs review.
662670
"""
663-
# |A_W + N_c Q_t^2 A_top|^2 = |-7 + 3*(4/9)*(4/3)|^2 = |-7 + 16/9|^2
664-
# = |(-63+16)/9|^2 = (47/9)^2
665-
amp_factor_sq = (Rational(47, 9))**2
666-
msq = alpha**2 * m_H**4 / (32 * pi**2 * v**2) * amp_factor_sq
671+
# Exact finite-mass form factors (Djouadi review hep-ph/0503173 Eq. 2.43):
672+
# τ_i = m_H² / (4 m_i²)
673+
# f(τ) = arcsin²(√τ) (for τ ≤ 1)
674+
# A_{1/2}(τ) = 2 [τ + (τ−1) f(τ)] / τ² (fermion loop)
675+
# A_1(τ) = −[2τ² + 3τ + 3(2τ−1) f(τ)] / τ² (W loop)
676+
# Heavy-particle limits: A_{1/2}(τ→0) = 4/3, A_1(τ→0) = −7, giving
677+
# |−7 + 16/9|² = (47/9)² ≈ 27.27. At m_H = 125 GeV the exact factors
678+
# give |A|² ≈ 41.9, so the heavy-particle limit underestimates by ~35 %.
679+
tau_W = m_H**2 / (4 * m_W**2)
680+
tau_t = m_H**2 / (4 * m_t**2)
681+
f_W = asin(sqrt(tau_W))**2
682+
f_t = asin(sqrt(tau_t))**2
683+
A_1_W = -(2 * tau_W**2 + 3 * tau_W + 3 * (2 * tau_W - 1) * f_W) / tau_W**2
684+
A_half_t = 2 * (tau_t + (tau_t - 1) * f_t) / tau_t**2
685+
# N_c Q_t² A_{1/2} for the top loop (N_c = 3, Q_t = 2/3 → N_c Q_t² = 4/3)
686+
A_total = A_1_W + Rational(4, 3) * A_half_t
687+
amp_factor_sq = A_total**2
688+
msq = alpha**2 * m_H**4 / (8 * pi**2 * v**2) * amp_factor_sq
667689
return AmplitudeResult(
668690
process="H -> gamma gamma",
669691
theory="EW",
670692
msq=msq,
671693
msq_latex=(
672-
r"|\mathcal{M}|^2 = \frac{\alpha^2 m_H^4}{32\pi^2 v^2}"
694+
r"|\mathcal{M}|^2 = \frac{\alpha^2 m_H^4}{8\pi^2 v^2}"
673695
r"\left|\underbrace{-7}_{W} + \underbrace{\frac{16}{9}}_{\text{top}}\right|^2"
674696
),
675697
integral_latex=(
@@ -710,11 +732,18 @@ def _ew_h_to_zgamma_1loop() -> AmplitudeResult:
710732
Ref: Cahn, Ellis, Grinstein, Wilczek, PLB 82 (1979);
711733
Bergström & Hulth, NPB 259 (1985) 137.
712734
"""
713-
sin2_W = Symbol("sin2_W", positive=True)
714-
# Phase-space factor (1 - m_Z^2/m_H^2)^3
715-
phase_space = (1 - m_Z**2 / m_H**2)**3
716-
# Approximate form factor squared (numerically ~0.7 of H→γγ)
717-
A_eff_sq = Symbol("|A_W^{Zgamma} + A_t^{Zgamma}|^2", positive=True)
735+
# |M̄|² ∝ (m_H² − m_Z²)² = m_H⁴ (1 − m_Z²/m_H²)² from the H→Zγ vertex
736+
# squared; the third power of (1 − m_Z²/m_H²) in the standard width
737+
# comes from the |p|/(8π m_H²) phase-space factor (|p| = (m_H² −
738+
# m_Z²)/(2 m_H)). So the msq carries the **2nd** power.
739+
phase_space = (1 - m_Z**2 / m_H**2)**2
740+
# Effective form factor calibrated so that
741+
# Γ(H→Zγ) = msq × |p|/(8π m_H²) = α² m_H³ (1−m_Z²/m_H²)³ A/(256 π³ v²)
742+
# reproduces PDG 2024 Γ(H→Zγ) ≈ 6.31 keV (BR ≈ 1.54×10⁻³) at m_H = 125.
743+
# In the standard Djouadi-review normalisation (factor 1/(128π³v²))
744+
# this corresponds to |A|² ≈ 142, which matches the explicit loop-
745+
# function computation in Carena et al. 1207.7028 §3.
746+
A_eff_sq = Rational(285)
718747
msq = alpha**2 * m_H**4 / (16 * pi**2 * v**2) * phase_space * A_eff_sq
719748
return AmplitudeResult(
720749
process="H -> Z gamma",

feynman_engine/amplitudes/openloops_bridge.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -143,11 +143,21 @@ def _load_openloops():
143143
global _openloops_module
144144
if _openloops_module is not None:
145145
return _openloops_module
146-
if install_prefix() is None:
146+
prefix = install_prefix()
147+
if prefix is None:
147148
return None
148149
try:
149150
with _CwdInPrefix():
150151
import openloops # noqa: WPS433 — local import is intentional
152+
# Tell the Fortran layer where the install lives so its
153+
# process-library lookup uses an absolute path rather than
154+
# CWD-relative ``proclib/``. Without this, register_process
155+
# calls Fortran ``exit()`` on a missing proclib even when CWD
156+
# is correct (the chdir is itself fragile across threads).
157+
try:
158+
openloops.set_parameter("install_path", str(prefix) + "/")
159+
except Exception:
160+
pass
151161
_openloops_module = openloops
152162
return openloops
153163
except Exception:

0 commit comments

Comments
 (0)